alecpl
2012-04-16 1aceb9cec82639250a603aa0806f0399f7bae68d
commit | author | age
47124c 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
0c2596 5  | program/include/rcubeoutput_html.php                                  |
47124c 6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
0c2596 8  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
7fe381 9  |                                                                       |
T 10  | Licensed under the GNU General Public License version 3 or            |
11  | any later version with exceptions for skins & plugins.                |
12  | See the README file for a full license statement.                     |
47124c 13  |                                                                       |
T 14  | PURPOSE:                                                              |
15  |   Class to handle HTML page output using a skin template.             |
16  |                                                                       |
17  +-----------------------------------------------------------------------+
18  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
19  +-----------------------------------------------------------------------+
20
21  $Id$
22
23  */
24
25
26 /**
27  * Class to create HTML page output using a skin template
28  *
29  * @package View
30  */
0c2596 31 class rcube_output_html extends rcube_output
47124c 32 {
c8a21d 33     public $type = 'html';
0c2596 34
A 35     protected $message = null;
36     protected $js_env = array();
37     protected $js_labels = array();
38     protected $js_commands = array();
39     protected $plugin_skin_path;
40     protected $template_name;
41     protected $scripts_path = '';
42     protected $script_files = array();
43     protected $css_files = array();
44     protected $scripts = array();
45     protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
46     protected $header = '';
47     protected $footer = '';
48     protected $body = '';
49     protected $base_path = '';
47124c 50
7fcb56 51     // deprecated names of templates used before 0.5
0c2596 52     protected $deprecated_templates = array(
A 53         'contact'      => 'showcontact',
54         'contactadd'   => 'addcontact',
55         'contactedit'  => 'editcontact',
7fcb56 56         'identityedit' => 'editidentity',
T 57         'messageprint' => 'printmessage',
58     );
59
47124c 60     /**
T 61      * Constructor
62      *
197601 63      * @todo   Replace $this->config with the real rcube_config object
47124c 64      */
0c2596 65     public function __construct($task = null, $framed = false)
47124c 66     {
T 67         parent::__construct();
68
197601 69         //$this->framed = $framed;
83a763 70         $this->set_env('task', $task);
0c2596 71         $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
47124c 72
423065 73         // load the correct skin (in case user-defined)
0c2596 74         $this->set_skin($this->config->get('skin'));
e58df3 75
47124c 76         // add common javascripts
1aceb9 77         $this->add_script('var '.rcmail::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
47124c 78
T 79         // don't wait for page onload. Call init at the bottom of the page (delayed)
1aceb9 80         $this->add_script(rcmail::JS_OBJECT_NAME.'.init();', 'docready');
47124c 81
T 82         $this->scripts_path = 'program/js/';
79275b 83         $this->include_script('jquery.min.js');
47124c 84         $this->include_script('common.js');
T 85         $this->include_script('app.js');
86
87         // register common UI objects
88         $this->add_handlers(array(
89             'loginform'       => array($this, 'login_form'),
8e211a 90             'preloader'       => array($this, 'preloader'),
47124c 91             'username'        => array($this, 'current_username'),
T 92             'message'         => array($this, 'message_container'),
93             'charsetselector' => array($this, 'charset_selector'),
1a0f60 94             'aboutcontent'    => array($this, 'about_content'),
47124c 95         ));
T 96     }
97
0c2596 98
47124c 99     /**
T 100      * Set environment variable
101      *
102      * @param string Property name
103      * @param mixed Property value
104      * @param boolean True if this property should be added to client environment
105      */
106     public function set_env($name, $value, $addtojs = true)
107     {
108         $this->env[$name] = $value;
109         if ($addtojs || isset($this->js_env[$name])) {
110             $this->js_env[$name] = $value;
111         }
112     }
113
f645ce 114
T 115     /**
116      * Getter for the current page title
117      *
118      * @return string The page title
119      */
0c2596 120     protected function get_pagetitle()
f645ce 121     {
T 122         if (!empty($this->pagetitle)) {
123             $title = $this->pagetitle;
124         }
125         else if ($this->env['task'] == 'login') {
0c2596 126             $title = $this->app->gettext(array(
A 127                 'name' => 'welcome',
128                 'vars' => array('product' => $this->config->get('product_name')
129             )));
f645ce 130         }
T 131         else {
132             $title = ucfirst($this->env['task']);
133         }
ad334a 134
f645ce 135         return $title;
T 136     }
0c2596 137
f645ce 138
e58df3 139     /**
A 140      * Set skin
141      */
142     public function set_skin($skin)
143     {
62c791 144         $valid = false;
ad334a 145
62c791 146         if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin)) {
423065 147             $skin_path = 'skins/'.$skin;
62c791 148             $valid = true;
T 149         }
150         else {
0c2596 151             $skin_path = $this->config->get('skin_path');
A 152             if (!$skin_path) {
153                 $skin_path = 'skins/default';
154             }
62c791 155             $valid = !$skin;
T 156         }
423065 157
0c2596 158         $this->config->set('skin_path', $skin_path);
ad334a 159
62c791 160         return $valid;
e58df3 161     }
A 162
fc7b5b 163
T 164     /**
e58df3 165      * Check if a specific template exists
A 166      *
167      * @param string Template name
168      * @return boolean True if template exists
169      */
170     public function template_exists($name)
171     {
0c2596 172         $filename = $this->config->get('skin_path') . '/templates/' . $name . '.html';
7fcb56 173         return (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
e58df3 174     }
47124c 175
T 176
177     /**
178      * Register a GUI object to the client script
179      *
180      * @param  string Object name
181      * @param  string Object ID
182      * @return void
183      */
184     public function add_gui_object($obj, $id)
185     {
1aceb9 186         $this->add_script(rcmail::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
47124c 187     }
0c2596 188
47124c 189
T 190     /**
191      * Call a client method
192      *
193      * @param string Method to call
194      * @param ... Additional arguments
195      */
196     public function command()
197     {
0e99d3 198         $cmd = func_get_args();
f66383 199         if (strpos($cmd[0], 'plugin.') !== false)
T 200           $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
201         else
0e99d3 202           $this->js_commands[] = $cmd;
47124c 203     }
T 204
0c2596 205
47124c 206     /**
T 207      * Add a localized label to the client environment
208      */
209     public function add_label()
210     {
cc97ea 211         $args = func_get_args();
T 212         if (count($args) == 1 && is_array($args[0]))
213           $args = $args[0];
ad334a 214
cc97ea 215         foreach ($args as $name) {
0c2596 216             $this->js_labels[$name] = $this->app->gettext($name);
47124c 217         }
T 218     }
0c2596 219
47124c 220
T 221     /**
222      * Invoke display_message command
223      *
7f5a84 224      * @param string  $message  Message to display
A 225      * @param string  $type     Message type [notice|confirm|error]
226      * @param array   $vars     Key-value pairs to be replaced in localized text
227      * @param boolean $override Override last set message
228      * @param int     $timeout  Message display time in seconds
47124c 229      * @uses self::command()
T 230      */
7f5a84 231     public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
47124c 232     {
69f18a 233         if ($override || !$this->message) {
0c2596 234             if ($this->app->text_exists($message)) {
8dd172 235                 if (!empty($vars))
A 236                     $vars = array_map('Q', $vars);
0c2596 237                 $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
8dd172 238             }
A 239             else
240                 $msgtext = $message;
241
69f18a 242             $this->message = $message;
7f5a84 243             $this->command('display_message', $msgtext, $type, $timeout * 1000);
69f18a 244         }
47124c 245     }
T 246
0c2596 247
47124c 248     /**
T 249      * Delete all stored env variables and commands
250      */
c719f3 251     public function reset()
47124c 252     {
0c2596 253         parent::reset();
47124c 254         $this->js_env = array();
4dcd43 255         $this->js_labels = array();
47124c 256         $this->js_commands = array();
0c2596 257         $this->script_files = array();
A 258         $this->scripts      = array();
259         $this->header       = '';
260         $this->footer       = '';
261         $this->body         = '';
47124c 262     }
0c2596 263
47124c 264
T 265     /**
c719f3 266      * Redirect to a certain url
T 267      *
0c2596 268      * @param mixed $p     Either a string with the action or url parameters as key-value pairs
A 269      * @param int   $delay Delay in seconds
c719f3 270      */
0c2596 271     public function redirect($p = array(), $delay = 1)
c719f3 272     {
T 273         $location = $this->app->url($p);
274         header('Location: ' . $location);
275         exit;
276     }
0c2596 277
c719f3 278
T 279     /**
47124c 280      * Send the request output to the client.
T 281      * This will either parse a skin tempalte or send an AJAX response
282      *
283      * @param string  Template name
284      * @param boolean True if script should terminate (default)
285      */
286     public function send($templ = null, $exit = true)
287     {
288         if ($templ != 'iframe') {
a366a3 289             // prevent from endless loops
f78dab 290             if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
0c2596 291                 rcube::raise_error(array('code' => 505, 'type' => 'php',
030db5 292                   'file' => __FILE__, 'line' => __LINE__,
T 293                   'message' => 'Recursion alert: ignoring output->send()'), true, false);
a366a3 294                 return;
T 295             }
47124c 296             $this->parse($templ, false);
T 297         }
298         else {
299             $this->framed = $templ == 'iframe' ? true : $this->framed;
300             $this->write();
301         }
302
c6514e 303         // set output asap
T 304         ob_flush();
305         flush();
ad334a 306
c6514e 307         if ($exit) {
47124c 308             exit;
T 309         }
310     }
311
0c2596 312
47124c 313     /**
T 314      * Process template and write to stdOut
315      *
0c2596 316      * @param string $template HTML template content
47124c 317      */
T 318     public function write($template = '')
319     {
320         // unlock interface after iframe load
5bfa44 321         $unlock = preg_replace('/[^a-z0-9]/i', '', $_REQUEST['_unlock']);
47124c 322         if ($this->framed) {
ad334a 323             array_unshift($this->js_commands, array('set_busy', false, null, $unlock));
A 324         }
325         else if ($unlock) {
326             array_unshift($this->js_commands, array('hide_message', $unlock));
47124c 327         }
ef27a6 328
49dac9 329         if (!empty($this->script_files))
T 330           $this->set_env('request_token', $this->app->get_request_token());
ef27a6 331
47124c 332         // write all env variables to client
T 333         $js = $this->framed ? "if(window.parent) {\n" : '';
334         $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
335         $this->add_script($js, 'head_top');
ad334a 336
c170bf 337         // send clickjacking protection headers
T 338         $iframe = $this->framed || !empty($_REQUEST['_framed']);
339         if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')))
340             header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
47124c 341
T 342         // call super method
0c2596 343         $this->_write($template, $this->config->get('skin_path'));
47124c 344     }
0c2596 345
47124c 346
T 347     /**
c739c7 348      * Parse a specific skin template and deliver to stdout (or return)
47124c 349      *
T 350      * @param  string  Template name
351      * @param  boolean Exit script
c739c7 352      * @param  boolean Don't write to stdout, return parsed content instead
A 353      *
47124c 354      * @link   http://php.net/manual/en/function.exit.php
T 355      */
c739c7 356     function parse($name = 'main', $exit = true, $write = true)
47124c 357     {
0c2596 358         $skin_path = $this->config->get('skin_path');
ca18a9 359         $plugin    = false;
A 360         $realname  = $name;
361         $temp      = explode('.', $name, 2);
8e99ff 362
fbe24e 363         $this->plugin_skin_path = null;
8e99ff 364         $this->template_name    = $realname;
ad334a 365
e7008c 366         if (count($temp) > 1) {
ca18a9 367             $plugin    = $temp[0];
A 368             $name      = $temp[1];
0c2596 369             $skin_dir  = $plugin . '/skins/' . $this->config->get('skin');
fbe24e 370             $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
ca18a9 371
A 372             // fallback to default skin
373             if (!is_dir($skin_path)) {
20d50d 374                 $skin_dir = $plugin . '/skins/default';
fbe24e 375                 $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
20d50d 376             }
e7008c 377         }
ad334a 378
e7008c 379         $path = "$skin_path/templates/$name.html";
47124c 380
ca18a9 381         if (!is_readable($path) && $this->deprecated_templates[$realname]) {
A 382             $path = "$skin_path/templates/".$this->deprecated_templates[$realname].".html";
7fcb56 383             if (is_readable($path))
0c2596 384                 rcube::raise_error(array('code' => 502, 'type' => 'php',
7fcb56 385                     'file' => __FILE__, 'line' => __LINE__,
ca18a9 386                     'message' => "Using deprecated template '".$this->deprecated_templates[$realname]
0c2596 387                         ."' in $skin_path/templates. Please rename to '".$realname."'"),
7fcb56 388                 true, false);
T 389         }
390
ff73e0 391         // read template file
7f22f2 392         if (($templ = @file_get_contents($path)) === false) {
0c2596 393             rcube::raise_error(array(
47124c 394                 'code' => 501,
T 395                 'type' => 'php',
396                 'line' => __LINE__,
397                 'file' => __FILE__,
ca18a9 398                 'message' => 'Error loading template for '.$realname
47124c 399                 ), true, true);
T 400             return false;
401         }
ad334a 402
66f68e 403         // replace all path references to plugins/... with the configured plugins dir
T 404         // and /this/ to the current plugin skin directory
e7008c 405         if ($plugin) {
20d50d 406             $templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
e7008c 407         }
47124c 408
T 409         // parse for specialtags
410         $output = $this->parse_conditions($templ);
411         $output = $this->parse_xml($output);
ad334a 412
742d61 413         // trigger generic hook where plugins can put additional content to the page
ca18a9 414         $hook = $this->app->plugins->exec_hook("render_page", array('template' => $realname, 'content' => $output));
47124c 415
e4a4ca 416         // save some memory
A 417         $output = $hook['content'];
418         unset($hook['content']);
419
420         $output = $this->parse_with_globals($output);
c739c7 421
5172ac 422         // make sure all <form> tags have a valid request token
T 423         $output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
424         $this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
425
c739c7 426         if ($write) {
A 427             // add debug console
0c2596 428             if ($realname != 'error' && ($this->config->get('debug_level') & 8)) {
909a3a 429                 $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;display:none">
c739c7 430                     <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
f23073 431                     <textarea name="console" id="dbgconsole" rows="20" cols="40" style="display:none;width:400px;border:none;font-size:10px" spellcheck="false"></textarea></div>'
c739c7 432                 );
909a3a 433                 $this->add_script(
A 434                     "if (!window.console || !window.console.log) {\n".
435                     "  window.console = new rcube_console();\n".
436                     "  $('#console').show();\n".
437                     "}", 'foot');
c739c7 438             }
A 439             $this->write(trim($output));
440         }
441         else {
442             return $output;
47124c 443         }
ad334a 444
47124c 445         if ($exit) {
T 446             exit;
447         }
448     }
449
0c2596 450
47124c 451     /**
T 452      * Return executable javascript code for all registered commands
453      *
454      * @return string $out
455      */
0c2596 456     protected function get_js_commands()
47124c 457     {
T 458         $out = '';
459         if (!$this->framed && !empty($this->js_env)) {
1aceb9 460             $out .= rcmail::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
47124c 461         }
4dcd43 462         if (!empty($this->js_labels)) {
T 463             $this->command('add_label', $this->js_labels);
464         }
47124c 465         foreach ($this->js_commands as $i => $args) {
T 466             $method = array_shift($args);
467             foreach ($args as $i => $arg) {
0c2596 468                 $args[$i] = self::json_serialize($arg);
47124c 469             }
T 470             $parent = $this->framed || preg_match('/^parent\./', $method);
471             $out .= sprintf(
472                 "%s.%s(%s);\n",
1aceb9 473                 ($parent ? 'if(window.parent && parent.'.rcmail::JS_OBJECT_NAME.') parent.' : '') . rcmail::JS_OBJECT_NAME,
cc97ea 474                 preg_replace('/^parent\./', '', $method),
T 475                 implode(',', $args)
47124c 476             );
T 477         }
ad334a 478
47124c 479         return $out;
T 480     }
481
0c2596 482
47124c 483     /**
T 484      * Make URLs starting with a slash point to skin directory
485      *
486      * @param  string Input string
487      * @return string
488      */
489     public function abs_url($str)
490     {
2aa2b3 491         if ($str[0] == '/')
0c2596 492             return $this->config->get('skin_path') . $str;
2aa2b3 493         else
A 494             return $str;
0c2596 495     }
A 496
497
498     /**
499      * Show error page and terminate script execution
500      *
501      * @param int    $code     Error code
502      * @param string $message  Error message
503      */
504     public function raise_error($code, $message)
505     {
506         global $__page_content, $ERROR_CODE, $ERROR_MESSAGE;
507
508         $ERROR_CODE    = $code;
509         $ERROR_MESSAGE = $message;
510
511         include INSTALL_PATH . 'program/steps/utils/error.inc';
512         exit;
47124c 513     }
T 514
515
516     /*****  Template parsing methods  *****/
517
518     /**
519      * Replace all strings ($varname)
520      * with the content of the according global variable.
521      */
0c2596 522     protected function parse_with_globals($input)
47124c 523     {
0c2596 524         $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
A 525         $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
526         $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
527
5740c0 528         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
0c2596 529             array($this, 'globals_callback'), $input);
5740c0 530     }
0c2596 531
5740c0 532
A 533     /**
534      * Callback funtion for preg_replace_callback() in parse_with_globals()
535      */
0c2596 536     protected function globals_callback($matches)
5740c0 537     {
A 538         return $GLOBALS[$matches[1]];
47124c 539     }
0c2596 540
47124c 541
T 542     /**
543      * Public wrapper to dipp into template parsing.
544      *
545      * @param  string $input
546      * @return string
0c2596 547      * @uses   rcube_output_html::parse_xml()
47124c 548      * @since  0.1-rc1
T 549      */
550     public function just_parse($input)
551     {
552         return $this->parse_xml($input);
553     }
554
0c2596 555
47124c 556     /**
T 557      * Parse for conditional tags
558      *
559      * @param  string $input
560      * @return string
561      */
0c2596 562     protected function parse_conditions($input)
47124c 563     {
84581e 564         $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>\n?/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
47124c 565         if ($matches && count($matches) == 4) {
T 566             if (preg_match('/^(else|endif)$/i', $matches[1])) {
567                 return $matches[0] . $this->parse_conditions($matches[3]);
568             }
0c2596 569             $attrib = html::parse_attrib_string($matches[2]);
47124c 570             if (isset($attrib['condition'])) {
T 571                 $condmet = $this->check_condition($attrib['condition']);
84581e 572                 $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
47124c 573                 if ($condmet) {
T 574                     $result = $submatches[0];
84581e 575                     $result.= ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>\n?/Uis', '', $submatches[3], 1) : $submatches[3]);
47124c 576                 }
T 577                 else {
578                     $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
579                 }
580                 return $matches[0] . $this->parse_conditions($result);
581             }
0c2596 582             rcube::raise_error(array(
47124c 583                 'code' => 500,
T 584                 'type' => 'php',
585                 'line' => __LINE__,
586                 'file' => __FILE__,
587                 'message' => "Unable to parse conditional tag " . $matches[2]
588             ), true, false);
589         }
590         return $input;
591     }
592
593
594     /**
595      * Determines if a given condition is met
596      *
597      * @todo   Get rid off eval() once I understand what this does.
598      * @todo   Extend this to allow real conditions, not just "set"
599      * @param  string Condition statement
8e1d4a 600      * @return boolean True if condition is met, False if not
47124c 601      */
0c2596 602     protected function check_condition($condition)
47124c 603     {
549933 604         return eval("return (".$this->parse_expression($condition).");");
T 605     }
ad334a 606
A 607
549933 608     /**
2eb794 609      * Inserts hidden field with CSRF-prevention-token into POST forms
549933 610      */
0c2596 611     protected function alter_form_tag($matches)
549933 612     {
0c2596 613         $out    = $matches[0];
A 614         $attrib = html::parse_attrib_string($matches[1]);
ad334a 615
549933 616         if (strtolower($attrib['method']) == 'post') {
T 617             $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
618             $out .= "\n" . $hidden->show();
619         }
ad334a 620
549933 621         return $out;
8e1d4a 622     }
A 623
624
625     /**
626      * Parses expression and replaces variables
627      *
628      * @param  string Expression statement
030db5 629      * @return string Expression value
8e1d4a 630      */
0c2596 631     protected function parse_expression($expression)
8e1d4a 632     {
A 633         return preg_replace(
47124c 634             array(
T 635                 '/session:([a-z0-9_]+)/i',
f645ce 636                 '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
40353f 637                 '/env:([a-z0-9_]+)/i',
A 638                 '/request:([a-z0-9_]+)/i',
639                 '/cookie:([a-z0-9_]+)/i',
8e99ff 640                 '/browser:([a-z0-9_]+)/i',
A 641                 '/template:name/i',
47124c 642             ),
T 643             array(
644                 "\$_SESSION['\\1']",
f645ce 645                 "\$this->app->config->get('\\1',get_boolean('\\3'))",
47124c 646                 "\$this->env['\\1']",
1aceb9 647                 "rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
a17fe6 648                 "\$_COOKIE['\\1']",
8e99ff 649                 "\$this->browser->{'\\1'}",
A 650                 $this->template_name,
47124c 651             ),
8e1d4a 652             $expression);
47124c 653     }
T 654
655
656     /**
657      * Search for special tags in input and replace them
658      * with the appropriate content
659      *
660      * @param  string Input string to parse
661      * @return string Altered input string
06655a 662      * @todo   Use DOM-parser to traverse template HTML
47124c 663      * @todo   Maybe a cache.
T 664      */
0c2596 665     protected function parse_xml($input)
47124c 666     {
6af593 667         return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input);
6f488b 668     }
A 669
670
671     /**
cc97ea 672      * Callback function for parsing an xml command tag
T 673      * and turn it into real html content
47124c 674      *
cc97ea 675      * @param  array Matches array of preg_replace_callback
47124c 676      * @return string Tag/Object content
T 677      */
0c2596 678     protected function xml_command($matches)
47124c 679     {
cc97ea 680         $command = strtolower($matches[1]);
0c2596 681         $attrib  = html::parse_attrib_string($matches[2]);
47124c 682
T 683         // empty output if required condition is not met
684         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
685             return '';
686         }
687
688         // execute command
689         switch ($command) {
690             // return a button
691             case 'button':
875a48 692                 if ($attrib['name'] || $attrib['command']) {
47124c 693                     return $this->button($attrib);
T 694                 }
695                 break;
696
697             // show a label
698             case 'label':
699                 if ($attrib['name'] || $attrib['command']) {
0c2596 700                     $vars = $attrib + array('product' => $this->config->get('product_name'));
c87806 701                     unset($vars['name'], $vars['command']);
0c2596 702                     $label = $this->app->gettext($attrib + array('vars' => $vars));
A 703                     return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : html::quote($label)) : '';
47124c 704                 }
T 705                 break;
706
707             // include a file
708             case 'include':
fbe24e 709                 if (!$this->plugin_skin_path || !is_file($path = realpath($this->plugin_skin_path . $attrib['file'])))
0c2596 710                     $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config->get('skin_path')).$attrib['file']);
A 711
ff73e0 712                 if (is_readable($path)) {
0c2596 713                     if ($this->config->get('skin_include_php')) {
47124c 714                         $incl = $this->include_php($path);
T 715                     }
ff73e0 716                     else {
cc97ea 717                       $incl = file_get_contents($path);
T 718                     }
b4f7c6 719                     $incl = $this->parse_conditions($incl);
47124c 720                     return $this->parse_xml($incl);
T 721                 }
722                 break;
723
724             case 'plugin.include':
cc97ea 725                 $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
T 726                 return $hook['content'];
727                 break;
ad334a 728
cc97ea 729             // define a container block
T 730             case 'container':
731                 if ($attrib['name'] && $attrib['id']) {
732                     $this->command('gui_container', $attrib['name'], $attrib['id']);
733                     // let plugins insert some content here
734                     $hook = $this->app->plugins->exec_hook("template_container", $attrib);
735                     return $hook['content'];
47124c 736                 }
T 737                 break;
738
739             // return code for a specific application object
740             case 'object':
741                 $object = strtolower($attrib['name']);
cc97ea 742                 $content = '';
47124c 743
T 744                 // we are calling a class/method
745                 if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
746                     if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
747                     (is_string($handler[0]) && class_exists($handler[0])))
cc97ea 748                     $content = call_user_func($handler, $attrib);
47124c 749                 }
cc97ea 750                 // execute object handler function
47124c 751                 else if (function_exists($handler)) {
cc97ea 752                     $content = call_user_func($handler, $attrib);
47124c 753                 }
f23073 754                 else if ($object == 'doctype') {
T 755                     $content = html::doctype($attrib['value']);
756                 }
ae39c4 757                 else if ($object == 'logo') {
T 758                     $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
0c2596 759                     if ($logo = $this->config->get('skin_logo'))
A 760                         $attrib['src'] = $logo;
ae39c4 761                     $content = html::img($attrib);
T 762                 }
cc97ea 763                 else if ($object == 'productname') {
0c2596 764                     $name = $this->config->get('product_name', 'Roundcube Webmail');
A 765                     $content = html::quote($name);
47124c 766                 }
cc97ea 767                 else if ($object == 'version') {
c8fb2b 768                     $ver = (string)RCMAIL_VERSION;
T 769                     if (is_file(INSTALL_PATH . '.svn/entries')) {
770                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
771                           $ver .= ' [SVN r'.$regs[1].']';
772                     }
0c2596 773                     $content = html::quote($ver);
47124c 774                 }
cc97ea 775                 else if ($object == 'steptitle') {
0c2596 776                   $content = html::quote($this->get_pagetitle());
f645ce 777                 }
cc97ea 778                 else if ($object == 'pagetitle') {
0c2596 779                     if ($this->config->get('devel_mode') && !empty($_SESSION['username']))
A 780                         $title = $_SESSION['username'].' :: ';
781                     else if ($prod_name = $this->config->get('product_name'))
782                         $title = $prod_name . ' :: ';
159763 783                     else
0c2596 784                         $title = '';
f645ce 785                     $title .= $this->get_pagetitle();
0c2596 786                     $content = html::quote($title);
47124c 787                 }
ad334a 788
cc97ea 789                 // exec plugin hooks for this template object
T 790                 $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
791                 return $hook['content'];
8e1d4a 792
A 793             // return code for a specified eval expression
794             case 'exp':
cc97ea 795                 $value = $this->parse_expression($attrib['expression']);
0c2596 796                 return eval("return html::quote($value);");
ad334a 797
47124c 798             // return variable
T 799             case 'var':
800                 $var = explode(':', $attrib['name']);
801                 $name = $var[1];
802                 $value = '';
803
804                 switch ($var[0]) {
805                     case 'env':
806                         $value = $this->env[$name];
807                         break;
808                     case 'config':
0c2596 809                         $value = $this->config->get($name);
c321a9 810                         if (is_array($value) && $value[$_SESSION['storage_host']]) {
T 811                             $value = $value[$_SESSION['storage_host']];
47124c 812                         }
T 813                         break;
814                     case 'request':
1aceb9 815                         $value = rcube_utils::get_input_value($name, rcube_utils::INPUT_GPC);
47124c 816                         break;
T 817                     case 'session':
818                         $value = $_SESSION[$name];
819                         break;
d4273b 820                     case 'cookie':
A 821                         $value = htmlspecialchars($_COOKIE[$name]);
822                         break;
a17fe6 823                     case 'browser':
A 824                         $value = $this->browser->{$name};
825                         break;
47124c 826                 }
T 827
828                 if (is_array($value)) {
829                     $value = implode(', ', $value);
830                 }
831
0c2596 832                 return html::quote($value);
47124c 833                 break;
T 834         }
835         return '';
836     }
0c2596 837
47124c 838
T 839     /**
840      * Include a specific file and return it's contents
841      *
842      * @param string File path
843      * @return string Contents of the processed file
844      */
0c2596 845     protected function include_php($file)
47124c 846     {
T 847         ob_start();
848         include $file;
849         $out = ob_get_contents();
850         ob_end_clean();
851
852         return $out;
853     }
0c2596 854
47124c 855
T 856     /**
857      * Create and register a button
858      *
859      * @param  array Named button attributes
860      * @return string HTML button
861      * @todo   Remove all inline JS calls and use jQuery instead.
862      * @todo   Remove all sprintf()'s - they are pretty, but also slow.
863      */
ed132e 864     public function button($attrib)
47124c 865     {
T 866         static $s_button_count = 100;
867
868         // these commands can be called directly via url
cc97ea 869         $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
47124c 870
T 871         if (!($attrib['command'] || $attrib['name'])) {
872             return '';
873         }
ae0c82 874
47124c 875         // try to find out the button type
T 876         if ($attrib['type']) {
877             $attrib['type'] = strtolower($attrib['type']);
878         }
879         else {
880             $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
881         }
e9b5a6 882
47124c 883         $command = $attrib['command'];
T 884
e9b5a6 885         if ($attrib['task'])
T 886           $command = $attrib['task'] . '.' . $command;
ad334a 887
af3cf8 888         if (!$attrib['image']) {
T 889             $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
890         }
47124c 891
T 892         if (!$attrib['id']) {
893             $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
894         }
895         // get localized text for labels and titles
896         if ($attrib['title']) {
0c2596 897             $attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain']));
47124c 898         }
T 899         if ($attrib['label']) {
0c2596 900             $attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain']));
47124c 901         }
T 902         if ($attrib['alt']) {
0c2596 903             $attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain']));
47124c 904         }
9b2ccd 905
47124c 906         // set title to alt attribute for IE browsers
c9e9fe 907         if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
A 908             $attrib['title'] = $attrib['alt'];
47124c 909         }
T 910
911         // add empty alt attribute for XHTML compatibility
912         if (!isset($attrib['alt'])) {
913             $attrib['alt'] = '';
914         }
915
916         // register button in the system
917         if ($attrib['command']) {
918             $this->add_script(sprintf(
919                 "%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
1aceb9 920                 rcmail::JS_OBJECT_NAME,
47124c 921                 $command,
T 922                 $attrib['id'],
923                 $attrib['type'],
ae0c82 924                 $attrib['imageact'] ? $this->abs_url($attrib['imageact']) : $attrib['classact'],
A 925                 $attrib['imagesel'] ? $this->abs_url($attrib['imagesel']) : $attrib['classsel'],
926                 $attrib['imageover'] ? $this->abs_url($attrib['imageover']) : ''
47124c 927             ));
T 928
929             // make valid href to specific buttons
197601 930             if (in_array($attrib['command'], rcmail::$main_tasks)) {
0c2596 931                 $attrib['href']    = $this->app->url(array('task' => $attrib['command']));
1aceb9 932                 $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", rcmail::JS_OBJECT_NAME, $attrib['command']);
47124c 933             }
e9b5a6 934             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
0c2596 935                 $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
e9b5a6 936             }
47124c 937             else if (in_array($attrib['command'], $a_static_commands)) {
0c2596 938                 $attrib['href'] = $this->app->url(array('action' => $attrib['command']));
a25d39 939             }
29f977 940             else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
T 941               $attrib['href'] = $this->env['permaurl'];
942             }
47124c 943         }
T 944
945         // overwrite attributes
946         if (!$attrib['href']) {
947             $attrib['href'] = '#';
948         }
e9b5a6 949         if ($attrib['task']) {
T 950             if ($attrib['classact'])
951                 $attrib['class'] = $attrib['classact'];
952         }
953         else if ($command && !$attrib['onclick']) {
47124c 954             $attrib['onclick'] = sprintf(
T 955                 "return %s.command('%s','%s',this)",
1aceb9 956                 rcmail::JS_OBJECT_NAME,
47124c 957                 $command,
T 958                 $attrib['prop']
959             );
960         }
961
962         $out = '';
963
964         // generate image tag
0c2596 965         if ($attrib['type'] == 'image') {
47124c 966             $attrib_str = html::attrib_string(
T 967                 $attrib,
968                 array(
c9e9fe 969                     'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
A 970                     'vspace', 'align', 'alt', 'tabindex', 'title'
47124c 971                 )
T 972             );
ae0c82 973             $btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
47124c 974             if ($attrib['label']) {
T 975                 $btn_content .= ' '.$attrib['label'];
976             }
c9e9fe 977             $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
47124c 978         }
0c2596 979         else if ($attrib['type'] == 'link') {
356a67 980             $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
203ee4 981             $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
5587b3 982             if ($attrib['innerclass'])
T 983                 $btn_content = html::span($attrib['innerclass'], $btn_content);
47124c 984         }
0c2596 985         else if ($attrib['type'] == 'input') {
47124c 986             $attrib['type'] = 'button';
T 987
988             if ($attrib['label']) {
989                 $attrib['value'] = $attrib['label'];
990             }
f94e44 991             if ($attrib['command']) {
T 992               $attrib['disabled'] = 'disabled';
993             }
47124c 994
ce6433 995             $out = html::tag('input', $attrib, null, array('type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex', 'disabled'));
47124c 996         }
T 997
998         // generate html code for button
999         if ($btn_content) {
1000             $attrib_str = html::attrib_string($attrib, $link_attrib);
1001             $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
1002         }
1003
1004         return $out;
0c2596 1005     }
A 1006
1007
1008     /**
1009      * Link an external script file
1010      *
1011      * @param string File URL
1012      * @param string Target position [head|foot]
1013      */
1014     public function include_script($file, $position='head')
1015     {
1016         static $sa_files = array();
1017
1018         if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
1019             $file = $this->scripts_path . $file;
1020             if ($fs = @filemtime($file)) {
1021                 $file .= '?s=' . $fs;
1022             }
1023         }
1024
1025         if (in_array($file, $sa_files)) {
1026             return;
1027         }
1028
1029         $sa_files[] = $file;
1030
1031         if (!is_array($this->script_files[$position])) {
1032             $this->script_files[$position] = array();
1033         }
1034
1035         $this->script_files[$position][] = $file;
1036     }
1037
1038
1039     /**
1040      * Add inline javascript code
1041      *
1042      * @param string JS code snippet
1043      * @param string Target position [head|head_top|foot]
1044      */
1045     public function add_script($script, $position='head')
1046     {
1047         if (!isset($this->scripts[$position])) {
1048             $this->scripts[$position] = "\n" . rtrim($script);
1049         }
1050         else {
1051             $this->scripts[$position] .= "\n" . rtrim($script);
1052         }
1053     }
1054
1055
1056     /**
1057      * Link an external css file
1058      *
1059      * @param string File URL
1060      */
1061     public function include_css($file)
1062     {
1063         $this->css_files[] = $file;
1064     }
1065
1066
1067     /**
1068      * Add HTML code to the page header
1069      *
1070      * @param string $str HTML code
1071      */
1072     public function add_header($str)
1073     {
1074         $this->header .= "\n" . $str;
1075     }
1076
1077
1078     /**
1079      * Add HTML code to the page footer
1080      * To be added right befor </body>
1081      *
1082      * @param string $str HTML code
1083      */
1084     public function add_footer($str)
1085     {
1086         $this->footer .= "\n" . $str;
1087     }
1088
1089
1090     /**
1091      * Process template and write to stdOut
1092      *
1093      * @param string HTML template
1094      * @param string Base for absolute paths
1095      */
1096     public function _write($templ = '', $base_path = '')
1097     {
1098         $output = empty($templ) ? $this->default_template : trim($templ);
1099
1100         // set default page title
1101         if (empty($this->pagetitle)) {
1102             $this->pagetitle = 'Roundcube Mail';
1103         }
1104
1105         // replace specialchars in content
1106         $page_title  = html::quote($this->pagetitle);
1107         $page_header = '';
1108         $page_footer = '';
1109
1110         // include meta tag with charset
1111         if (!empty($this->charset)) {
1112             if (!headers_sent()) {
1113                 header('Content-Type: text/html; charset=' . $this->charset);
1114             }
1115             $page_header = '<meta http-equiv="content-type"';
1116             $page_header.= ' content="text/html; charset=';
1117             $page_header.= $this->charset . '" />'."\n";
1118         }
1119
1120         // definition of the code to be placed in the document header and footer
1121         if (is_array($this->script_files['head'])) {
1122             foreach ($this->script_files['head'] as $file) {
1123                 $page_header .= html::script($file);
1124             }
1125         }
1126
1127         $head_script = $this->scripts['head_top'] . $this->scripts['head'];
1128         if (!empty($head_script)) {
1129             $page_header .= html::script(array(), $head_script);
1130         }
1131
1132         if (!empty($this->header)) {
1133             $page_header .= $this->header;
1134         }
1135
1136         // put docready commands into page footer
1137         if (!empty($this->scripts['docready'])) {
1138             $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
1139         }
1140
1141         if (is_array($this->script_files['foot'])) {
1142             foreach ($this->script_files['foot'] as $file) {
1143                 $page_footer .= html::script($file);
1144             }
1145         }
1146
1147         if (!empty($this->footer)) {
1148             $page_footer .= $this->footer . "\n";
1149         }
1150
1151         if (!empty($this->scripts['foot'])) {
1152             $page_footer .= html::script(array(), $this->scripts['foot']);
1153         }
1154
1155         // find page header
1156         if ($hpos = stripos($output, '</head>')) {
1157             $page_header .= "\n";
1158         }
1159         else {
1160             if (!is_numeric($hpos)) {
1161                 $hpos = stripos($output, '<body');
1162             }
1163             if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) {
1164                 while ($output[$hpos] != '>') {
1165                     $hpos++;
1166                 }
1167                 $hpos++;
1168             }
1169             $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n";
1170         }
1171
1172         // add page hader
1173         if ($hpos) {
1174             $output = substr_replace($output, $page_header, $hpos, 0);
1175         }
1176         else {
1177             $output = $page_header . $output;
1178         }
1179
1180         // add page footer
1181         if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) {
1182             $output = substr_replace($output, $page_footer."\n", $fpos, 0);
1183         }
1184         else {
1185             $output .= "\n".$page_footer;
1186         }
1187
1188         // add css files in head, before scripts, for speed up with parallel downloads
1189         if (!empty($this->css_files) && 
1190             (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
1191         ) {
1192             $css = '';
1193             foreach ($this->css_files as $file) {
1194                 $css .= html::tag('link', array('rel' => 'stylesheet',
1195                     'type' => 'text/css', 'href' => $file, 'nl' => true));
1196             }
1197             $output = substr_replace($output, $css, $pos, 0);
1198         }
1199
1200         $this->base_path = $base_path;
1201
1202         // correct absolute paths in images and other tags
1203         // add timestamp to .js and .css filename
1204         $output = preg_replace_callback(
1205             '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
1206             array($this, 'file_callback'), $output);
1207
1208         // trigger hook with final HTML content to be sent
1209         $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output));
1210         if (!$hook['abort']) {
1211             if ($this->charset != RCMAIL_CHARSET) {
1212                 echo rcube_charset::convert($hook['content'], RCMAIL_CHARSET, $this->charset);
1213             }
1214             else {
1215                 echo $hook['content'];
1216             }
1217         }
1218     }
1219
1220
1221     /**
1222      * Callback function for preg_replace_callback in write()
1223      *
1224      * @return string Parsed string
1225      */
1226     protected function file_callback($matches)
1227     {
1228         $file = $matches[3];
1229
1230         // correct absolute paths
1231         if ($file[0] == '/') {
1232             $file = $this->base_path . $file;
1233         }
1234
1235         // add file modification timestamp
1236         if (preg_match('/\.(js|css)$/', $file)) {
1237             if ($fs = @filemtime($file)) {
1238                 $file .= '?s=' . $fs;
1239             }
1240         }
1241
1242         return $matches[1] . '=' . $matches[2] . $file . $matches[4];
47124c 1243     }
T 1244
1245
1246     /*  ************* common functions delivering gui objects **************  */
1247
1248
1249     /**
197601 1250      * Create a form tag with the necessary hidden fields
T 1251      *
1252      * @param array Named tag parameters
1253      * @return string HTML code for the form
1254      */
1255     public function form_tag($attrib, $content = null)
1256     {
57f0c8 1257       if ($this->framed || !empty($_REQUEST['_framed'])) {
197601 1258         $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
T 1259         $hidden = $hiddenfield->show();
1260       }
ad334a 1261
197601 1262       if (!$content)
T 1263         $attrib['noclose'] = true;
ad334a 1264
197601 1265       return html::tag('form',
T 1266         $attrib + array('action' => "./", 'method' => "get"),
57f0c8 1267         $hidden . $content,
T 1268         array('id','class','style','name','method','action','enctype','onsubmit'));
1269     }
ad334a 1270
A 1271
57f0c8 1272     /**
T 1273      * Build a form tag with a unique request token
1274      *
1275      * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
1276      * @param string Form content
1277      * @return string HTML code for the form
1278      */
747797 1279     public function request_form($attrib, $content = '')
57f0c8 1280     {
T 1281         $hidden = new html_hiddenfield();
1282         if ($attrib['task']) {
1283             $hidden->add(array('name' => '_task', 'value' => $attrib['task']));
1284         }
1285         if ($attrib['action']) {
1286             $hidden->add(array('name' => '_action', 'value' => $attrib['action']));
1287         }
ad334a 1288
57f0c8 1289         unset($attrib['task'], $attrib['request']);
T 1290         $attrib['action'] = './';
ad334a 1291
57f0c8 1292         // we already have a <form> tag
0501b6 1293         if ($attrib['form']) {
T 1294             if ($this->framed || !empty($_REQUEST['_framed']))
1295                 $hidden->add(array('name' => '_framed', 'value' => '1'));
57f0c8 1296             return $hidden->show() . $content;
0501b6 1297         }
57f0c8 1298         else
T 1299             return $this->form_tag($attrib, $hidden->show() . $content);
197601 1300     }
T 1301
1302
1303     /**
47124c 1304      * GUI object 'username'
T 1305      * Showing IMAP username of the current session
1306      *
1307      * @param array Named tag parameters (currently not used)
1308      * @return string HTML code for the gui object
1309      */
197601 1310     public function current_username($attrib)
47124c 1311     {
T 1312         static $username;
1313
1314         // alread fetched
1315         if (!empty($username)) {
1316             return $username;
1317         }
1318
e99991 1319         // Current username is an e-mail address
A 1320         if (strpos($_SESSION['username'], '@')) {
1321             $username = $_SESSION['username'];
1322         }
929a50 1323         // get e-mail address from default identity
e99991 1324         else if ($sql_arr = $this->app->user->get_identity()) {
7e9cec 1325             $username = $sql_arr['email'];
47124c 1326         }
T 1327         else {
7e9cec 1328             $username = $this->app->user->get_username();
47124c 1329         }
T 1330
1aceb9 1331         return rcube_utils::idn_to_utf8($username);
47124c 1332     }
T 1333
1334
1335     /**
1336      * GUI object 'loginform'
1337      * Returns code for the webmail login form
1338      *
1339      * @param array Named parameters
1340      * @return string HTML code for the gui object
1341      */
0c2596 1342     protected function login_form($attrib)
47124c 1343     {
0c2596 1344         $default_host = $this->config->get('default_host');
A 1345         $autocomplete = (int) $this->config->get('login_autocomplete');
47124c 1346
T 1347         $_SESSION['temp'] = true;
ad334a 1348
cc97ea 1349         // save original url
1aceb9 1350         $url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST);
3a2b27 1351         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
cc97ea 1352             $url = $_SERVER['QUERY_STRING'];
47124c 1353
1cca4f 1354         // set atocomplete attribute
A 1355         $user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
1356         $host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
1357         $pass_attrib = $autocomplete > 1 ? array() : array('autocomplete' => 'off');
1358
c3be8e 1359         $input_task   = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
47124c 1360         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
c8ae24 1361         $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
65082b 1362         $input_dst    = new html_hiddenfield(array('name' => '_dstactive', 'id' => 'rcmlogindst', 'value' => '_default_'));
cc97ea 1363         $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
1cca4f 1364         $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser')
A 1365             + $attrib + $user_attrib);
1366         $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd')
1367             + $attrib + $pass_attrib);
47124c 1368         $input_host   = null;
T 1369
f3e101 1370         if (is_array($default_host) && count($default_host) > 1) {
47124c 1371             $input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
T 1372
1373             foreach ($default_host as $key => $value) {
1374                 if (!is_array($value)) {
1375                     $input_host->add($value, (is_numeric($key) ? $value : $key));
1376                 }
1377                 else {
1378                     $input_host = null;
1379                     break;
1380                 }
1381             }
f3e101 1382         }
A 1383         else if (is_array($default_host) && ($host = array_pop($default_host))) {
1384             $hide_host = true;
1385             $input_host = new html_hiddenfield(array(
1386                 'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);
47124c 1387         }
e3e597 1388         else if (empty($default_host)) {
1cca4f 1389             $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost')
A 1390                 + $attrib + $host_attrib);
47124c 1391         }
T 1392
1393         $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form';
1394         $this->add_gui_object('loginform', $form_name);
1395
1396         // create HTML table with two cols
1397         $table = new html_table(array('cols' => 2));
1398
0c2596 1399         $table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username'))));
1aceb9 1400         $table->add('input', $input_user->show(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC)));
47124c 1401
0c2596 1402         $table->add('title', html::label('rcmloginpwd', html::quote($this->app->gettext('password'))));
497013 1403         $table->add('input', $input_pass->show());
47124c 1404
T 1405         // add host selection row
f3e101 1406         if (is_object($input_host) && !$hide_host) {
0c2596 1407             $table->add('title', html::label('rcmloginhost', html::quote($this->app->gettext('server'))));
1aceb9 1408             $table->add('input', $input_host->show(rcube_utils::get_input_value('_host', rcube_utils::INPUT_GPC)));
47124c 1409         }
T 1410
c3be8e 1411         $out  = $input_task->show();
T 1412         $out .= $input_action->show();
c8ae24 1413         $out .= $input_tzone->show();
65082b 1414         $out .= $input_dst->show();
cc97ea 1415         $out .= $input_url->show();
47124c 1416         $out .= $table->show();
ad334a 1417
f3e101 1418         if ($hide_host) {
A 1419             $out .= $input_host->show();
1420         }
47124c 1421
T 1422         // surround html output with a form tag
1423         if (empty($attrib['form'])) {
f3e101 1424             $out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
47124c 1425         }
T 1426
1427         return $out;
1428     }
1429
1430
1431     /**
8e211a 1432      * GUI object 'preloader'
A 1433      * Loads javascript code for images preloading
1434      *
1435      * @param array Named parameters
1436      * @return void
1437      */
0c2596 1438     protected function preloader($attrib)
8e211a 1439     {
A 1440         $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
1441         $images = array_map(array($this, 'abs_url'), $images);
1442
1443         if (empty($images) || $this->app->task == 'logout')
1444             return;
1445
0c2596 1446         $this->add_script('var images = ' . self::json_serialize($images) .';
8e211a 1447             for (var i=0; i<images.length; i++) {
A 1448                 img = new Image();
1449                 img.src = images[i];
044d66 1450             }', 'docready');
8e211a 1451     }
A 1452
1453
1454     /**
47124c 1455      * GUI object 'searchform'
T 1456      * Returns code for search function
1457      *
1458      * @param array Named parameters
1459      * @return string HTML code for the gui object
1460      */
0c2596 1461     protected function search_form($attrib)
47124c 1462     {
T 1463         // add some labels to client
1464         $this->add_label('searching');
1465
1466         $attrib['name'] = '_q';
1467
1468         if (empty($attrib['id'])) {
1469             $attrib['id'] = 'rcmqsearchbox';
1470         }
a3f149 1471         if ($attrib['type'] == 'search' && !$this->browser->khtml) {
2eb794 1472             unset($attrib['type'], $attrib['results']);
a3f149 1473         }
ad334a 1474
47124c 1475         $input_q = new html_inputfield($attrib);
T 1476         $out = $input_q->show();
1477
1478         $this->add_gui_object('qsearchbox', $attrib['id']);
1479
1480         // add form tag around text field
1481         if (empty($attrib['form'])) {
197601 1482             $out = $this->form_tag(array(
T 1483                 'name' => "rcmqsearchform",
1aceb9 1484                 'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search');return false;",
197601 1485                 'style' => "display:inline"),
2eb794 1486                 $out);
47124c 1487         }
T 1488
1489         return $out;
1490     }
1491
1492
1493     /**
1494      * Builder for GUI object 'message'
1495      *
1496      * @param array Named tag parameters
1497      * @return string HTML code for the gui object
1498      */
0c2596 1499     protected function message_container($attrib)
47124c 1500     {
T 1501         if (isset($attrib['id']) === false) {
1502             $attrib['id'] = 'rcmMessageContainer';
1503         }
1504
1505         $this->add_gui_object('message', $attrib['id']);
0c2596 1506
A 1507         return html::div($attrib, '');
47124c 1508     }
T 1509
1510
1511     /**
1512      * GUI object 'charsetselector'
1513      *
1514      * @param array Named parameters for the select tag
1515      * @return string HTML code for the gui object
1516      */
0c2596 1517     public function charset_selector($attrib)
47124c 1518     {
T 1519         // pass the following attributes to the form class
1520         $field_attrib = array('name' => '_charset');
1521         foreach ($attrib as $attr => $value) {
e55ab0 1522             if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
47124c 1523                 $field_attrib[$attr] = $value;
T 1524             }
1525         }
e55ab0 1526
47124c 1527         $charsets = array(
0c2596 1528             'UTF-8'        => 'UTF-8 ('.$this->app->gettext('unicode').')',
A 1529             'US-ASCII'     => 'ASCII ('.$this->app->gettext('english').')',
1530             'ISO-8859-1'   => 'ISO-8859-1 ('.$this->app->gettext('westerneuropean').')',
1531             'ISO-8859-2'   => 'ISO-8859-2 ('.$this->app->gettext('easterneuropean').')',
1532             'ISO-8859-4'   => 'ISO-8859-4 ('.$this->app->gettext('baltic').')',
1533             'ISO-8859-5'   => 'ISO-8859-5 ('.$this->app->gettext('cyrillic').')',
1534             'ISO-8859-6'   => 'ISO-8859-6 ('.$this->app->gettext('arabic').')',
1535             'ISO-8859-7'   => 'ISO-8859-7 ('.$this->app->gettext('greek').')',
1536             'ISO-8859-8'   => 'ISO-8859-8 ('.$this->app->gettext('hebrew').')',
1537             'ISO-8859-9'   => 'ISO-8859-9 ('.$this->app->gettext('turkish').')',
1538             'ISO-8859-10'   => 'ISO-8859-10 ('.$this->app->gettext('nordic').')',
1539             'ISO-8859-11'   => 'ISO-8859-11 ('.$this->app->gettext('thai').')',
1540             'ISO-8859-13'   => 'ISO-8859-13 ('.$this->app->gettext('baltic').')',
1541             'ISO-8859-14'   => 'ISO-8859-14 ('.$this->app->gettext('celtic').')',
1542             'ISO-8859-15'   => 'ISO-8859-15 ('.$this->app->gettext('westerneuropean').')',
1543             'ISO-8859-16'   => 'ISO-8859-16 ('.$this->app->gettext('southeasterneuropean').')',
1544             'WINDOWS-1250' => 'Windows-1250 ('.$this->app->gettext('easterneuropean').')',
1545             'WINDOWS-1251' => 'Windows-1251 ('.$this->app->gettext('cyrillic').')',
1546             'WINDOWS-1252' => 'Windows-1252 ('.$this->app->gettext('westerneuropean').')',
1547             'WINDOWS-1253' => 'Windows-1253 ('.$this->app->gettext('greek').')',
1548             'WINDOWS-1254' => 'Windows-1254 ('.$this->app->gettext('turkish').')',
1549             'WINDOWS-1255' => 'Windows-1255 ('.$this->app->gettext('hebrew').')',
1550             'WINDOWS-1256' => 'Windows-1256 ('.$this->app->gettext('arabic').')',
1551             'WINDOWS-1257' => 'Windows-1257 ('.$this->app->gettext('baltic').')',
1552             'WINDOWS-1258' => 'Windows-1258 ('.$this->app->gettext('vietnamese').')',
1553             'ISO-2022-JP'  => 'ISO-2022-JP ('.$this->app->gettext('japanese').')',
1554             'ISO-2022-KR'  => 'ISO-2022-KR ('.$this->app->gettext('korean').')',
1555             'ISO-2022-CN'  => 'ISO-2022-CN ('.$this->app->gettext('chinese').')',
1556             'EUC-JP'       => 'EUC-JP ('.$this->app->gettext('japanese').')',
1557             'EUC-KR'       => 'EUC-KR ('.$this->app->gettext('korean').')',
1558             'EUC-CN'       => 'EUC-CN ('.$this->app->gettext('chinese').')',
1559             'BIG5'         => 'BIG5 ('.$this->app->gettext('chinese').')',
1560             'GB2312'       => 'GB2312 ('.$this->app->gettext('chinese').')',
e55ab0 1561         );
47124c 1562
e55ab0 1563         if (!empty($_POST['_charset']))
2eb794 1564             $set = $_POST['_charset'];
A 1565         else if (!empty($attrib['selected']))
1566             $set = $attrib['selected'];
1567         else
1568             $set = $this->get_charset();
47124c 1569
2eb794 1570         $set = strtoupper($set);
A 1571         if (!isset($charsets[$set]))
1572             $charsets[$set] = $set;
e55ab0 1573
A 1574         $select = new html_select($field_attrib);
1575         $select->add(array_values($charsets), array_keys($charsets));
1576
1577         return $select->show($set);
47124c 1578     }
T 1579
1a0f60 1580     /**
T 1581      * Include content from config/about.<LANG>.html if available
1582      */
0c2596 1583     protected function about_content($attrib)
1a0f60 1584     {
T 1585         $content = '';
1586         $filenames = array(
1587             'about.' . $_SESSION['language'] . '.html',
1588             'about.' . substr($_SESSION['language'], 0, 2) . '.html',
1589             'about.html',
1590         );
1591         foreach ($filenames as $file) {
1592             $fn = RCMAIL_CONFIG_DIR . '/' . $file;
1593             if (is_readable($fn)) {
1594                 $content = file_get_contents($fn);
1595                 $content = $this->parse_conditions($content);
1596                 $content = $this->parse_xml($content);
1597                 break;
1598             }
1599         }
1600
1601         return $content;
1602     }
1603
0c2596 1604 }