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

Last change on this file since 2705 was 2705, checked in by thomasb, 4 years ago

Add new hook 'list_mailboxes' to alter the mailboxlist

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