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

Last change on this file since 2505 was 2505, checked in by alec, 4 years ago
  • Support UTF-7 encoding in messages (#1485832)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.8 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/main.inc                                              |
6 |                                                                       |
7 | This file is part of the RoundCube Webmail client                     |
8 | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Provide basic functions for the webmail package                     |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 +-----------------------------------------------------------------------+
17
18 $Id$
19
20*/
21
22/**
23 * RoundCube Webmail common functions
24 *
25 * @package Core
26 * @author Thomas Bruederli <roundcube@gmail.com>
27 */
28
29require_once('lib/utf7.inc');
30require_once('include/rcube_shared.inc');
31
32// fallback if not PHP modules are available
33@include_once('lib/des.inc');
34@include_once('lib/utf8.class.php');
35
36// define constannts for input reading
37define('RCUBE_INPUT_GET', 0x0101);
38define('RCUBE_INPUT_POST', 0x0102);
39define('RCUBE_INPUT_GPC', 0x0103);
40
41
42
43/**
44 * Return correct name for a specific database table
45 *
46 * @param string Table name
47 * @return string Translated table name
48 */
49function get_table_name($table)
50  {
51  global $CONFIG;
52
53  // return table name if configured
54  $config_key = 'db_table_'.$table;
55
56  if (strlen($CONFIG[$config_key]))
57    return $CONFIG[$config_key];
58
59  return $table;
60  }
61
62
63/**
64 * Return correct name for a specific database sequence
65 * (used for Postgres only)
66 *
67 * @param string Secuence name
68 * @return string Translated sequence name
69 */
70function get_sequence_name($sequence)
71  {
72  // return table name if configured
73  $config_key = 'db_sequence_'.$sequence;
74  $opt = rcmail::get_instance()->config->get($config_key);
75
76  if (!empty($opt))
77    return $opt;
78   
79  return $sequence;
80  }
81
82
83/**
84 * Get localized text in the desired language
85 * It's a global wrapper for rcmail::gettext()
86 *
87 * @param mixed Named parameters array or label name
88 * @return string Localized text
89 * @see rcmail::gettext()
90 */
91function rcube_label($p, $domain=null)
92{
93  return rcmail::get_instance()->gettext($p, $domain);
94}
95
96
97/**
98 * Overwrite action variable
99 *
100 * @param string New action value
101 */
102function rcmail_overwrite_action($action)
103  {
104  $app = rcmail::get_instance();
105  $app->action = $action;
106  $app->output->set_env('action', $action);
107  }
108
109
110/**
111 * Compose an URL for a specific action
112 *
113 * @param string  Request action
114 * @param array   More URL parameters
115 * @param string  Request task (omit if the same)
116 * @return The application URL
117 */
118function rcmail_url($action, $p=array(), $task=null)
119{
120  $app = rcmail::get_instance();
121  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
122}
123
124
125/**
126 * Garbage collector function for temp files.
127 * Remove temp files older than two days
128 */
129function rcmail_temp_gc()
130  {
131  $tmp = unslashify($CONFIG['temp_dir']);
132  $expire = mktime() - 172800;  // expire in 48 hours
133
134  if ($dir = opendir($tmp))
135    {
136    while (($fname = readdir($dir)) !== false)
137      {
138      if ($fname{0} == '.')
139        continue;
140
141      if (filemtime($tmp.'/'.$fname) < $expire)
142        @unlink($tmp.'/'.$fname);
143      }
144
145    closedir($dir);
146    }
147  }
148
149
150/**
151 * Garbage collector for cache entries.
152 * Remove all expired message cache records
153 */
154function rcmail_cache_gc()
155  {
156  $rcmail = rcmail::get_instance();
157  $db = $rcmail->get_dbh();
158 
159  // get target timestamp
160  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
161 
162  $db->query("DELETE FROM ".get_table_name('messages')."
163             WHERE  created < " . $db->fromunixtime($ts));
164
165  $db->query("DELETE FROM ".get_table_name('cache')."
166              WHERE  created < " . $db->fromunixtime($ts));
167  }
168
169
170/**
171 * Convert a string from one charset to another.
172 * Uses mbstring and iconv functions if possible
173 *
174 * @param  string Input string
175 * @param  string Suspected charset of the input string
176 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
177 * @return Converted string
178 */
179function rcube_charset_convert($str, $from, $to=NULL)
180  {
181  static $mbstring_loaded = null;
182  static $mbstring_list = null;
183  static $convert_warning = false;
184
185  $error = false;
186  $conv = null;
187
188  $to = empty($to) ? $to = strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
189  $from = rcube_parse_charset($from);
190
191  if ($from == $to || empty($str) || empty($from))
192    return $str;
193   
194  // convert charset using iconv module 
195  if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
196    $aliases['GB2312'] = 'GB18030';
197    $_iconv = iconv(($aliases[$from] ? $aliases[$from] : $from), ($aliases[$to] ? $aliases[$to] : $to) . "//IGNORE", $str);
198    if ($_iconv !== false) {
199        return $_iconv;
200    }
201  }
202
203  if (is_null($mbstring_loaded))
204    $mbstring_loaded = extension_loaded('mbstring');
205   
206  // convert charset using mbstring module
207  if ($mbstring_loaded) {
208    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
209   
210    if (is_null($mbstring_list)) {
211      $mbstring_list = mb_list_encodings();
212      $mbstring_list = array_map('strtoupper', $mbstring_list);
213    }
214
215    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
216    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
217   
218    // return if encoding found, string matches encoding and convert succeeded
219    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
220      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
221        return $out;
222    }
223  }
224
225  # try to convert with custom classes
226  if (class_exists('utf8'))
227    $conv = new utf8();
228
229  // convert string to UTF-8
230  if ($to == 'UTF-8') {
231    if ($from == 'UTF7-IMAP') {
232      if ($_str = utf7_to_utf8($str))
233        $str = $_str;
234      else
235        $error = true;
236    }
237    else if ($from == 'UTF-7') {
238      if ($_str = rcube_utf7_to_utf8($str))
239        $str = $_str;
240      else
241        $error = true;
242    }
243    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
244      $str = utf8_encode($str);
245    }
246    else if ($from != 'UTF-8' && $conv) {
247      $conv->loadCharset($from);
248      $str = $conv->strToUtf8($str);
249    }
250    else if ($from != 'UTF-8')
251      $error = true;
252  }
253 
254  // encode string for output
255  if ($from == 'UTF-8') {
256    // @TODO: we need a function for UTF-7 (RFC2152) conversion
257    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
258      if ($_str = utf8_to_utf7($str))
259        $str = $_str;
260      else
261        $error = true;
262    }
263    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
264      return utf8_decode($str);
265    }
266    else if ($to != 'UTF-8' && $conv) {
267      $conv->loadCharset($to);
268      return $conv->utf8ToStr($str);
269    }
270    else if ($to != 'UTF-8') {
271      $error = true;
272    }
273  }
274 
275  // report error
276  if ($error && !$convert_warning){
277    raise_error(array(
278      'code' => 500,
279      'type' => 'php',
280      'file' => __FILE__,
281      'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
282      ), true, false);
283   
284    $convert_warning = true;
285  }
286 
287  // return UTF-8 string
288  return $str;
289  }
290
291
292/**
293 * Parse and validate charset name string (see #1485758).
294 * Sometimes charset string is malformed, there are also charset aliases
295 * but we need strict names for charset conversion (specially utf8 class)
296 *
297 * @param  string  Input charset name
298 * @return The validated charset name
299 */
300function rcube_parse_charset($charset)
301  {
302  $charset = strtoupper($charset);
303
304  # RFC1642
305  $charset = str_replace('UNICODE-1-1-', '', $charset);
306
307  $aliases = array(
308    'USASCII'       => 'ISO-8859-1',
309    'ANSIX31101983' => 'ISO-8859-1',
310    'ANSIX341968'   => 'ISO-8859-1',
311    'UNKNOWN8BIT'   => 'ISO-8859-15',
312    'XUNKNOWN'      => 'ISO-8859-15',
313    'XUSERDEFINED'  => 'ISO-8859-15',
314    'ISO88598I'     => 'ISO-8859-8',
315    'KSC56011987'   => 'EUC-KR',
316    'UNICODE'       => 'UTF-8',
317    'UTF7IMAP'      => 'UTF7-IMAP'
318  );
319
320  $str = preg_replace('/[^a-z0-9]/i', '', $charset);
321
322  if (isset($aliases[$str]))
323    return $aliases[$str];
324
325  if (preg_match('/UTF(7|8|16|32)(BE|LE)*/', $str, $m))
326    return 'UTF-' . $m[1] . $m[2];
327
328  if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m))
329    return 'ISO-8859-' . ($m[1] ? $m[1] : 1);
330
331  return $charset;
332  }
333
334
335/**
336 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
337 *
338 * @param  string  Input string
339 * @return The converted string
340 */
341function rcube_utf7_to_utf8($str)
342{
343  $Index_64 = array(
344    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
345    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
346    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
347    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
348    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
349    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
350    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
351    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
352  );
353
354  $u7len = strlen($str);
355  $str = strval($str);
356  $res = '';
357
358  for ($i=0; $u7len > 0; $i++, $u7len--)
359  {
360    $u7 = $str[$i];
361    if ($u7 == '+')
362    {
363      $i++;
364      $u7len--;
365      $ch = '';
366
367      for (; $u7len > 0; $i++, $u7len--)
368      {
369        $u7 = $str[$i];
370
371        if (!$Index_64[ord($u7)])
372          break;
373
374        $ch .= $u7;
375      }
376
377      if ($ch == '') {
378        if ($u7 == '-')
379          $res .= '+';
380        continue;
381      }
382
383      $res .= rcube_utf16_to_utf8(base64_decode($ch));
384    }
385    else
386    {
387      $res .= $u7;
388    }
389  }
390
391  return $res;
392}
393
394/**
395 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
396 *
397 * @param  string  Input string
398 * @return The converted string
399 */
400function rcube_utf16_to_utf8($str)
401{
402  $len = strlen($str);
403  $dec = '';
404
405  for ($i = 0; $i < $len; $i += 2) {
406    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
407    if ($c >= 0x0001 && $c <= 0x007F) {
408      $dec .= chr($c);
409    } else if ($c > 0x07FF) {
410      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
411      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
412      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
413    } else {
414      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
415      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
416    }
417  }
418  return $dec;
419}
420
421
422/**
423 * Replacing specials characters to a specific encoding type
424 *
425 * @param  string  Input string
426 * @param  string  Encoding type: text|html|xml|js|url
427 * @param  string  Replace mode for tags: show|replace|remove
428 * @param  boolean Convert newlines
429 * @return The quoted string
430 */
431function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
432  {
433  static $html_encode_arr = false;
434  static $js_rep_table = false;
435  static $xml_rep_table = false;
436
437  $charset = rcmail::get_instance()->config->get('charset', RCMAIL_CHARSET);
438  $is_iso_8859_1 = false;
439  if ($charset == 'ISO-8859-1') {
440    $is_iso_8859_1 = true;
441  }
442  if (!$enctype)
443    $enctype = $OUTPUT->type;
444
445  // encode for plaintext
446  if ($enctype=='text')
447    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
448
449  // encode for HTML output
450  if ($enctype=='html')
451    {
452    if (!$html_encode_arr)
453      {
454      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);       
455      unset($html_encode_arr['?']);
456      }
457
458    $ltpos = strpos($str, '<');
459    $encode_arr = $html_encode_arr;
460
461    // don't replace quotes and html tags
462    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
463      {
464      unset($encode_arr['"']);
465      unset($encode_arr['<']);
466      unset($encode_arr['>']);
467      unset($encode_arr['&']);
468      }
469    else if ($mode=='remove')
470      $str = strip_tags($str);
471   
472    // avoid douple quotation of &
473    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
474     
475    return $newlines ? nl2br($out) : $out;
476    }
477
478  if ($enctype=='url')
479    return rawurlencode($str);
480
481  // if the replace tables for XML and JS are not yet defined
482  if ($js_rep_table===false)
483    {
484    $js_rep_table = $xml_rep_table = array();
485    $xml_rep_table['&'] = '&amp;';
486
487    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
488      {
489      $xml_rep_table[Chr($c)] = "&#$c;";
490     
491      if ($is_iso_8859_1)
492        $js_rep_table[Chr($c)] = sprintf("\\u%04x", $c);
493      }
494
495    $xml_rep_table['"'] = '&quot;';
496    $js_rep_table['"'] = '\\"';
497    $js_rep_table["'"] = "\\'";
498    $js_rep_table["\\"] = "\\\\";
499    }
500
501  // encode for XML
502  if ($enctype=='xml')
503    return strtr($str, $xml_rep_table);
504
505  // encode for javascript use
506  if ($enctype=='js')
507    {
508    if ($charset!='UTF-8')
509      $str = rcube_charset_convert($str, RCMAIL_CHARSET, $charset);
510     
511    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
512    }
513
514  // no encoding given -> return original string
515  return $str;
516  }
517 
518/**
519 * Quote a given string.
520 * Shortcut function for rep_specialchars_output
521 *
522 * @return string HTML-quoted string
523 * @see rep_specialchars_output()
524 */
525function Q($str, $mode='strict', $newlines=TRUE)
526  {
527  return rep_specialchars_output($str, 'html', $mode, $newlines);
528  }
529
530/**
531 * Quote a given string for javascript output.
532 * Shortcut function for rep_specialchars_output
533 *
534 * @return string JS-quoted string
535 * @see rep_specialchars_output()
536 */
537function JQ($str)
538  {
539  return rep_specialchars_output($str, 'js');
540  }
541
542
543/**
544 * Read input value and convert it for internal use
545 * Performs stripslashes() and charset conversion if necessary
546 *
547 * @param  string   Field name to read
548 * @param  int      Source to get value from (GPC)
549 * @param  boolean  Allow HTML tags in field value
550 * @param  string   Charset to convert into
551 * @return string   Field value or NULL if not available
552 */
553function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
554  {
555  global $OUTPUT;
556  $value = NULL;
557 
558  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
559    $value = $_GET[$fname];
560  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
561    $value = $_POST[$fname];
562  else if ($source==RCUBE_INPUT_GPC)
563    {
564    if (isset($_POST[$fname]))
565      $value = $_POST[$fname];
566    else if (isset($_GET[$fname]))
567      $value = $_GET[$fname];
568    else if (isset($_COOKIE[$fname]))
569      $value = $_COOKIE[$fname];
570    }
571 
572  // strip single quotes if magic_quotes_sybase is enabled
573  if (ini_get('magic_quotes_sybase'))
574    $value = str_replace("''", "'", $value);
575  // strip slashes if magic_quotes enabled
576  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
577    $value = stripslashes($value);
578
579  // remove HTML tags if not allowed   
580  if (!$allow_html)
581    $value = strip_tags($value);
582 
583  // convert to internal charset
584  if (is_object($OUTPUT))
585    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
586  else
587    return $value;
588  }
589
590/**
591 * Remove all non-ascii and non-word chars
592 * except . and -
593 */
594function asciiwords($str, $css_id = false)
595{
596  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
597  return preg_replace("/[^$allowed]/i", '', $str);
598}
599
600/**
601 * Remove single and double quotes from given string
602 *
603 * @param string Input value
604 * @return string Dequoted string
605 */
606function strip_quotes($str)
607{
608  return preg_replace('/[\'"]/', '', $str);
609}
610
611
612/**
613 * Remove new lines characters from given string
614 *
615 * @param string Input value
616 * @return string Stripped string
617 */
618function strip_newlines($str)
619{
620  return preg_replace('/[\r\n]/', '', $str);
621}
622
623
624/**
625 * Create a HTML table based on the given data
626 *
627 * @param  array  Named table attributes
628 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
629 * @param  array  List of cols to show
630 * @param  string Name of the identifier col
631 * @return string HTML table code
632 */
633function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
634  {
635  global $RCMAIL;
636 
637  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
638   
639  // add table header
640  foreach ($a_show_cols as $col)
641    $table->add_header($col, Q(rcube_label($col)));
642 
643  $c = 0;
644  if (!is_array($table_data))
645  {
646    $db = $RCMAIL->get_dbh();
647    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
648    {
649      $zebra_class = $c % 2 ? 'even' : 'odd';
650      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => "contact $zebra_class"));
651
652      // format each col
653      foreach ($a_show_cols as $col)
654        $table->add($col, Q($sql_arr[$col]));
655     
656      $c++;
657    }
658  }
659  else
660  {
661    foreach ($table_data as $row_data)
662    {
663      $zebra_class = $c % 2 ? 'even' : 'odd';
664      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => "contact $zebra_class"));
665
666      // format each col
667      foreach ($a_show_cols as $col)
668        $table->add($col, Q($row_data[$col]));
669       
670      $c++;
671    }
672  }
673
674  return $table->show($attrib);
675  }
676
677
678/**
679 * Create an edit field for inclusion on a form
680 *
681 * @param string col field name
682 * @param string value field value
683 * @param array attrib HTML element attributes for field
684 * @param string type HTML element type (default 'text')
685 * @return string HTML field definition
686 */
687function rcmail_get_edit_field($col, $value, $attrib, $type='text')
688  {
689  $fname = '_'.$col;
690  $attrib['name'] = $fname;
691 
692  if ($type=='checkbox')
693    {
694    $attrib['value'] = '1';
695    $input = new html_checkbox($attrib);
696    }
697  else if ($type=='textarea')
698    {
699    $attrib['cols'] = $attrib['size'];
700    $input = new html_textarea($attrib);
701    }
702  else
703    $input = new html_inputfield($attrib);
704
705  // use value from post
706  if (!empty($_POST[$fname]))
707    $value = get_input_value($fname, RCUBE_INPUT_POST,
708            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
709
710  $out = $input->show($value);
711         
712  return $out;
713  }
714
715
716/**
717 * Replace all css definitions with #container [def]
718 * and remove css-inlined scripting
719 *
720 * @param string CSS source code
721 * @param string Container ID to use as prefix
722 * @return string Modified CSS source
723 */
724function rcmail_mod_css_styles($source, $container_id)
725  {
726  $last_pos = 0;
727  $replacements = new rcube_string_replacer;
728 
729  // ignore the whole block if evil styles are detected
730  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entitiy_decode($source));
731  if (preg_match('/expression|behavior|url\(|import/', $stripped))
732    return '/* evil! */';
733
734  // cut out all contents between { and }
735  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
736  {
737    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
738    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
739    $last_pos = $pos+2;
740  }
741 
742  // remove html comments and add #container to each tag selector.
743  // also replace body definition because we also stripped off the <body> tag
744  $styles = preg_replace(
745    array(
746      '/(^\s*<!--)|(-->\s*$)/',
747      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
748      "/$container_id\s+body/i",
749    ),
750    array(
751      '',
752      "\\1#$container_id \\2",
753      "$container_id div.rcmBody",
754    ),
755    $source);
756 
757  // put block contents back in
758  $styles = $replacements->resolve($styles);
759
760  return $styles;
761  }
762
763
764/**
765 * Decode escaped entities used by known XSS exploits.
766 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
767 *
768 * @param string CSS content to decode
769 * @return string Decoded string
770 */
771function rcmail_xss_entitiy_decode($content)
772{
773  $out = html_entity_decode(html_entity_decode($content));
774  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entitiy_decode_callback', $out);
775  $out = preg_replace('#/\*.*\*/#Um', '', $out);
776  return $out;
777}
778
779
780/**
781 * preg_replace_callback callback for rcmail_xss_entitiy_decode_callback
782 *
783 * @param array matches result from preg_replace_callback
784 * @return string decoded entity
785 */
786function rcmail_xss_entitiy_decode_callback($matches)
787{
788  return chr(hexdec($matches[1]));
789}
790
791/**
792 * Compose a valid attribute string for HTML tags
793 *
794 * @param array Named tag attributes
795 * @param array List of allowed attributes
796 * @return string HTML formatted attribute string
797 */
798function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
799  {
800  // allow the following attributes to be added to the <iframe> tag
801  $attrib_str = '';
802  foreach ($allowed_attribs as $a)
803    if (isset($attrib[$a]))
804      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
805
806  return $attrib_str;
807  }
808
809
810/**
811 * Convert a HTML attribute string attributes to an associative array (name => value)
812 *
813 * @param string Input string
814 * @return array Key-value pairs of parsed attributes
815 */
816function parse_attrib_string($str)
817  {
818  $attrib = array();
819  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
820
821  // convert attributes to an associative array (name => value)
822  if ($regs) {
823    foreach ($regs as $attr) {
824      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
825    }
826  }
827
828  return $attrib;
829  }
830
831
832/**
833 * Convert the given date to a human readable form
834 * This uses the date formatting properties from config
835 *
836 * @param mixed Date representation (string or timestamp)
837 * @param string Date format to use
838 * @return string Formatted date string
839 */
840function format_date($date, $format=NULL)
841  {
842  global $CONFIG;
843 
844  $ts = NULL;
845
846  if (is_numeric($date))
847    $ts = $date;
848  else if (!empty($date))
849    {
850    // support non-standard "GMTXXXX" literal
851    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
852    // if date parsing fails, we have a date in non-rfc format.
853    // remove token from the end and try again
854    while ((($ts = @strtotime($date))===false) || ($ts < 0))
855      {
856        $d = explode(' ', $date);
857        array_pop($d);
858        if (!$d) break;
859        $date = implode(' ', $d);
860      }
861    }
862
863  if (empty($ts))
864    return '';
865   
866  // get user's timezone
867  if ($CONFIG['timezone'] === 'auto')
868    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
869  else {
870    $tz = $CONFIG['timezone'];
871    if ($CONFIG['dst_active'])
872      $tz++;
873  }
874
875  // convert time to user's timezone
876  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
877 
878  // get current timestamp in user's timezone
879  $now = time();  // local time
880  $now -= (int)date('Z'); // make GMT time
881  $now += ($tz * 3600); // user's time
882  $now_date = getdate($now);
883
884  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
885  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
886
887  // define date format depending on current time 
888  if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
889    return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
890  else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
891    $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
892  else if (!$format)
893    $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
894
895
896  // parse format string manually in order to provide localized weekday and month names
897  // an alternative would be to convert the date() format string to fit with strftime()
898  $out = '';
899  for($i=0; $i<strlen($format); $i++)
900    {
901    if ($format{$i}=='\\')  // skip escape chars
902      continue;
903   
904    // write char "as-is"
905    if ($format{$i}==' ' || $format{$i-1}=='\\')
906      $out .= $format{$i};
907    // weekday (short)
908    else if ($format{$i}=='D')
909      $out .= rcube_label(strtolower(date('D', $timestamp)));
910    // weekday long
911    else if ($format{$i}=='l')
912      $out .= rcube_label(strtolower(date('l', $timestamp)));
913    // month name (short)
914    else if ($format{$i}=='M')
915      $out .= rcube_label(strtolower(date('M', $timestamp)));
916    // month name (long)
917    else if ($format{$i}=='F')
918      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
919    else if ($format{$i}=='x')
920      $out .= strftime('%x %X', $timestamp);
921    else
922      $out .= date($format{$i}, $timestamp);
923    }
924 
925  return $out;
926  }
927
928
929/**
930 * Compose a valid representaion of name and e-mail address
931 *
932 * @param string E-mail address
933 * @param string Person name
934 * @return string Formatted string
935 */
936function format_email_recipient($email, $name='')
937  {
938  if ($name && $name != $email)
939    {
940    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
941    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, $email);
942    }
943  else
944    return $email;
945  }
946
947
948
949/****** debugging functions ********/
950
951
952/**
953 * Print or write debug messages
954 *
955 * @param mixed Debug message or data
956 */
957function console()
958  {
959  $args = func_get_args();
960
961  if (class_exists('rcmail', false))
962    rcmail::get_instance()->plugins->exec_hook('console', $args);
963
964  $msg = array();
965  foreach ($args as $arg)
966    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
967
968  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
969    write_log('console', join(";\n", $msg));
970  else if ($GLOBALS['OUTPUT']->ajax_call)
971    print "/*\n " . join(";\n", $msg) . " \n*/\n";
972  else
973    {
974    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
975    print join(";<br/>\n", $msg);
976    print "</pre></div>\n";
977    }
978  }
979
980
981/**
982 * Append a line to a logfile in the logs directory.
983 * Date will be added automatically to the line.
984 *
985 * @param $name name of log file
986 * @param line Line to append
987 */
988function write_log($name, $line)
989  {
990  global $CONFIG;
991
992  if (!is_string($line))
993    $line = var_export($line, true);
994 
995  if (empty($CONFIG['log_date_format']))
996    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
997 
998  $log_entry = sprintf("[%s]: %s\n", date($CONFIG['log_date_format']), $line);
999
1000  if ($CONFIG['log_driver'] == 'syslog') {
1001    if ($name == 'errors')
1002      $prio = LOG_ERR;
1003    else
1004      $prio = LOG_INFO;
1005    syslog($prio, $log_entry);
1006  } else {
1007    // log_driver == 'file' is assumed here
1008    if (empty($CONFIG['log_dir']))
1009      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1010
1011    // try to open specific log file for writing
1012    if ($fp = @fopen($CONFIG['log_dir'].'/'.$name, 'a')) {
1013      fwrite($fp, $log_entry);
1014      fflush($fp);
1015      fclose($fp);
1016    }
1017  }
1018}
1019
1020
1021/**
1022 * @access private
1023 */
1024function rcube_timer()
1025  {
1026  list($usec, $sec) = explode(" ", microtime());
1027  return ((float)$usec + (float)$sec);
1028  }
1029 
1030
1031/**
1032 * @access private
1033 */
1034function rcube_print_time($timer, $label='Timer')
1035  {
1036  static $print_count = 0;
1037 
1038  $print_count++;
1039  $now = rcube_timer();
1040  $diff = $now-$timer;
1041 
1042  if (empty($label))
1043    $label = 'Timer '.$print_count;
1044 
1045  console(sprintf("%s: %0.4f sec", $label, $diff));
1046  }
1047
1048
1049/**
1050 * Return the mailboxlist in HTML
1051 *
1052 * @param array Named parameters
1053 * @return string HTML code for the gui object
1054 */
1055function rcmail_mailbox_list($attrib)
1056{
1057  global $RCMAIL;
1058  static $a_mailboxes;
1059 
1060  $attrib += array('maxlength' => 100, 'relanames' => false);
1061
1062  // add some labels to client
1063  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1064 
1065  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1066  unset($attrib['type']);
1067
1068  if ($type=='ul' && !$attrib['id'])
1069    $attrib['id'] = 'rcmboxlist';
1070
1071  // get mailbox list
1072  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1073 
1074  // build the folders tree
1075  if (empty($a_mailboxes)) {
1076    // get mailbox list
1077    $a_folders = $RCMAIL->imap->list_mailboxes();
1078    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1079    $a_mailboxes = array();
1080
1081    foreach ($a_folders as $folder)
1082      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1083  }
1084
1085  if ($type=='select') {
1086    $select = new html_select($attrib);
1087   
1088    // add no-selection option
1089    if ($attrib['noselection'])
1090      $select->add(rcube_label($attrib['noselection']), '0');
1091   
1092    rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1093    $out = $select->show();
1094  }
1095  else {
1096    $js_mailboxlist = array();
1097    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1098   
1099    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1100    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1101    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1102  }
1103
1104  return $out;
1105}
1106
1107
1108/**
1109 * Return the mailboxlist as html_select object
1110 *
1111 * @param array Named parameters
1112 * @return object html_select HTML drop-down object
1113 */
1114function rcmail_mailbox_select($p = array())
1115{
1116  global $RCMAIL;
1117 
1118  $p += array('maxlength' => 100, 'relanames' => false);
1119  $a_mailboxes = array();
1120 
1121  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1122    rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1123
1124  $select = new html_select($p);
1125 
1126  if ($p['noselection'])
1127    $select->add($p['noselection'], '');
1128   
1129  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1130 
1131  return $select;
1132}
1133
1134
1135/**
1136 * Create a hierarchical array of the mailbox list
1137 * @access private
1138 */
1139function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1140{
1141  $pos = strpos($folder, $delm);
1142  if ($pos !== false) {
1143    $subFolders = substr($folder, $pos+1);
1144    $currentFolder = substr($folder, 0, $pos);
1145    $virtual = !isset($arrFolders[$currentFolder]);
1146  }
1147  else {
1148    $subFolders = false;
1149    $currentFolder = $folder;
1150    $virtual = false;
1151  }
1152
1153  $path .= $currentFolder;
1154
1155  if (!isset($arrFolders[$currentFolder])) {
1156    $arrFolders[$currentFolder] = array(
1157      'id' => $path,
1158      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1159      'virtual' => $virtual,
1160      'folders' => array());
1161  }
1162  else
1163    $arrFolders[$currentFolder]['virtual'] = $virtual;
1164
1165  if (!empty($subFolders))
1166    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1167}
1168 
1169
1170/**
1171 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1172 * @access private
1173 */
1174function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1175{
1176  global $RCMAIL, $CONFIG;
1177 
1178  $maxlength = intval($attrib['maxlength']);
1179  $realnames = (bool)$attrib['realnames'];
1180  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1181
1182  $idx = 0;
1183  $out = '';
1184  foreach ($arrFolders as $key => $folder) {
1185    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1186    $title = null;
1187
1188    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1189      $foldername = rcube_label($folder_class);
1190    }
1191    else {
1192      $foldername = $folder['name'];
1193
1194      // shorten the folder name to a given length
1195      if ($maxlength && $maxlength > 1) {
1196        $fname = abbreviate_string($foldername, $maxlength);
1197        if ($fname != $foldername)
1198          $title = $foldername;
1199        $foldername = $fname;
1200      }
1201    }
1202
1203    // make folder name safe for ids and class names
1204    $folder_id = asciiwords($folder['id'], true);
1205    $classes = array('mailbox');
1206
1207    // set special class for Sent, Drafts, Trash and Junk
1208    if ($folder['id']==$CONFIG['sent_mbox'])
1209      $classes[] = 'sent';
1210    else if ($folder['id']==$CONFIG['drafts_mbox'])
1211      $classes[] = 'drafts';
1212    else if ($folder['id']==$CONFIG['trash_mbox'])
1213      $classes[] = 'trash';
1214    else if ($folder['id']==$CONFIG['junk_mbox'])
1215      $classes[] = 'junk';
1216    else if ($folder['id']=='INBOX')
1217      $classes[] = 'inbox';
1218    else
1219      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1220     
1221    $classes[] = $zebra_class;
1222   
1223    if ($folder['id'] == $mbox_name)
1224      $classes[] = 'selected';
1225
1226    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1227    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1228   
1229    if ($folder['virtual'])
1230      $classes[] = 'virtual';
1231    else if ($unread)
1232      $classes[] = 'unread';
1233
1234    $js_name = JQ($folder['id']);
1235    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1236    $link_attrib = $folder['virtual'] ? array() : array(
1237      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1238      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1239      'title' => $title,
1240    );
1241
1242    $out .= html::tag('li', array(
1243        'id' => "rcmli".$folder_id,
1244        'class' => join(' ', $classes),
1245        'noclose' => true),
1246      html::a($link_attrib, $html_name) .
1247      (!empty($folder['folders']) ? html::div(array(
1248        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1249        'style' => "position:absolute",
1250        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1251      ), '&nbsp;') : ''));
1252   
1253    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1254   
1255    if (!empty($folder['folders'])) {
1256      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1257        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1258    }
1259
1260    $out .= "</li>\n";
1261    $idx++;
1262  }
1263
1264  return $out;
1265}
1266
1267
1268/**
1269 * Return html for a flat list <select> for the mailbox tree
1270 * @access private
1271 */
1272function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1273  {
1274  $idx = 0;
1275  $out = '';
1276  foreach ($arrFolders as $key=>$folder)
1277    {
1278    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1279      $foldername = rcube_label($folder_class);
1280    else
1281      {
1282      $foldername = $folder['name'];
1283     
1284      // shorten the folder name to a given length
1285      if ($maxlength && $maxlength>1)
1286        $foldername = abbreviate_string($foldername, $maxlength);
1287      }
1288
1289    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1290
1291    if (!empty($folder['folders']))
1292      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1293
1294    $idx++;
1295    }
1296
1297  return $out;
1298  }
1299
1300
1301/**
1302 * Return internal name for the given folder if it matches the configured special folders
1303 * @access private
1304 */
1305function rcmail_folder_classname($folder_id)
1306{
1307  global $CONFIG;
1308
1309  // for these mailboxes we have localized labels and css classes
1310  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1311  {
1312    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1313      return $smbx;
1314  }
1315
1316  if ($folder_id == 'INBOX')
1317    return 'inbox';
1318}
1319
1320
1321/**
1322 * Try to localize the given IMAP folder name.
1323 * UTF-7 decode it in case no localized text was found
1324 *
1325 * @param string Folder name
1326 * @return string Localized folder name in UTF-8 encoding
1327 */
1328function rcmail_localize_foldername($name)
1329{
1330  if ($folder_class = rcmail_folder_classname($name))
1331    return rcube_label($folder_class);
1332  else
1333    return rcube_charset_convert($name, 'UTF7-IMAP');
1334}
1335
1336
1337/**
1338 * Output HTML editor scripts
1339 *
1340 * @param string Editor mode
1341 */
1342function rcube_html_editor($mode='')
1343{
1344  global $OUTPUT, $CONFIG;
1345
1346  $lang = $tinylang = strtolower(substr($_SESSION['language'], 0, 2));
1347  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$tinylang.'.js'))
1348    $tinylang = 'en';
1349
1350  $OUTPUT->include_script('tiny_mce/tiny_mce.js');
1351  $OUTPUT->include_script('editor.js');
1352  $OUTPUT->add_script('rcmail_editor_init("$__skin_path", "'.JQ($tinylang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1353}
1354
1355
1356
1357/**
1358 * Helper class to turn relative urls into absolute ones
1359 * using a predefined base
1360 */
1361class rcube_base_replacer
1362{
1363  private $base_url;
1364 
1365  public function __construct($base)
1366  {
1367    $this->base_url = $base;
1368  }
1369 
1370  public function callback($matches)
1371  {
1372    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1373  }
1374}
1375
1376?>
Note: See TracBrowser for help on using the repository browser.