source: github/program/include/main.inc @ 9e953bc2

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 9e953bc2 was 5dc7c21, checked in by alecpl <alec@…>, 4 years ago
  • Fix charset names with X- prefix handling
  • Property mode set to 100644
File size: 36.4 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  $rcmail = rcmail::get_instance();
132
133  $tmp = unslashify($rcmail->config->get('temp_dir'));
134  $expire = mktime() - 172800;  // expire in 48 hours
135
136  if ($dir = opendir($tmp))
137    {
138    while (($fname = readdir($dir)) !== false)
139      {
140      if ($fname{0} == '.')
141        continue;
142
143      if (filemtime($tmp.'/'.$fname) < $expire)
144        @unlink($tmp.'/'.$fname);
145      }
146
147    closedir($dir);
148    }
149  }
150
151
152/**
153 * Garbage collector for cache entries.
154 * Remove all expired message cache records
155 */
156function rcmail_cache_gc()
157  {
158  $rcmail = rcmail::get_instance();
159  $db = $rcmail->get_dbh();
160 
161  // get target timestamp
162  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
163 
164  $db->query("DELETE FROM ".get_table_name('messages')."
165             WHERE  created < " . $db->fromunixtime($ts));
166
167  $db->query("DELETE FROM ".get_table_name('cache')."
168              WHERE  created < " . $db->fromunixtime($ts));
169  }
170
171
172/**
173 * Convert a string from one charset to another.
174 * Uses mbstring and iconv functions if possible
175 *
176 * @param  string Input string
177 * @param  string Suspected charset of the input string
178 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
179 * @return Converted string
180 */
181function rcube_charset_convert($str, $from, $to=NULL)
182  {
183  static $mbstring_loaded = null;
184  static $mbstring_list = null;
185  static $convert_warning = false;
186  static $conv = null;
187 
188  $error = false;
189
190  $to = empty($to) ? $to = strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
191  $from = rcube_parse_charset($from);
192
193  if ($from == $to || empty($str) || empty($from))
194    return $str;
195
196  // convert charset using iconv module 
197  if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
198    $_iconv = iconv($from, $to . '//IGNORE', $str);
199    if ($_iconv !== false) {
200        return $_iconv;
201    }
202  }
203
204  if (is_null($mbstring_loaded))
205    $mbstring_loaded = extension_loaded('mbstring');
206   
207  // convert charset using mbstring module
208  if ($mbstring_loaded) {
209    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
210   
211    if (is_null($mbstring_list)) {
212      $mbstring_list = mb_list_encodings();
213      $mbstring_list = array_map('strtoupper', $mbstring_list);
214    }
215
216    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
217    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
218   
219    // return if encoding found, string matches encoding and convert succeeded
220    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
221      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
222        return $out;
223    }
224  }
225
226  // convert charset using bundled classes/functions
227  if ($to == 'UTF-8') {
228    if ($from == 'UTF7-IMAP') {
229      if ($_str = utf7_to_utf8($str))
230        return $_str;
231    }
232    else if ($from == 'UTF-7') {
233      if ($_str = rcube_utf7_to_utf8($str))
234        return $_str;
235    }
236    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
237      return utf8_encode($str);
238    }
239    else if (class_exists('utf8')) {
240      if (!$conv)
241        $conv = new utf8($from);
242      else
243        $conv->loadCharset($from);
244
245      if($_str = $conv->strToUtf8($str))
246        return $_str;
247    }
248    $error = true;
249  }
250 
251  // encode string for output
252  if ($from == 'UTF-8') {
253    // @TODO: we need a function for UTF-7 (RFC2152) conversion
254    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
255      if ($_str = utf8_to_utf7($str))
256        return $_str;
257    }
258    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
259      return utf8_decode($str);
260    }
261    else if (class_exists('utf8')) {
262      if (!$conv)
263        $conv = new utf8($to);
264      else
265        $conv->loadCharset($from);
266
267      if ($_str = $conv->strToUtf8($str))
268        return $_str;
269    }
270    $error = true;
271  }
272 
273  // report error
274  if ($error && !$convert_warning) {
275    raise_error(array(
276      'code' => 500,
277      'type' => 'php',
278      'file' => __FILE__,
279      'line' => __LINE__,
280      'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
281      ), true, false);
282   
283    $convert_warning = true;
284  }
285 
286  // return UTF-8 or original string
287  return $str;
288  }
289
290
291/**
292 * Parse and validate charset name string (see #1485758).
293 * Sometimes charset string is malformed, there are also charset aliases
294 * but we need strict names for charset conversion (specially utf8 class)
295 *
296 * @param  string  Input charset name
297 * @return The validated charset name
298 */
299function rcube_parse_charset($charset)
300  {
301  $charset = strtoupper($charset);
302
303  # RFC1642
304  $charset = str_replace('UNICODE-1-1-', '', $charset);
305
306  # Aliases: some of them from HTML5 spec.
307  $aliases = array(
308    'USASCII'       => 'WINDOWS-1252',
309    'ANSIX31101983' => 'WINDOWS-1252',
310    'ANSIX341968'   => 'WINDOWS-1252',
311    'UNKNOWN8BIT'   => 'ISO-8859-15',
312    'UNKNOWN'       => 'ISO-8859-15',
313    'USERDEFINED'   => 'ISO-8859-15',
314    'KSC56011987'   => 'EUC-KR',
315    'GB2312'        => 'GBK',
316    'GB231280'      => 'GBK',
317    'UNICODE'       => 'UTF-8',
318    'UTF7IMAP'      => 'UTF7-IMAP',
319    'TIS620'        => 'WINDOWS-874',
320    'ISO88599'      => 'WINDOWS-1254',
321    'ISO885911'     => 'WINDOWS-874',
322  );
323
324  // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8)
325  $str = preg_replace(array('/[^a-z0-9]/i', '/^x+/i'), '', $charset);
326
327  if (isset($aliases[$str]))
328    return $aliases[$str];
329
330  if (preg_match('/UTF(7|8|16|32)(BE|LE)*/', $str, $m))
331    return 'UTF-' . $m[1] . $m[2];
332
333  if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
334    $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
335    # some clients sends windows-1252 text as latin1,
336    # it is safe to use windows-1252 for all latin1
337    return $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
338    }
339
340  return $charset;
341  }
342
343
344/**
345 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
346 *
347 * @param  string  Input string
348 * @return The converted string
349 */
350function rcube_utf7_to_utf8($str)
351{
352  $Index_64 = array(
353    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
354    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
355    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
356    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
357    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
358    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
359    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
360    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
361  );
362
363  $u7len = strlen($str);
364  $str = strval($str);
365  $res = '';
366
367  for ($i=0; $u7len > 0; $i++, $u7len--)
368  {
369    $u7 = $str[$i];
370    if ($u7 == '+')
371    {
372      $i++;
373      $u7len--;
374      $ch = '';
375
376      for (; $u7len > 0; $i++, $u7len--)
377      {
378        $u7 = $str[$i];
379
380        if (!$Index_64[ord($u7)])
381          break;
382
383        $ch .= $u7;
384      }
385
386      if ($ch == '') {
387        if ($u7 == '-')
388          $res .= '+';
389        continue;
390      }
391
392      $res .= rcube_utf16_to_utf8(base64_decode($ch));
393    }
394    else
395    {
396      $res .= $u7;
397    }
398  }
399
400  return $res;
401}
402
403/**
404 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
405 *
406 * @param  string  Input string
407 * @return The converted string
408 */
409function rcube_utf16_to_utf8($str)
410{
411  $len = strlen($str);
412  $dec = '';
413
414  for ($i = 0; $i < $len; $i += 2) {
415    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
416    if ($c >= 0x0001 && $c <= 0x007F) {
417      $dec .= chr($c);
418    } else if ($c > 0x07FF) {
419      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
420      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
421      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
422    } else {
423      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
424      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
425    }
426  }
427  return $dec;
428}
429
430
431/**
432 * Replacing specials characters to a specific encoding type
433 *
434 * @param  string  Input string
435 * @param  string  Encoding type: text|html|xml|js|url
436 * @param  string  Replace mode for tags: show|replace|remove
437 * @param  boolean Convert newlines
438 * @return The quoted string
439 */
440function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
441  {
442  static $html_encode_arr = false;
443  static $js_rep_table = false;
444  static $xml_rep_table = false;
445
446  if (!$enctype)
447    $enctype = $OUTPUT->type;
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 the replace tables for XML and JS are not yet defined
479  if ($js_rep_table===false)
480    {
481    $js_rep_table = $xml_rep_table = array();
482    $xml_rep_table['&'] = '&amp;';
483
484    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
485      $xml_rep_table[Chr($c)] = "&#$c;";
486
487    $xml_rep_table['"'] = '&quot;';
488    $js_rep_table['"'] = '\\"';
489    $js_rep_table["'"] = "\\'";
490    $js_rep_table["\\"] = "\\\\";
491    }
492
493  // encode for javascript use
494  if ($enctype=='js')
495    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
496
497  // encode for plaintext
498  if ($enctype=='text')
499    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
500
501  if ($enctype=='url')
502    return rawurlencode($str);
503
504  // encode for XML
505  if ($enctype=='xml')
506    return strtr($str, $xml_rep_table);
507
508  // no encoding given -> return original string
509  return $str;
510  }
511 
512/**
513 * Quote a given string.
514 * Shortcut function for rep_specialchars_output
515 *
516 * @return string HTML-quoted string
517 * @see rep_specialchars_output()
518 */
519function Q($str, $mode='strict', $newlines=TRUE)
520  {
521  return rep_specialchars_output($str, 'html', $mode, $newlines);
522  }
523
524/**
525 * Quote a given string for javascript output.
526 * Shortcut function for rep_specialchars_output
527 *
528 * @return string JS-quoted string
529 * @see rep_specialchars_output()
530 */
531function JQ($str)
532  {
533  return rep_specialchars_output($str, 'js');
534  }
535
536
537/**
538 * Read input value and convert it for internal use
539 * Performs stripslashes() and charset conversion if necessary
540 *
541 * @param  string   Field name to read
542 * @param  int      Source to get value from (GPC)
543 * @param  boolean  Allow HTML tags in field value
544 * @param  string   Charset to convert into
545 * @return string   Field value or NULL if not available
546 */
547function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
548  {
549  global $OUTPUT;
550  $value = NULL;
551 
552  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
553    $value = $_GET[$fname];
554  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
555    $value = $_POST[$fname];
556  else if ($source==RCUBE_INPUT_GPC)
557    {
558    if (isset($_POST[$fname]))
559      $value = $_POST[$fname];
560    else if (isset($_GET[$fname]))
561      $value = $_GET[$fname];
562    else if (isset($_COOKIE[$fname]))
563      $value = $_COOKIE[$fname];
564    }
565
566  if (empty($value))
567    return $value;
568
569  // strip single quotes if magic_quotes_sybase is enabled
570  if (ini_get('magic_quotes_sybase'))
571    $value = str_replace("''", "'", $value);
572  // strip slashes if magic_quotes enabled
573  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
574    $value = stripslashes($value);
575
576  // remove HTML tags if not allowed   
577  if (!$allow_html)
578    $value = strip_tags($value);
579 
580  // convert to internal charset
581  if (is_object($OUTPUT))
582    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
583  else
584    return $value;
585  }
586
587/**
588 * Remove all non-ascii and non-word chars
589 * except ., -, _
590 */
591function asciiwords($str, $css_id = false, $replace_with = '')
592{
593  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
594  return preg_replace("/[^$allowed]/i", $replace_with, $str);
595}
596
597/**
598 * Remove single and double quotes from given string
599 *
600 * @param string Input value
601 * @return string Dequoted string
602 */
603function strip_quotes($str)
604{
605  return preg_replace('/[\'"]/', '', $str);
606}
607
608
609/**
610 * Remove new lines characters from given string
611 *
612 * @param string Input value
613 * @return string Stripped string
614 */
615function strip_newlines($str)
616{
617  return preg_replace('/[\r\n]/', '', $str);
618}
619
620
621/**
622 * Create a HTML table based on the given data
623 *
624 * @param  array  Named table attributes
625 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
626 * @param  array  List of cols to show
627 * @param  string Name of the identifier col
628 * @return string HTML table code
629 */
630function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
631  {
632  global $RCMAIL;
633 
634  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
635   
636  // add table header
637  foreach ($a_show_cols as $col)
638    $table->add_header($col, Q(rcube_label($col)));
639 
640  $c = 0;
641  if (!is_array($table_data))
642  {
643    $db = $RCMAIL->get_dbh();
644    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
645    {
646      $zebra_class = $c % 2 ? 'even' : 'odd';
647      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => "contact $zebra_class"));
648
649      // format each col
650      foreach ($a_show_cols as $col)
651        $table->add($col, Q($sql_arr[$col]));
652     
653      $c++;
654    }
655  }
656  else
657  {
658    foreach ($table_data as $row_data)
659    {
660      $zebra_class = $c % 2 ? 'even' : 'odd';
661      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => "contact $zebra_class"));
662
663      // format each col
664      foreach ($a_show_cols as $col)
665        $table->add($col, Q($row_data[$col]));
666       
667      $c++;
668    }
669  }
670
671  return $table->show($attrib);
672  }
673
674
675/**
676 * Create an edit field for inclusion on a form
677 *
678 * @param string col field name
679 * @param string value field value
680 * @param array attrib HTML element attributes for field
681 * @param string type HTML element type (default 'text')
682 * @return string HTML field definition
683 */
684function rcmail_get_edit_field($col, $value, $attrib, $type='text')
685  {
686  $fname = '_'.$col;
687  $attrib['name'] = $fname;
688 
689  if ($type=='checkbox')
690    {
691    $attrib['value'] = '1';
692    $input = new html_checkbox($attrib);
693    }
694  else if ($type=='textarea')
695    {
696    $attrib['cols'] = $attrib['size'];
697    $input = new html_textarea($attrib);
698    }
699  else
700    $input = new html_inputfield($attrib);
701
702  // use value from post
703  if (!empty($_POST[$fname]))
704    $value = get_input_value($fname, RCUBE_INPUT_POST,
705            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
706
707  $out = $input->show($value);
708         
709  return $out;
710  }
711
712
713/**
714 * Replace all css definitions with #container [def]
715 * and remove css-inlined scripting
716 *
717 * @param string CSS source code
718 * @param string Container ID to use as prefix
719 * @return string Modified CSS source
720 */
721function rcmail_mod_css_styles($source, $container_id)
722  {
723  $last_pos = 0;
724  $replacements = new rcube_string_replacer;
725 
726  // ignore the whole block if evil styles are detected
727  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
728  if (preg_match('/expression|behavior|url\(|import/', $stripped))
729    return '/* evil! */';
730
731  // cut out all contents between { and }
732  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
733  {
734    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
735    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
736    $last_pos = $pos+2;
737  }
738 
739  // remove html comments and add #container to each tag selector.
740  // also replace body definition because we also stripped off the <body> tag
741  $styles = preg_replace(
742    array(
743      '/(^\s*<!--)|(-->\s*$)/',
744      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
745      "/$container_id\s+body/i",
746    ),
747    array(
748      '',
749      "\\1#$container_id \\2",
750      "$container_id div.rcmBody",
751    ),
752    $source);
753 
754  // put block contents back in
755  $styles = $replacements->resolve($styles);
756
757  return $styles;
758  }
759
760
761/**
762 * Decode escaped entities used by known XSS exploits.
763 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
764 *
765 * @param string CSS content to decode
766 * @return string Decoded string
767 */
768function rcmail_xss_entity_decode($content)
769{
770  $out = html_entity_decode(html_entity_decode($content));
771  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
772  $out = preg_replace('#/\*.*\*/#Um', '', $out);
773  return $out;
774}
775
776
777/**
778 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
779 *
780 * @param array matches result from preg_replace_callback
781 * @return string decoded entity
782 */
783function rcmail_xss_entity_decode_callback($matches)
784{
785  return chr(hexdec($matches[1]));
786}
787
788/**
789 * Compose a valid attribute string for HTML tags
790 *
791 * @param array Named tag attributes
792 * @param array List of allowed attributes
793 * @return string HTML formatted attribute string
794 */
795function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
796  {
797  // allow the following attributes to be added to the <iframe> tag
798  $attrib_str = '';
799  foreach ($allowed_attribs as $a)
800    if (isset($attrib[$a]))
801      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
802
803  return $attrib_str;
804  }
805
806
807/**
808 * Convert a HTML attribute string attributes to an associative array (name => value)
809 *
810 * @param string Input string
811 * @return array Key-value pairs of parsed attributes
812 */
813function parse_attrib_string($str)
814  {
815  $attrib = array();
816  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
817
818  // convert attributes to an associative array (name => value)
819  if ($regs) {
820    foreach ($regs as $attr) {
821      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
822    }
823  }
824
825  return $attrib;
826  }
827
828
829/**
830 * Convert the given date to a human readable form
831 * This uses the date formatting properties from config
832 *
833 * @param mixed Date representation (string or timestamp)
834 * @param string Date format to use
835 * @return string Formatted date string
836 */
837function format_date($date, $format=NULL)
838  {
839  global $CONFIG;
840 
841  $ts = NULL;
842
843  if (is_numeric($date))
844    $ts = $date;
845  else if (!empty($date))
846    {
847    // support non-standard "GMTXXXX" literal
848    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
849    // if date parsing fails, we have a date in non-rfc format.
850    // remove token from the end and try again
851    while ((($ts = @strtotime($date))===false) || ($ts < 0))
852      {
853        $d = explode(' ', $date);
854        array_pop($d);
855        if (!$d) break;
856        $date = implode(' ', $d);
857      }
858    }
859
860  if (empty($ts))
861    return '';
862   
863  // get user's timezone
864  if ($CONFIG['timezone'] === 'auto')
865    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
866  else {
867    $tz = $CONFIG['timezone'];
868    if ($CONFIG['dst_active'])
869      $tz++;
870  }
871
872  // convert time to user's timezone
873  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
874 
875  // get current timestamp in user's timezone
876  $now = time();  // local time
877  $now -= (int)date('Z'); // make GMT time
878  $now += ($tz * 3600); // user's time
879  $now_date = getdate($now);
880
881  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
882  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
883
884  // define date format depending on current time 
885  if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
886    return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
887  else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
888    $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
889  else if (!$format)
890    $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
891
892  // strftime() format
893  if (preg_match('/%[a-z]+/i', $format))
894    return strftime($format, $timestamp);
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, $RCMAIL;
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  $date = date($CONFIG['log_date_format']);
999 
1000  // trigger logging hook
1001  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1002    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1003    $name = $log['name'];
1004    $line = $log['line'];
1005    $date = $log['date'];
1006    if ($log['abort'])
1007      return;
1008  }
1009 
1010  $log_entry = sprintf("[%s]: %s\n", $date, $line);
1011
1012  if ($CONFIG['log_driver'] == 'syslog') {
1013    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1014    syslog($prio, $log_entry);
1015    return true;
1016  }
1017  else {
1018    // log_driver == 'file' is assumed here
1019    if (empty($CONFIG['log_dir']))
1020      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1021
1022    // try to open specific log file for writing
1023    if ($fp = @fopen($CONFIG['log_dir'].'/'.$name, 'a')) {
1024      fwrite($fp, $log_entry);
1025      fflush($fp);
1026      fclose($fp);
1027      return true;
1028    }
1029  }
1030  return false;
1031}
1032
1033
1034/**
1035 * @access private
1036 */
1037function rcube_timer()
1038{
1039  return microtime(true);
1040}
1041 
1042
1043/**
1044 * @access private
1045 */
1046function rcube_print_time($timer, $label='Timer', $dest='console')
1047{
1048  static $print_count = 0;
1049 
1050  $print_count++;
1051  $now = rcube_timer();
1052  $diff = $now-$timer;
1053 
1054  if (empty($label))
1055    $label = 'Timer '.$print_count;
1056 
1057  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1058}
1059
1060
1061/**
1062 * Return the mailboxlist in HTML
1063 *
1064 * @param array Named parameters
1065 * @return string HTML code for the gui object
1066 */
1067function rcmail_mailbox_list($attrib)
1068{
1069  global $RCMAIL;
1070  static $a_mailboxes;
1071 
1072  $attrib += array('maxlength' => 100, 'relanames' => false);
1073
1074  // add some labels to client
1075  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1076 
1077  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1078  unset($attrib['type']);
1079
1080  if ($type=='ul' && !$attrib['id'])
1081    $attrib['id'] = 'rcmboxlist';
1082
1083  // get mailbox list
1084  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1085 
1086  // build the folders tree
1087  if (empty($a_mailboxes)) {
1088    // get mailbox list
1089    $a_folders = $RCMAIL->imap->list_mailboxes();
1090    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1091    $a_mailboxes = array();
1092
1093    foreach ($a_folders as $folder)
1094      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1095  }
1096 
1097  // allow plugins to alter the folder tree or to localize folder names
1098  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1099
1100  if ($type=='select') {
1101    $select = new html_select($attrib);
1102   
1103    // add no-selection option
1104    if ($attrib['noselection'])
1105      $select->add(rcube_label($attrib['noselection']), '0');
1106   
1107    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1108    $out = $select->show();
1109  }
1110  else {
1111    $js_mailboxlist = array();
1112    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1113   
1114    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1115    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1116    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1117  }
1118
1119  return $out;
1120}
1121
1122
1123/**
1124 * Return the mailboxlist as html_select object
1125 *
1126 * @param array Named parameters
1127 * @return object html_select HTML drop-down object
1128 */
1129function rcmail_mailbox_select($p = array())
1130{
1131  global $RCMAIL;
1132 
1133  $p += array('maxlength' => 100, 'relanames' => false);
1134  $a_mailboxes = array();
1135 
1136  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1137    rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1138
1139  $select = new html_select($p);
1140 
1141  if ($p['noselection'])
1142    $select->add($p['noselection'], '');
1143   
1144  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1145 
1146  return $select;
1147}
1148
1149
1150/**
1151 * Create a hierarchical array of the mailbox list
1152 * @access private
1153 */
1154function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1155{
1156  $pos = strpos($folder, $delm);
1157  if ($pos !== false) {
1158    $subFolders = substr($folder, $pos+1);
1159    $currentFolder = substr($folder, 0, $pos);
1160    $virtual = !isset($arrFolders[$currentFolder]);
1161  }
1162  else {
1163    $subFolders = false;
1164    $currentFolder = $folder;
1165    $virtual = false;
1166  }
1167
1168  $path .= $currentFolder;
1169
1170  if (!isset($arrFolders[$currentFolder])) {
1171    $arrFolders[$currentFolder] = array(
1172      'id' => $path,
1173      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1174      'virtual' => $virtual,
1175      'folders' => array());
1176  }
1177  else
1178    $arrFolders[$currentFolder]['virtual'] = $virtual;
1179
1180  if (!empty($subFolders))
1181    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1182}
1183 
1184
1185/**
1186 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1187 * @access private
1188 */
1189function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1190{
1191  global $RCMAIL, $CONFIG;
1192 
1193  $maxlength = intval($attrib['maxlength']);
1194  $realnames = (bool)$attrib['realnames'];
1195  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1196
1197  $idx = 0;
1198  $out = '';
1199  foreach ($arrFolders as $key => $folder) {
1200    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1201    $title = null;
1202
1203    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1204      $foldername = rcube_label($folder_class);
1205    }
1206    else {
1207      $foldername = $folder['name'];
1208
1209      // shorten the folder name to a given length
1210      if ($maxlength && $maxlength > 1) {
1211        $fname = abbreviate_string($foldername, $maxlength);
1212        if ($fname != $foldername)
1213          $title = $foldername;
1214        $foldername = $fname;
1215      }
1216    }
1217
1218    // make folder name safe for ids and class names
1219    $folder_id = asciiwords($folder['id'], true, '_');
1220    $classes = array('mailbox');
1221
1222    // set special class for Sent, Drafts, Trash and Junk
1223    if ($folder['id']==$CONFIG['sent_mbox'])
1224      $classes[] = 'sent';
1225    else if ($folder['id']==$CONFIG['drafts_mbox'])
1226      $classes[] = 'drafts';
1227    else if ($folder['id']==$CONFIG['trash_mbox'])
1228      $classes[] = 'trash';
1229    else if ($folder['id']==$CONFIG['junk_mbox'])
1230      $classes[] = 'junk';
1231    else if ($folder['id']=='INBOX')
1232      $classes[] = 'inbox';
1233    else
1234      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1235     
1236    $classes[] = $zebra_class;
1237   
1238    if ($folder['id'] == $mbox_name)
1239      $classes[] = 'selected';
1240
1241    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1242    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1243   
1244    if ($folder['virtual'])
1245      $classes[] = 'virtual';
1246    else if ($unread)
1247      $classes[] = 'unread';
1248
1249    $js_name = JQ($folder['id']);
1250    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1251    $link_attrib = $folder['virtual'] ? array() : array(
1252      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1253      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1254      'title' => $title,
1255    );
1256
1257    $out .= html::tag('li', array(
1258        'id' => "rcmli".$folder_id,
1259        'class' => join(' ', $classes),
1260        'noclose' => true),
1261      html::a($link_attrib, $html_name) .
1262      (!empty($folder['folders']) ? html::div(array(
1263        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1264        'style' => "position:absolute",
1265        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1266      ), '&nbsp;') : ''));
1267   
1268    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1269   
1270    if (!empty($folder['folders'])) {
1271      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1272        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1273    }
1274
1275    $out .= "</li>\n";
1276    $idx++;
1277  }
1278
1279  return $out;
1280}
1281
1282
1283/**
1284 * Return html for a flat list <select> for the mailbox tree
1285 * @access private
1286 */
1287function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1288  {
1289  $idx = 0;
1290  $out = '';
1291  foreach ($arrFolders as $key=>$folder)
1292    {
1293    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1294      $foldername = rcube_label($folder_class);
1295    else
1296      {
1297      $foldername = $folder['name'];
1298     
1299      // shorten the folder name to a given length
1300      if ($maxlength && $maxlength>1)
1301        $foldername = abbreviate_string($foldername, $maxlength);
1302      }
1303
1304    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1305
1306    if (!empty($folder['folders']))
1307      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1308
1309    $idx++;
1310    }
1311
1312  return $out;
1313  }
1314
1315
1316/**
1317 * Return internal name for the given folder if it matches the configured special folders
1318 * @access private
1319 */
1320function rcmail_folder_classname($folder_id)
1321{
1322  global $CONFIG;
1323
1324  // for these mailboxes we have localized labels and css classes
1325  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1326  {
1327    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1328      return $smbx;
1329  }
1330
1331  if ($folder_id == 'INBOX')
1332    return 'inbox';
1333}
1334
1335
1336/**
1337 * Try to localize the given IMAP folder name.
1338 * UTF-7 decode it in case no localized text was found
1339 *
1340 * @param string Folder name
1341 * @return string Localized folder name in UTF-8 encoding
1342 */
1343function rcmail_localize_foldername($name)
1344{
1345  if ($folder_class = rcmail_folder_classname($name))
1346    return rcube_label($folder_class);
1347  else
1348    return rcube_charset_convert($name, 'UTF7-IMAP');
1349}
1350
1351
1352/**
1353 * Output HTML editor scripts
1354 *
1355 * @param string Editor mode
1356 */
1357function rcube_html_editor($mode='')
1358{
1359  global $OUTPUT, $CONFIG;
1360
1361  $lang = $tinylang = strtolower(substr($_SESSION['language'], 0, 2));
1362  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$tinylang.'.js'))
1363    $tinylang = 'en';
1364
1365  $OUTPUT->include_script('tiny_mce/tiny_mce.js');
1366  $OUTPUT->include_script('editor.js');
1367  $OUTPUT->add_script('rcmail_editor_init("$__skin_path", "'.JQ($tinylang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1368}
1369
1370
1371
1372/**
1373 * Helper class to turn relative urls into absolute ones
1374 * using a predefined base
1375 */
1376class rcube_base_replacer
1377{
1378  private $base_url;
1379 
1380  public function __construct($base)
1381  {
1382    $this->base_url = $base;
1383  }
1384 
1385  public function callback($matches)
1386  {
1387    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1388  }
1389}
1390
1391?>
Note: See TracBrowser for help on using the repository browser.