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

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 9ab7bc6 was 9ab7bc6, checked in by alecpl <alec@…>, 4 years ago
  • Property mode set to 100644
File size: 37.2 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 * Convert array of request parameters (prefixed with _)
589 * to a regular array with non-prefixed keys.
590 *
591 * @param  int   Source to get value from (GPC)
592 * @return array Hash array with all request parameters
593 */
594function request2param($mode = RCUBE_INPUT_GPC)
595{
596  $out = array();
597  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
598  foreach ($src as $key => $value) {
599    $fname = $key[0] == '_' ? substr($key, 1) : $key;
600    $out[$fname] = get_input_value($key, $mode);
601  }
602 
603  return $out;
604}
605
606/**
607 * Remove all non-ascii and non-word chars
608 * except ., -, _
609 */
610function asciiwords($str, $css_id = false, $replace_with = '')
611{
612  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
613  return preg_replace("/[^$allowed]/i", $replace_with, $str);
614}
615
616/**
617 * Remove single and double quotes from given string
618 *
619 * @param string Input value
620 * @return string Dequoted string
621 */
622function strip_quotes($str)
623{
624  return preg_replace('/[\'"]/', '', $str);
625}
626
627
628/**
629 * Remove new lines characters from given string
630 *
631 * @param string Input value
632 * @return string Stripped string
633 */
634function strip_newlines($str)
635{
636  return preg_replace('/[\r\n]/', '', $str);
637}
638
639
640/**
641 * Create a HTML table based on the given data
642 *
643 * @param  array  Named table attributes
644 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
645 * @param  array  List of cols to show
646 * @param  string Name of the identifier col
647 * @return string HTML table code
648 */
649function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
650  {
651  global $RCMAIL;
652 
653  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
654   
655  // add table header
656  foreach ($a_show_cols as $col)
657    $table->add_header($col, Q(rcube_label($col)));
658 
659  $c = 0;
660  if (!is_array($table_data))
661  {
662    $db = $RCMAIL->get_dbh();
663    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
664    {
665      $zebra_class = $c % 2 ? 'even' : 'odd';
666      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
667
668      // format each col
669      foreach ($a_show_cols as $col)
670        $table->add($col, Q($sql_arr[$col]));
671     
672      $c++;
673    }
674  }
675  else
676  {
677    foreach ($table_data as $row_data)
678    {
679      $zebra_class = $c % 2 ? 'even' : 'odd';
680      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
681
682      // format each col
683      foreach ($a_show_cols as $col)
684        $table->add($col, Q($row_data[$col]));
685       
686      $c++;
687    }
688  }
689
690  return $table->show($attrib);
691  }
692
693
694/**
695 * Create an edit field for inclusion on a form
696 *
697 * @param string col field name
698 * @param string value field value
699 * @param array attrib HTML element attributes for field
700 * @param string type HTML element type (default 'text')
701 * @return string HTML field definition
702 */
703function rcmail_get_edit_field($col, $value, $attrib, $type='text')
704  {
705  $fname = '_'.$col;
706  $attrib['name'] = $fname;
707 
708  if ($type=='checkbox')
709    {
710    $attrib['value'] = '1';
711    $input = new html_checkbox($attrib);
712    }
713  else if ($type=='textarea')
714    {
715    $attrib['cols'] = $attrib['size'];
716    $input = new html_textarea($attrib);
717    }
718  else
719    $input = new html_inputfield($attrib);
720
721  // use value from post
722  if (!empty($_POST[$fname]))
723    $value = get_input_value($fname, RCUBE_INPUT_POST,
724            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
725
726  $out = $input->show($value);
727         
728  return $out;
729  }
730
731
732/**
733 * Replace all css definitions with #container [def]
734 * and remove css-inlined scripting
735 *
736 * @param string CSS source code
737 * @param string Container ID to use as prefix
738 * @return string Modified CSS source
739 */
740function rcmail_mod_css_styles($source, $container_id)
741  {
742  $last_pos = 0;
743  $replacements = new rcube_string_replacer;
744 
745  // ignore the whole block if evil styles are detected
746  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
747  if (preg_match('/expression|behavior|url\(|import/', $stripped))
748    return '/* evil! */';
749
750  // cut out all contents between { and }
751  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
752  {
753    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
754    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
755    $last_pos = $pos+2;
756  }
757 
758  // remove html comments and add #container to each tag selector.
759  // also replace body definition because we also stripped off the <body> tag
760  $styles = preg_replace(
761    array(
762      '/(^\s*<!--)|(-->\s*$)/',
763      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
764      "/$container_id\s+body/i",
765    ),
766    array(
767      '',
768      "\\1#$container_id \\2",
769      "$container_id div.rcmBody",
770    ),
771    $source);
772 
773  // put block contents back in
774  $styles = $replacements->resolve($styles);
775
776  return $styles;
777  }
778
779
780/**
781 * Decode escaped entities used by known XSS exploits.
782 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
783 *
784 * @param string CSS content to decode
785 * @return string Decoded string
786 */
787function rcmail_xss_entity_decode($content)
788{
789  $out = html_entity_decode(html_entity_decode($content));
790  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
791  $out = preg_replace('#/\*.*\*/#Um', '', $out);
792  return $out;
793}
794
795
796/**
797 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
798 *
799 * @param array matches result from preg_replace_callback
800 * @return string decoded entity
801 */
802function rcmail_xss_entity_decode_callback($matches)
803{
804  return chr(hexdec($matches[1]));
805}
806
807/**
808 * Compose a valid attribute string for HTML tags
809 *
810 * @param array Named tag attributes
811 * @param array List of allowed attributes
812 * @return string HTML formatted attribute string
813 */
814function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
815  {
816  // allow the following attributes to be added to the <iframe> tag
817  $attrib_str = '';
818  foreach ($allowed_attribs as $a)
819    if (isset($attrib[$a]))
820      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
821
822  return $attrib_str;
823  }
824
825
826/**
827 * Convert a HTML attribute string attributes to an associative array (name => value)
828 *
829 * @param string Input string
830 * @return array Key-value pairs of parsed attributes
831 */
832function parse_attrib_string($str)
833  {
834  $attrib = array();
835  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
836
837  // convert attributes to an associative array (name => value)
838  if ($regs) {
839    foreach ($regs as $attr) {
840      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
841    }
842  }
843
844  return $attrib;
845  }
846
847
848/**
849 * Convert the given date to a human readable form
850 * This uses the date formatting properties from config
851 *
852 * @param mixed Date representation (string or timestamp)
853 * @param string Date format to use
854 * @return string Formatted date string
855 */
856function format_date($date, $format=NULL)
857  {
858  global $CONFIG;
859 
860  $ts = NULL;
861
862  if (is_numeric($date))
863    $ts = $date;
864  else if (!empty($date))
865    {
866    // support non-standard "GMTXXXX" literal
867    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
868    // if date parsing fails, we have a date in non-rfc format.
869    // remove token from the end and try again
870    while ((($ts = @strtotime($date))===false) || ($ts < 0))
871      {
872        $d = explode(' ', $date);
873        array_pop($d);
874        if (!$d) break;
875        $date = implode(' ', $d);
876      }
877    }
878
879  if (empty($ts))
880    return '';
881   
882  // get user's timezone
883  if ($CONFIG['timezone'] === 'auto')
884    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
885  else {
886    $tz = $CONFIG['timezone'];
887    if ($CONFIG['dst_active'])
888      $tz++;
889  }
890
891  // convert time to user's timezone
892  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
893 
894  // get current timestamp in user's timezone
895  $now = time();  // local time
896  $now -= (int)date('Z'); // make GMT time
897  $now += ($tz * 3600); // user's time
898  $now_date = getdate($now);
899
900  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
901  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
902
903  // define date format depending on current time 
904  if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
905    return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
906  else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
907    $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
908  else if (!$format)
909    $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
910
911  // strftime() format
912  if (preg_match('/%[a-z]+/i', $format))
913    return strftime($format, $timestamp);
914
915  // parse format string manually in order to provide localized weekday and month names
916  // an alternative would be to convert the date() format string to fit with strftime()
917  $out = '';
918  for($i=0; $i<strlen($format); $i++)
919    {
920    if ($format{$i}=='\\')  // skip escape chars
921      continue;
922   
923    // write char "as-is"
924    if ($format{$i}==' ' || $format{$i-1}=='\\')
925      $out .= $format{$i};
926    // weekday (short)
927    else if ($format{$i}=='D')
928      $out .= rcube_label(strtolower(date('D', $timestamp)));
929    // weekday long
930    else if ($format{$i}=='l')
931      $out .= rcube_label(strtolower(date('l', $timestamp)));
932    // month name (short)
933    else if ($format{$i}=='M')
934      $out .= rcube_label(strtolower(date('M', $timestamp)));
935    // month name (long)
936    else if ($format{$i}=='F')
937      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
938    else if ($format{$i}=='x')
939      $out .= strftime('%x %X', $timestamp);
940    else
941      $out .= date($format{$i}, $timestamp);
942    }
943 
944  return $out;
945  }
946
947
948/**
949 * Compose a valid representaion of name and e-mail address
950 *
951 * @param string E-mail address
952 * @param string Person name
953 * @return string Formatted string
954 */
955function format_email_recipient($email, $name='')
956  {
957  if ($name && $name != $email)
958    {
959    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
960    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, $email);
961    }
962  else
963    return $email;
964  }
965
966
967
968/****** debugging functions ********/
969
970
971/**
972 * Print or write debug messages
973 *
974 * @param mixed Debug message or data
975 */
976function console()
977  {
978  $args = func_get_args();
979
980  if (class_exists('rcmail', false)) {
981    $rcmail = rcmail::get_instance();
982    if (is_object($rcmail->plugins))
983      $rcmail->plugins->exec_hook('console', $args);
984  }
985
986  $msg = array();
987  foreach ($args as $arg)
988    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
989
990  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
991    write_log('console', join(";\n", $msg));
992  else if ($GLOBALS['OUTPUT']->ajax_call)
993    print "/*\n " . join(";\n", $msg) . " \n*/\n";
994  else
995    {
996    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
997    print join(";<br/>\n", $msg);
998    print "</pre></div>\n";
999    }
1000  }
1001
1002
1003/**
1004 * Append a line to a logfile in the logs directory.
1005 * Date will be added automatically to the line.
1006 *
1007 * @param $name name of log file
1008 * @param line Line to append
1009 */
1010function write_log($name, $line)
1011  {
1012  global $CONFIG, $RCMAIL;
1013
1014  if (!is_string($line))
1015    $line = var_export($line, true);
1016 
1017  if (empty($CONFIG['log_date_format']))
1018    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1019 
1020  $date = date($CONFIG['log_date_format']);
1021 
1022  // trigger logging hook
1023  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1024    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1025    $name = $log['name'];
1026    $line = $log['line'];
1027    $date = $log['date'];
1028    if ($log['abort'])
1029      return;
1030  }
1031 
1032  $log_entry = sprintf("[%s]: %s\n", $date, $line);
1033
1034  if ($CONFIG['log_driver'] == 'syslog') {
1035    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1036    syslog($prio, $log_entry);
1037    return true;
1038  }
1039  else {
1040    // log_driver == 'file' is assumed here
1041    if (empty($CONFIG['log_dir']))
1042      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1043
1044    // try to open specific log file for writing
1045    if ($fp = @fopen($CONFIG['log_dir'].'/'.$name, 'a')) {
1046      fwrite($fp, $log_entry);
1047      fflush($fp);
1048      fclose($fp);
1049      return true;
1050    }
1051  }
1052  return false;
1053}
1054
1055
1056/**
1057 * @access private
1058 */
1059function rcube_timer()
1060{
1061  return microtime(true);
1062}
1063 
1064
1065/**
1066 * @access private
1067 */
1068function rcube_print_time($timer, $label='Timer', $dest='console')
1069{
1070  static $print_count = 0;
1071 
1072  $print_count++;
1073  $now = rcube_timer();
1074  $diff = $now-$timer;
1075 
1076  if (empty($label))
1077    $label = 'Timer '.$print_count;
1078 
1079  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1080}
1081
1082
1083/**
1084 * Return the mailboxlist in HTML
1085 *
1086 * @param array Named parameters
1087 * @return string HTML code for the gui object
1088 */
1089function rcmail_mailbox_list($attrib)
1090{
1091  global $RCMAIL;
1092  static $a_mailboxes;
1093 
1094  $attrib += array('maxlength' => 100, 'relanames' => false);
1095
1096  // add some labels to client
1097  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1098 
1099  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1100  unset($attrib['type']);
1101
1102  if ($type=='ul' && !$attrib['id'])
1103    $attrib['id'] = 'rcmboxlist';
1104
1105  // get mailbox list
1106  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1107 
1108  // build the folders tree
1109  if (empty($a_mailboxes)) {
1110    // get mailbox list
1111    $a_folders = $RCMAIL->imap->list_mailboxes();
1112    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1113    $a_mailboxes = array();
1114
1115    foreach ($a_folders as $folder)
1116      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1117  }
1118 
1119  // allow plugins to alter the folder tree or to localize folder names
1120  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1121
1122  if ($type=='select') {
1123    $select = new html_select($attrib);
1124   
1125    // add no-selection option
1126    if ($attrib['noselection'])
1127      $select->add(rcube_label($attrib['noselection']), '0');
1128   
1129    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1130    $out = $select->show();
1131  }
1132  else {
1133    $js_mailboxlist = array();
1134    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1135   
1136    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1137    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1138    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1139  }
1140
1141  return $out;
1142}
1143
1144
1145/**
1146 * Return the mailboxlist as html_select object
1147 *
1148 * @param array Named parameters
1149 * @return object html_select HTML drop-down object
1150 */
1151function rcmail_mailbox_select($p = array())
1152{
1153  global $RCMAIL;
1154 
1155  $p += array('maxlength' => 100, 'relanames' => false);
1156  $a_mailboxes = array();
1157 
1158  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1159    rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1160
1161  $select = new html_select($p);
1162 
1163  if ($p['noselection'])
1164    $select->add($p['noselection'], '');
1165   
1166  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1167 
1168  return $select;
1169}
1170
1171
1172/**
1173 * Create a hierarchical array of the mailbox list
1174 * @access private
1175 */
1176function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1177{
1178  $pos = strpos($folder, $delm);
1179  if ($pos !== false) {
1180    $subFolders = substr($folder, $pos+1);
1181    $currentFolder = substr($folder, 0, $pos);
1182    $virtual = !isset($arrFolders[$currentFolder]);
1183  }
1184  else {
1185    $subFolders = false;
1186    $currentFolder = $folder;
1187    $virtual = false;
1188  }
1189
1190  $path .= $currentFolder;
1191
1192  if (!isset($arrFolders[$currentFolder])) {
1193    $arrFolders[$currentFolder] = array(
1194      'id' => $path,
1195      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1196      'virtual' => $virtual,
1197      'folders' => array());
1198  }
1199  else
1200    $arrFolders[$currentFolder]['virtual'] = $virtual;
1201
1202  if (!empty($subFolders))
1203    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1204}
1205 
1206
1207/**
1208 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1209 * @access private
1210 */
1211function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1212{
1213  global $RCMAIL, $CONFIG;
1214 
1215  $maxlength = intval($attrib['maxlength']);
1216  $realnames = (bool)$attrib['realnames'];
1217  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1218
1219  $idx = 0;
1220  $out = '';
1221  foreach ($arrFolders as $key => $folder) {
1222    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1223    $title = null;
1224
1225    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1226      $foldername = rcube_label($folder_class);
1227    }
1228    else {
1229      $foldername = $folder['name'];
1230
1231      // shorten the folder name to a given length
1232      if ($maxlength && $maxlength > 1) {
1233        $fname = abbreviate_string($foldername, $maxlength);
1234        if ($fname != $foldername)
1235          $title = $foldername;
1236        $foldername = $fname;
1237      }
1238    }
1239
1240    // make folder name safe for ids and class names
1241    $folder_id = asciiwords($folder['id'], true, '_');
1242    $classes = array('mailbox');
1243
1244    // set special class for Sent, Drafts, Trash and Junk
1245    if ($folder['id']==$CONFIG['sent_mbox'])
1246      $classes[] = 'sent';
1247    else if ($folder['id']==$CONFIG['drafts_mbox'])
1248      $classes[] = 'drafts';
1249    else if ($folder['id']==$CONFIG['trash_mbox'])
1250      $classes[] = 'trash';
1251    else if ($folder['id']==$CONFIG['junk_mbox'])
1252      $classes[] = 'junk';
1253    else if ($folder['id']=='INBOX')
1254      $classes[] = 'inbox';
1255    else
1256      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1257     
1258    $classes[] = $zebra_class;
1259   
1260    if ($folder['id'] == $mbox_name)
1261      $classes[] = 'selected';
1262
1263    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1264    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1265   
1266    if ($folder['virtual'])
1267      $classes[] = 'virtual';
1268    else if ($unread)
1269      $classes[] = 'unread';
1270
1271    $js_name = JQ($folder['id']);
1272    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1273    $link_attrib = $folder['virtual'] ? array() : array(
1274      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1275      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1276      'title' => $title,
1277    );
1278
1279    $out .= html::tag('li', array(
1280        'id' => "rcmli".$folder_id,
1281        'class' => join(' ', $classes),
1282        'noclose' => true),
1283      html::a($link_attrib, $html_name) .
1284      (!empty($folder['folders']) ? html::div(array(
1285        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1286        'style' => "position:absolute",
1287        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1288      ), '&nbsp;') : ''));
1289   
1290    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1291   
1292    if (!empty($folder['folders'])) {
1293      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1294        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1295    }
1296
1297    $out .= "</li>\n";
1298    $idx++;
1299  }
1300
1301  return $out;
1302}
1303
1304
1305/**
1306 * Return html for a flat list <select> for the mailbox tree
1307 * @access private
1308 */
1309function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1310  {
1311  $idx = 0;
1312  $out = '';
1313  foreach ($arrFolders as $key=>$folder)
1314    {
1315    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1316      $foldername = rcube_label($folder_class);
1317    else
1318      {
1319      $foldername = $folder['name'];
1320     
1321      // shorten the folder name to a given length
1322      if ($maxlength && $maxlength>1)
1323        $foldername = abbreviate_string($foldername, $maxlength);
1324      }
1325
1326    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1327
1328    if (!empty($folder['folders']))
1329      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1330
1331    $idx++;
1332    }
1333
1334  return $out;
1335  }
1336
1337
1338/**
1339 * Return internal name for the given folder if it matches the configured special folders
1340 * @access private
1341 */
1342function rcmail_folder_classname($folder_id)
1343{
1344  global $CONFIG;
1345
1346  // for these mailboxes we have localized labels and css classes
1347  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1348  {
1349    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1350      return $smbx;
1351  }
1352
1353  if ($folder_id == 'INBOX')
1354    return 'inbox';
1355}
1356
1357
1358/**
1359 * Try to localize the given IMAP folder name.
1360 * UTF-7 decode it in case no localized text was found
1361 *
1362 * @param string Folder name
1363 * @return string Localized folder name in UTF-8 encoding
1364 */
1365function rcmail_localize_foldername($name)
1366{
1367  if ($folder_class = rcmail_folder_classname($name))
1368    return rcube_label($folder_class);
1369  else
1370    return rcube_charset_convert($name, 'UTF7-IMAP');
1371}
1372
1373
1374/**
1375 * Output HTML editor scripts
1376 *
1377 * @param string Editor mode
1378 */
1379function rcube_html_editor($mode='')
1380{
1381  global $RCMAIL, $CONFIG;
1382
1383  $lang = strtolower(substr($_SESSION['language'], 0, 2));
1384  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1385    $lang = 'en';
1386
1387  $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('abort' => false,
1388    'mode' => $mode, 'lang' => $lang));
1389
1390  if ($hook['abort'])
1391    return; 
1392
1393  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1394  $RCMAIL->output->include_script('editor.js');
1395  $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1396    "'.JQ($hook['lang']).'", '.intval($CONFIG['enable_spellcheck']).', "'.$hook['mode'].'");');
1397}
1398
1399
1400/**
1401 * Helper class to turn relative urls into absolute ones
1402 * using a predefined base
1403 */
1404class rcube_base_replacer
1405{
1406  private $base_url;
1407 
1408  public function __construct($base)
1409  {
1410    $this->base_url = $base;
1411  }
1412 
1413  public function callback($matches)
1414  {
1415    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1416  }
1417}
1418
1419?>
Note: See TracBrowser for help on using the repository browser.