source: github/program/include/main.inc @ 79c45f4

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