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