source: github/program/include/main.inc @ 75fd64f

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 75fd64f was 75fd64f, checked in by thomascube <thomas@…>, 4 years ago

Add hook to write_log function

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