alecpl
2010-09-25 e019f2d0f2dc2fbfa345ab5d7ae85e67bfdd76b8
commit | author | age
47124c 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_template.php                                    |
6  |                                                                       |
e019f2 7  | This file is part of the Roundcube Webmail client                     |
A 8  | Copyright (C) 2006-2010, Roundcube Dev. - Switzerland                 |
47124c 9  | Licensed under the GNU GPL                                            |
T 10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Class to handle HTML page output using a skin template.             |
13  |   Extends rcube_html_page class from rcube_shared.inc                 |
14  |                                                                       |
15  +-----------------------------------------------------------------------+
16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17  +-----------------------------------------------------------------------+
18
19  $Id$
20
21  */
22
23
24 /**
25  * Class to create HTML page output using a skin template
26  *
27  * @package View
28  * @todo Documentation
29  * @uses rcube_html_page
30  */
31 class rcube_template extends rcube_html_page
32 {
197601 33     var $app;
47124c 34     var $config;
T 35     var $framed = false;
36     var $pagetitle = '';
69f18a 37     var $message = null;
47124c 38     var $env = array();
T 39     var $js_env = array();
40     var $js_commands = array();
41     var $object_handlers = array();
42
115158 43     public $browser;
c8a21d 44     public $type = 'html';
47124c 45     public $ajax_call = false;
T 46
47     /**
48      * Constructor
49      *
50      * @todo   Use jQuery's $(document).ready() here.
197601 51      * @todo   Replace $this->config with the real rcube_config object
47124c 52      */
197601 53     public function __construct($task, $framed = false)
47124c 54     {
T 55         parent::__construct();
56
197601 57         $this->app = rcmail::get_instance();
T 58         $this->config = $this->app->config->all();
a3f149 59         $this->browser = new rcube_browser();
197601 60         
T 61         //$this->framed = $framed;
83a763 62         $this->set_env('task', $task);
549933 63         $this->set_env('request_token', $this->app->get_request_token());
47124c 64
423065 65         // load the correct skin (in case user-defined)
T 66         $this->set_skin($this->config['skin']);
e58df3 67
47124c 68         // add common javascripts
T 69         $javascript = 'var '.JS_OBJECT_NAME.' = new rcube_webmail();';
70
71         // don't wait for page onload. Call init at the bottom of the page (delayed)
cc97ea 72         $javascript_foot = '$(document).ready(function(){ '.JS_OBJECT_NAME.'.init(); });';
47124c 73
T 74         $this->add_script($javascript, 'head_top');
75         $this->add_script($javascript_foot, 'foot');
76         $this->scripts_path = 'program/js/';
8f85e3 77         $this->include_script('jquery-1.4.min.js');
47124c 78         $this->include_script('common.js');
T 79         $this->include_script('app.js');
80
81         // register common UI objects
82         $this->add_handlers(array(
83             'loginform'       => array($this, 'login_form'),
8e211a 84             'preloader'       => array($this, 'preloader'),
47124c 85             'username'        => array($this, 'current_username'),
T 86             'message'         => array($this, 'message_container'),
87             'charsetselector' => array($this, 'charset_selector'),
88         ));
89     }
90
91     /**
92      * Set environment variable
93      *
94      * @param string Property name
95      * @param mixed Property value
96      * @param boolean True if this property should be added to client environment
97      */
98     public function set_env($name, $value, $addtojs = true)
99     {
100         $this->env[$name] = $value;
101         if ($addtojs || isset($this->js_env[$name])) {
102             $this->js_env[$name] = $value;
103         }
104     }
105
106
107     /**
108      * Set page title variable
109      */
110     public function set_pagetitle($title)
111     {
112         $this->pagetitle = $title;
113     }
114
f645ce 115
T 116     /**
117      * Getter for the current page title
118      *
119      * @return string The page title
120      */
121     public function get_pagetitle()
122     {
123         if (!empty($this->pagetitle)) {
124             $title = $this->pagetitle;
125         }
126         else if ($this->env['task'] == 'login') {
127             $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
128         }
129         else {
130             $title = ucfirst($this->env['task']);
131         }
132         
133         return $title;
134     }
135
136
e58df3 137     /**
A 138      * Set skin
139      */
140     public function set_skin($skin)
141     {
62c791 142         $valid = false;
T 143         
144         if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin)) {
423065 145             $skin_path = 'skins/'.$skin;
62c791 146             $valid = true;
T 147         }
148         else {
423065 149             $skin_path = $this->config['skin_path'] ? $this->config['skin_path'] : 'skins/default';
62c791 150             $valid = !$skin;
T 151         }
423065 152
T 153         $this->app->config->set('skin_path', $skin_path);
154         $this->config['skin_path'] = $skin_path;
62c791 155         
T 156         return $valid;
e58df3 157     }
A 158
159     /**
160      * Check if a specific template exists
161      *
162      * @param string Template name
163      * @return boolean True if template exists
164      */
165     public function template_exists($name)
166     {
423065 167         $filename = $this->config['skin_path'] . '/templates/' . $name . '.html';
e58df3 168
423065 169         return (is_file($filename) && is_readable($filename));
e58df3 170     }
47124c 171
T 172     /**
173      * Register a template object handler
174      *
175      * @param  string Object name
176      * @param  string Function name to call
177      * @return void
178      */
179     public function add_handler($obj, $func)
180     {
181         $this->object_handlers[$obj] = $func;
182     }
183
184     /**
185      * Register a list of template object handlers
186      *
187      * @param  array Hash array with object=>handler pairs
188      * @return void
189      */
190     public function add_handlers($arr)
191     {
192         $this->object_handlers = array_merge($this->object_handlers, $arr);
193     }
194
195     /**
196      * Register a GUI object to the client script
197      *
198      * @param  string Object name
199      * @param  string Object ID
200      * @return void
201      */
202     public function add_gui_object($obj, $id)
203     {
204         $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');");
205     }
206
207     /**
208      * Call a client method
209      *
210      * @param string Method to call
211      * @param ... Additional arguments
212      */
213     public function command()
214     {
0e99d3 215         $cmd = func_get_args();
T 216         if (strpos($cmd[0], 'plugin.') === false)
217           $this->js_commands[] = $cmd;
47124c 218     }
T 219
220
221     /**
222      * Add a localized label to the client environment
223      */
224     public function add_label()
225     {
cc97ea 226         $args = func_get_args();
T 227         if (count($args) == 1 && is_array($args[0]))
228           $args = $args[0];
229         
230         foreach ($args as $name) {
47124c 231             $this->command('add_label', $name, rcube_label($name));
T 232         }
233     }
234
235
236     /**
237      * Invoke display_message command
238      *
239      * @param string Message to display
240      * @param string Message type [notice|confirm|error]
241      * @param array Key-value pairs to be replaced in localized text
69f18a 242      * @param boolean Override last set message
47124c 243      * @uses self::command()
T 244      */
69f18a 245     public function show_message($message, $type='notice', $vars=null, $override=true)
47124c 246     {
69f18a 247         if ($override || !$this->message) {
T 248             $this->message = $message;
249             $this->command(
250                 'display_message',
251                 rcube_label(array('name' => $message, 'vars' => $vars)),
252                 $type);
253         }
47124c 254     }
T 255
256
257     /**
258      * Delete all stored env variables and commands
259      *
260      * @return void
261      * @uses   rcube_html::reset()
262      * @uses   self::$env
263      * @uses   self::$js_env
264      * @uses   self::$js_commands
265      * @uses   self::$object_handlers
266      */
c719f3 267     public function reset()
47124c 268     {
T 269         $this->env = array();
270         $this->js_env = array();
271         $this->js_commands = array();
272         $this->object_handlers = array();
273         parent::reset();
274     }
275
276
277     /**
c719f3 278      * Redirect to a certain url
T 279      *
280      * @param mixed Either a string with the action or url parameters as key-value pairs
281      * @see rcmail::url()
282      */
283     public function redirect($p = array())
284     {
285         $location = $this->app->url($p);
286         header('Location: ' . $location);
287         exit;
288     }
289
290
291     /**
47124c 292      * Send the request output to the client.
T 293      * This will either parse a skin tempalte or send an AJAX response
294      *
295      * @param string  Template name
296      * @param boolean True if script should terminate (default)
297      */
298     public function send($templ = null, $exit = true)
299     {
300         if ($templ != 'iframe') {
a366a3 301             // prevent from endless loops
f78dab 302             if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
10eedb 303                 raise_error(array('code' => 505, 'type' => 'php',
030db5 304                   'file' => __FILE__, 'line' => __LINE__,
T 305                   'message' => 'Recursion alert: ignoring output->send()'), true, false);
a366a3 306                 return;
T 307             }
47124c 308             $this->parse($templ, false);
T 309         }
310         else {
311             $this->framed = $templ == 'iframe' ? true : $this->framed;
312             $this->write();
313         }
314
c6514e 315         // set output asap
T 316         ob_flush();
317         flush();
353657 318         
c6514e 319         if ($exit) {
47124c 320             exit;
T 321         }
322     }
323
324     /**
325      * Process template and write to stdOut
326      *
327      * @param string HTML template
328      * @see rcube_html_page::write()
329      * @override
330      */
331     public function write($template = '')
332     {
333         // unlock interface after iframe load
334         if ($this->framed) {
335             array_unshift($this->js_commands, array('set_busy', false));
336         }
337         // write all env variables to client
338         $js = $this->framed ? "if(window.parent) {\n" : '';
339         $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
340         $this->add_script($js, 'head_top');
549933 341         
T 342         // make sure all <form> tags have a valid request token
343         $template = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $template);
efbe9e 344         $this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
47124c 345
T 346         // call super method
347         parent::write($template, $this->config['skin_path']);
348     }
349
350     /**
351      * Parse a specific skin template and deliver to stdout
352      *
353      * Either returns nothing, or exists hard (exit();)
354      *
355      * @param  string  Template name
356      * @param  boolean Exit script
357      * @return void
358      * @link   http://php.net/manual/en/function.exit.php
359      */
360     private function parse($name = 'main', $exit = true)
361     {
362         $skin_path = $this->config['skin_path'];
e7008c 363         $plugin = false;
969f6b 364         
e7008c 365         $temp = explode(".", $name, 2);
T 366         if (count($temp) > 1) {
367             $plugin = $temp[0];
368             $name = $temp[1];
20d50d 369             $skin_dir = $plugin . '/skins/' . $this->config['skin'];
7dbe2f 370             $skin_path = $this->app->plugins->dir . $skin_dir;
20d50d 371             if (!is_dir($skin_path)) {  // fallback to default skin
T 372                 $skin_dir = $plugin . '/skins/default';
7dbe2f 373                 $skin_path = $this->app->plugins->dir . $skin_dir;
20d50d 374             }
e7008c 375         }
T 376         
377         $path = "$skin_path/templates/$name.html";
47124c 378
ff73e0 379         // read template file
7f22f2 380         if (($templ = @file_get_contents($path)) === false) {
f92b2f 381             raise_error(array(
47124c 382                 'code' => 501,
T 383                 'type' => 'php',
384                 'line' => __LINE__,
385                 'file' => __FILE__,
7f22f2 386                 'message' => 'Error loading template for '.$name
47124c 387                 ), true, true);
T 388             return false;
389         }
e7008c 390         
66f68e 391         // replace all path references to plugins/... with the configured plugins dir
T 392         // and /this/ to the current plugin skin directory
e7008c 393         if ($plugin) {
20d50d 394             $templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
e7008c 395         }
47124c 396
T 397         // parse for specialtags
398         $output = $this->parse_conditions($templ);
399         $output = $this->parse_xml($output);
742d61 400         
T 401         // trigger generic hook where plugins can put additional content to the page
402         $hook = $this->app->plugins->exec_hook("render_page", array('template' => $name, 'content' => $output));
47124c 403
T 404         // add debug console
405         if ($this->config['debug_level'] & 8) {
09839a 406             $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;">
fe7d78 407                 <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
A 408                 <textarea name="console" id="dbgconsole" rows="20" cols="40" wrap="off" style="display:none;width:400px;border:none;font-size:10px" spellcheck="false"></textarea></div>'
47124c 409             );
T 410         }
742d61 411         
T 412         $output = $this->parse_with_globals($hook['content']);
66f68e 413         $this->write(trim($output));
47124c 414         if ($exit) {
T 415             exit;
416         }
417     }
418
419
420     /**
421      * Return executable javascript code for all registered commands
422      *
423      * @return string $out
424      */
425     private function get_js_commands()
426     {
427         $out = '';
428         if (!$this->framed && !empty($this->js_env)) {
2717f9 429             $out .= JS_OBJECT_NAME . '.set_env('.json_serialize($this->js_env).");\n";
47124c 430         }
T 431         foreach ($this->js_commands as $i => $args) {
432             $method = array_shift($args);
433             foreach ($args as $i => $arg) {
2717f9 434                 $args[$i] = json_serialize($arg);
47124c 435             }
T 436             $parent = $this->framed || preg_match('/^parent\./', $method);
437             $out .= sprintf(
438                 "%s.%s(%s);\n",
cc97ea 439                 ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME,
T 440                 preg_replace('/^parent\./', '', $method),
441                 implode(',', $args)
47124c 442             );
T 443         }
f645ce 444         
47124c 445         return $out;
T 446     }
447
448     /**
449      * Make URLs starting with a slash point to skin directory
450      *
451      * @param  string Input string
452      * @return string
453      */
454     public function abs_url($str)
455     {
2aa2b3 456         if ($str[0] == '/')
A 457             return $this->config['skin_path'] . $str;
458         else
459             return $str;
47124c 460     }
T 461
462
463     /*****  Template parsing methods  *****/
464
465     /**
466      * Replace all strings ($varname)
467      * with the content of the according global variable.
468      */
469     private function parse_with_globals($input)
470     {
533e86 471         $GLOBALS['__version'] = Q(RCMAIL_VERSION);
197601 472         $GLOBALS['__comm_path'] = Q($this->app->comm_path);
5740c0 473         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
A 474         array($this, 'globals_callback'), $input);
475     }
476
477     /**
478      * Callback funtion for preg_replace_callback() in parse_with_globals()
479      */
480     private function globals_callback($matches)
481     {
482         return $GLOBALS[$matches[1]];
47124c 483     }
T 484
485     /**
486      * Public wrapper to dipp into template parsing.
487      *
488      * @param  string $input
489      * @return string
490      * @uses   rcube_template::parse_xml()
491      * @since  0.1-rc1
492      */
493     public function just_parse($input)
494     {
495         return $this->parse_xml($input);
496     }
497
498     /**
499      * Parse for conditional tags
500      *
501      * @param  string $input
502      * @return string
503      */
504     private function parse_conditions($input)
505     {
506         $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
507         if ($matches && count($matches) == 4) {
508             if (preg_match('/^(else|endif)$/i', $matches[1])) {
509                 return $matches[0] . $this->parse_conditions($matches[3]);
510             }
511             $attrib = parse_attrib_string($matches[2]);
512             if (isset($attrib['condition'])) {
513                 $condmet = $this->check_condition($attrib['condition']);
514                 $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
515                 if ($condmet) {
516                     $result = $submatches[0];
517                     $result.= ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>/Uis', '', $submatches[3], 1) : $submatches[3]);
518                 }
519                 else {
520                     $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
521                 }
522                 return $matches[0] . $this->parse_conditions($result);
523             }
f92b2f 524             raise_error(array(
47124c 525                 'code' => 500,
T 526                 'type' => 'php',
527                 'line' => __LINE__,
528                 'file' => __FILE__,
529                 'message' => "Unable to parse conditional tag " . $matches[2]
530             ), true, false);
531         }
532         return $input;
533     }
534
535
536     /**
537      * Determines if a given condition is met
538      *
539      * @todo   Get rid off eval() once I understand what this does.
540      * @todo   Extend this to allow real conditions, not just "set"
541      * @param  string Condition statement
8e1d4a 542      * @return boolean True if condition is met, False if not
47124c 543      */
T 544     private function check_condition($condition)
545     {
549933 546         return eval("return (".$this->parse_expression($condition).");");
T 547     }
548     
549     
550     /**
2eb794 551      * Inserts hidden field with CSRF-prevention-token into POST forms
549933 552      */
T 553     private function alter_form_tag($matches)
554     {
555         $out = $matches[0];
556         $attrib  = parse_attrib_string($matches[1]);
557       
558         if (strtolower($attrib['method']) == 'post') {
559             $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
560             $out .= "\n" . $hidden->show();
561         }
562       
563         return $out;
8e1d4a 564     }
A 565
566
567     /**
568      * Parses expression and replaces variables
569      *
570      * @param  string Expression statement
030db5 571      * @return string Expression value
8e1d4a 572      */
A 573     private function parse_expression($expression)
574     {
575         return preg_replace(
47124c 576             array(
T 577                 '/session:([a-z0-9_]+)/i',
f645ce 578                 '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
40353f 579                 '/env:([a-z0-9_]+)/i',
A 580                 '/request:([a-z0-9_]+)/i',
581                 '/cookie:([a-z0-9_]+)/i',
a17fe6 582                 '/browser:([a-z0-9_]+)/i'
47124c 583             ),
T 584             array(
585                 "\$_SESSION['\\1']",
f645ce 586                 "\$this->app->config->get('\\1',get_boolean('\\3'))",
47124c 587                 "\$this->env['\\1']",
ea373f 588                 "get_input_value('\\1', RCUBE_INPUT_GPC)",
a17fe6 589                 "\$_COOKIE['\\1']",
A 590                 "\$this->browser->{'\\1'}"
47124c 591             ),
8e1d4a 592             $expression);
47124c 593     }
T 594
595
596     /**
597      * Search for special tags in input and replace them
598      * with the appropriate content
599      *
600      * @param  string Input string to parse
601      * @return string Altered input string
06655a 602      * @todo   Use DOM-parser to traverse template HTML
47124c 603      * @todo   Maybe a cache.
T 604      */
605     private function parse_xml($input)
606     {
cc97ea 607         return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input);
6f488b 608     }
A 609
610
611     /**
cc97ea 612      * Callback function for parsing an xml command tag
T 613      * and turn it into real html content
47124c 614      *
cc97ea 615      * @param  array Matches array of preg_replace_callback
47124c 616      * @return string Tag/Object content
T 617      */
cc97ea 618     private function xml_command($matches)
47124c 619     {
cc97ea 620         $command = strtolower($matches[1]);
T 621         $attrib  = parse_attrib_string($matches[2]);
47124c 622
T 623         // empty output if required condition is not met
624         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
625             return '';
626         }
627
628         // execute command
629         switch ($command) {
630             // return a button
631             case 'button':
875a48 632                 if ($attrib['name'] || $attrib['command']) {
47124c 633                     return $this->button($attrib);
T 634                 }
635                 break;
636
637             // show a label
638             case 'label':
639                 if ($attrib['name'] || $attrib['command']) {
640                     return Q(rcube_label($attrib + array('vars' => array('product' => $this->config['product_name']))));
641                 }
642                 break;
643
644             // include a file
645             case 'include':
646                 $path = realpath($this->config['skin_path'].$attrib['file']);
ff73e0 647                 if (is_readable($path)) {
47124c 648                     if ($this->config['skin_include_php']) {
T 649                         $incl = $this->include_php($path);
650                     }
ff73e0 651                     else {
cc97ea 652                       $incl = file_get_contents($path);
T 653                     }
b4f7c6 654                     $incl = $this->parse_conditions($incl);
47124c 655                     return $this->parse_xml($incl);
T 656                 }
657                 break;
658
659             case 'plugin.include':
cc97ea 660                 $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
T 661                 return $hook['content'];
662                 break;
663             
664             // define a container block
665             case 'container':
666                 if ($attrib['name'] && $attrib['id']) {
667                     $this->command('gui_container', $attrib['name'], $attrib['id']);
668                     // let plugins insert some content here
669                     $hook = $this->app->plugins->exec_hook("template_container", $attrib);
670                     return $hook['content'];
47124c 671                 }
T 672                 break;
673
674             // return code for a specific application object
675             case 'object':
676                 $object = strtolower($attrib['name']);
cc97ea 677                 $content = '';
47124c 678
T 679                 // we are calling a class/method
680                 if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
681                     if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
682                     (is_string($handler[0]) && class_exists($handler[0])))
cc97ea 683                     $content = call_user_func($handler, $attrib);
47124c 684                 }
cc97ea 685                 // execute object handler function
47124c 686                 else if (function_exists($handler)) {
cc97ea 687                     $content = call_user_func($handler, $attrib);
47124c 688                 }
cc97ea 689                 else if ($object == 'productname') {
e019f2 690                     $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail';
cc97ea 691                     $content = Q($name);
47124c 692                 }
cc97ea 693                 else if ($object == 'version') {
c8fb2b 694                     $ver = (string)RCMAIL_VERSION;
T 695                     if (is_file(INSTALL_PATH . '.svn/entries')) {
696                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
697                           $ver .= ' [SVN r'.$regs[1].']';
698                     }
cc97ea 699                     $content = Q($ver);
47124c 700                 }
cc97ea 701                 else if ($object == 'steptitle') {
T 702                   $content = Q($this->get_pagetitle());
f645ce 703                 }
cc97ea 704                 else if ($object == 'pagetitle') {
47124c 705                     $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
f645ce 706                     $title .= $this->get_pagetitle();
cc97ea 707                     $content = Q($title);
47124c 708                 }
cc97ea 709                 
T 710                 // exec plugin hooks for this template object
711                 $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
712                 return $hook['content'];
8e1d4a 713
A 714             // return code for a specified eval expression
715             case 'exp':
cc97ea 716                 $value = $this->parse_expression($attrib['expression']);
8e1d4a 717                 return eval("return Q($value);");
47124c 718             
T 719             // return variable
720             case 'var':
721                 $var = explode(':', $attrib['name']);
722                 $name = $var[1];
723                 $value = '';
724
725                 switch ($var[0]) {
726                     case 'env':
727                         $value = $this->env[$name];
728                         break;
729                     case 'config':
730                         $value = $this->config[$name];
731                         if (is_array($value) && $value[$_SESSION['imap_host']]) {
732                             $value = $value[$_SESSION['imap_host']];
733                         }
734                         break;
735                     case 'request':
736                         $value = get_input_value($name, RCUBE_INPUT_GPC);
737                         break;
738                     case 'session':
739                         $value = $_SESSION[$name];
740                         break;
d4273b 741                     case 'cookie':
A 742                         $value = htmlspecialchars($_COOKIE[$name]);
743                         break;
a17fe6 744                     case 'browser':
A 745                         $value = $this->browser->{$name};
746                         break;
47124c 747                 }
T 748
749                 if (is_array($value)) {
750                     $value = implode(', ', $value);
751                 }
752
753                 return Q($value);
754                 break;
755         }
756         return '';
757     }
758
759     /**
760      * Include a specific file and return it's contents
761      *
762      * @param string File path
763      * @return string Contents of the processed file
764      */
765     private function include_php($file)
766     {
767         ob_start();
768         include $file;
769         $out = ob_get_contents();
770         ob_end_clean();
771
772         return $out;
773     }
774
775     /**
776      * Create and register a button
777      *
778      * @param  array Named button attributes
779      * @return string HTML button
780      * @todo   Remove all inline JS calls and use jQuery instead.
781      * @todo   Remove all sprintf()'s - they are pretty, but also slow.
782      */
ed132e 783     public function button($attrib)
47124c 784     {
T 785         static $s_button_count = 100;
786
787         // these commands can be called directly via url
cc97ea 788         $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
47124c 789
T 790         if (!($attrib['command'] || $attrib['name'])) {
791             return '';
792         }
ae0c82 793
47124c 794         // try to find out the button type
T 795         if ($attrib['type']) {
796             $attrib['type'] = strtolower($attrib['type']);
797         }
798         else {
799             $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
800         }
e9b5a6 801
47124c 802         $command = $attrib['command'];
T 803
e9b5a6 804         if ($attrib['task'])
T 805           $command = $attrib['task'] . '.' . $command;
af3cf8 806           
T 807         if (!$attrib['image']) {
808             $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
809         }
47124c 810
T 811         if (!$attrib['id']) {
812             $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
813         }
814         // get localized text for labels and titles
815         if ($attrib['title']) {
1c932d 816             $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain']));
47124c 817         }
T 818         if ($attrib['label']) {
1c932d 819             $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain']));
47124c 820         }
T 821         if ($attrib['alt']) {
1c932d 822             $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
47124c 823         }
9b2ccd 824
47124c 825         // set title to alt attribute for IE browsers
c9e9fe 826         if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
A 827             $attrib['title'] = $attrib['alt'];
47124c 828         }
T 829
830         // add empty alt attribute for XHTML compatibility
831         if (!isset($attrib['alt'])) {
832             $attrib['alt'] = '';
833         }
834
835         // register button in the system
836         if ($attrib['command']) {
837             $this->add_script(sprintf(
838                 "%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
839                 JS_OBJECT_NAME,
840                 $command,
841                 $attrib['id'],
842                 $attrib['type'],
ae0c82 843                 $attrib['imageact'] ? $this->abs_url($attrib['imageact']) : $attrib['classact'],
A 844                 $attrib['imagesel'] ? $this->abs_url($attrib['imagesel']) : $attrib['classsel'],
845                 $attrib['imageover'] ? $this->abs_url($attrib['imageover']) : ''
47124c 846             ));
T 847
848             // make valid href to specific buttons
197601 849             if (in_array($attrib['command'], rcmail::$main_tasks)) {
203ee4 850                 $attrib['href'] = rcmail_url(null, null, $attrib['command']);
47124c 851             }
e9b5a6 852             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
T 853                 $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']);
854             }
47124c 855             else if (in_array($attrib['command'], $a_static_commands)) {
203ee4 856                 $attrib['href'] = rcmail_url($attrib['command']);
a25d39 857             }
29f977 858             else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
T 859               $attrib['href'] = $this->env['permaurl'];
860             }
47124c 861         }
T 862
863         // overwrite attributes
864         if (!$attrib['href']) {
865             $attrib['href'] = '#';
866         }
e9b5a6 867         if ($attrib['task']) {
T 868             if ($attrib['classact'])
869                 $attrib['class'] = $attrib['classact'];
870         }
871         else if ($command && !$attrib['onclick']) {
47124c 872             $attrib['onclick'] = sprintf(
T 873                 "return %s.command('%s','%s',this)",
874                 JS_OBJECT_NAME,
875                 $command,
876                 $attrib['prop']
877             );
878         }
879
880         $out = '';
881
882         // generate image tag
883         if ($attrib['type']=='image') {
884             $attrib_str = html::attrib_string(
885                 $attrib,
886                 array(
c9e9fe 887                     'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
A 888                     'vspace', 'align', 'alt', 'tabindex', 'title'
47124c 889                 )
T 890             );
ae0c82 891             $btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
47124c 892             if ($attrib['label']) {
T 893                 $btn_content .= ' '.$attrib['label'];
894             }
c9e9fe 895             $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
47124c 896         }
T 897         else if ($attrib['type']=='link') {
356a67 898             $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
203ee4 899             $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
47124c 900         }
T 901         else if ($attrib['type']=='input') {
902             $attrib['type'] = 'button';
903
904             if ($attrib['label']) {
905                 $attrib['value'] = $attrib['label'];
906             }
907
908             $attrib_str = html::attrib_string(
909                 $attrib,
910                 array(
c9e9fe 911                     'type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex'
47124c 912                 )
T 913             );
914             $out = sprintf('<input%s disabled="disabled" />', $attrib_str);
915         }
916
917         // generate html code for button
918         if ($btn_content) {
919             $attrib_str = html::attrib_string($attrib, $link_attrib);
920             $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
921         }
922
923         return $out;
924     }
925
926
927     /*  ************* common functions delivering gui objects **************  */
928
929
930     /**
197601 931      * Create a form tag with the necessary hidden fields
T 932      *
933      * @param array Named tag parameters
934      * @return string HTML code for the form
935      */
936     public function form_tag($attrib, $content = null)
937     {
57f0c8 938       if ($this->framed || !empty($_REQUEST['_framed'])) {
197601 939         $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
T 940         $hidden = $hiddenfield->show();
941       }
942       
943       if (!$content)
944         $attrib['noclose'] = true;
945       
946       return html::tag('form',
947         $attrib + array('action' => "./", 'method' => "get"),
57f0c8 948         $hidden . $content,
T 949         array('id','class','style','name','method','action','enctype','onsubmit'));
950     }
951     
952     
953     /**
954      * Build a form tag with a unique request token
955      *
956      * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
957      * @param string Form content
958      * @return string HTML code for the form
959      */
747797 960     public function request_form($attrib, $content = '')
57f0c8 961     {
T 962         $hidden = new html_hiddenfield();
963         if ($attrib['task']) {
964             $hidden->add(array('name' => '_task', 'value' => $attrib['task']));
965         }
966         if ($attrib['action']) {
967             $hidden->add(array('name' => '_action', 'value' => $attrib['action']));
968         }
969       
970         unset($attrib['task'], $attrib['request']);
971         $attrib['action'] = './';
972       
973         // we already have a <form> tag
974         if ($attrib['form'])
975             return $hidden->show() . $content;
976         else
977             return $this->form_tag($attrib, $hidden->show() . $content);
197601 978     }
T 979
980
981     /**
47124c 982      * GUI object 'username'
T 983      * Showing IMAP username of the current session
984      *
985      * @param array Named tag parameters (currently not used)
986      * @return string HTML code for the gui object
987      */
197601 988     public function current_username($attrib)
47124c 989     {
T 990         static $username;
991
992         // alread fetched
993         if (!empty($username)) {
994             return $username;
995         }
996
929a50 997         // get e-mail address from default identity
7e9cec 998         if ($sql_arr = $this->app->user->get_identity()) {
T 999             $username = $sql_arr['email'];
47124c 1000         }
T 1001         else {
7e9cec 1002             $username = $this->app->user->get_username();
47124c 1003         }
T 1004
1005         return $username;
1006     }
1007
1008
1009     /**
1010      * GUI object 'loginform'
1011      * Returns code for the webmail login form
1012      *
1013      * @param array Named parameters
1014      * @return string HTML code for the gui object
1015      */
1016     private function login_form($attrib)
1017     {
197601 1018         $default_host = $this->config['default_host'];
47124c 1019
T 1020         $_SESSION['temp'] = true;
cc97ea 1021         
T 1022         // save original url
1023         $url = get_input_value('_url', RCUBE_INPUT_POST);
3a2b27 1024         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
cc97ea 1025             $url = $_SERVER['QUERY_STRING'];
47124c 1026
6a486e 1027         $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser') + $attrib);
A 1028         $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd') + $attrib);
47124c 1029         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
c8ae24 1030         $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
cc97ea 1031         $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
47124c 1032         $input_host   = null;
T 1033
f3e101 1034         if (is_array($default_host) && count($default_host) > 1) {
47124c 1035             $input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
T 1036
1037             foreach ($default_host as $key => $value) {
1038                 if (!is_array($value)) {
1039                     $input_host->add($value, (is_numeric($key) ? $value : $key));
1040                 }
1041                 else {
1042                     $input_host = null;
1043                     break;
1044                 }
1045             }
f3e101 1046         }
A 1047         else if (is_array($default_host) && ($host = array_pop($default_host))) {
1048             $hide_host = true;
1049             $input_host = new html_hiddenfield(array(
1050                 'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);
47124c 1051         }
e3e597 1052         else if (empty($default_host)) {
6a486e 1053             $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost') + $attrib);
47124c 1054         }
T 1055
1056         $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form';
1057         $this->add_gui_object('loginform', $form_name);
1058
1059         // create HTML table with two cols
1060         $table = new html_table(array('cols' => 2));
1061
1062         $table->add('title', html::label('rcmloginuser', Q(rcube_label('username'))));
e3e597 1063         $table->add(null, $input_user->show(get_input_value('_user', RCUBE_INPUT_POST)));
47124c 1064
T 1065         $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password'))));
1066         $table->add(null, $input_pass->show());
1067
1068         // add host selection row
f3e101 1069         if (is_object($input_host) && !$hide_host) {
47124c 1070             $table->add('title', html::label('rcmloginhost', Q(rcube_label('server'))));
e3e597 1071             $table->add(null, $input_host->show(get_input_value('_host', RCUBE_INPUT_POST)));
47124c 1072         }
T 1073
197601 1074         $out = $input_action->show();
c8ae24 1075         $out .= $input_tzone->show();
cc97ea 1076         $out .= $input_url->show();
47124c 1077         $out .= $table->show();
f3e101 1078         
A 1079         if ($hide_host) {
1080             $out .= $input_host->show();
1081         }
47124c 1082
T 1083         // surround html output with a form tag
1084         if (empty($attrib['form'])) {
f3e101 1085             $out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
47124c 1086         }
T 1087
1088         return $out;
1089     }
1090
1091
1092     /**
8e211a 1093      * GUI object 'preloader'
A 1094      * Loads javascript code for images preloading
1095      *
1096      * @param array Named parameters
1097      * @return void
1098      */
1099     private function preloader($attrib)
1100     {
1101         $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
1102         $images = array_map(array($this, 'abs_url'), $images);
1103
1104         if (empty($images) || $this->app->task == 'logout')
1105             return;
1106
1107         $this->add_script('$(document).ready(function(){
1108             var images = ' . json_serialize($images) .';
1109             for (var i=0; i<images.length; i++) {
1110                 img = new Image();
1111                 img.src = images[i];
1112             }});', 'foot');
1113     }
1114
1115
1116     /**
47124c 1117      * GUI object 'searchform'
T 1118      * Returns code for search function
1119      *
1120      * @param array Named parameters
1121      * @return string HTML code for the gui object
1122      */
1123     private function search_form($attrib)
1124     {
1125         // add some labels to client
1126         $this->add_label('searching');
1127
1128         $attrib['name'] = '_q';
1129
1130         if (empty($attrib['id'])) {
1131             $attrib['id'] = 'rcmqsearchbox';
1132         }
a3f149 1133         if ($attrib['type'] == 'search' && !$this->browser->khtml) {
2eb794 1134             unset($attrib['type'], $attrib['results']);
a3f149 1135         }
T 1136         
47124c 1137         $input_q = new html_inputfield($attrib);
T 1138         $out = $input_q->show();
1139
1140         $this->add_gui_object('qsearchbox', $attrib['id']);
1141
1142         // add form tag around text field
1143         if (empty($attrib['form'])) {
197601 1144             $out = $this->form_tag(array(
T 1145                 'name' => "rcmqsearchform",
1146                 'onsubmit' => JS_OBJECT_NAME . ".command('search');return false;",
1147                 'style' => "display:inline"),
2eb794 1148                 $out);
47124c 1149         }
T 1150
1151         return $out;
1152     }
1153
1154
1155     /**
1156      * Builder for GUI object 'message'
1157      *
1158      * @param array Named tag parameters
1159      * @return string HTML code for the gui object
1160      */
1161     private function message_container($attrib)
1162     {
1163         if (isset($attrib['id']) === false) {
1164             $attrib['id'] = 'rcmMessageContainer';
1165         }
1166
1167         $this->add_gui_object('message', $attrib['id']);
1168         return html::div($attrib, "");
1169     }
1170
1171
1172     /**
1173      * GUI object 'charsetselector'
1174      *
1175      * @param array Named parameters for the select tag
1176      * @return string HTML code for the gui object
1177      */
e55ab0 1178     function charset_selector($attrib)
47124c 1179     {
T 1180         // pass the following attributes to the form class
1181         $field_attrib = array('name' => '_charset');
1182         foreach ($attrib as $attr => $value) {
e55ab0 1183             if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
47124c 1184                 $field_attrib[$attr] = $value;
T 1185             }
1186         }
e55ab0 1187
47124c 1188         $charsets = array(
e55ab0 1189             'UTF-8'        => 'UTF-8 ('.rcube_label('unicode').')',
A 1190             'US-ASCII'     => 'ASCII ('.rcube_label('english').')',
1191             'ISO-8859-1'   => 'ISO-8859-1 ('.rcube_label('westerneuropean').')',
1192             'ISO-8859-2'   => 'ISO-8895-2 ('.rcube_label('easterneuropean').')',
1193             'ISO-8859-4'   => 'ISO-8895-4 ('.rcube_label('baltic').')',
1194             'ISO-8859-5'   => 'ISO-8859-5 ('.rcube_label('cyrillic').')',
1195             'ISO-8859-6'   => 'ISO-8859-6 ('.rcube_label('arabic').')',
1196             'ISO-8859-7'   => 'ISO-8859-7 ('.rcube_label('greek').')',
1197             'ISO-8859-8'   => 'ISO-8859-8 ('.rcube_label('hebrew').')',
1198             'ISO-8859-9'   => 'ISO-8859-9 ('.rcube_label('turkish').')',
1199             'ISO-8859-10'   => 'ISO-8859-10 ('.rcube_label('nordic').')',
1200             'ISO-8859-11'   => 'ISO-8859-11 ('.rcube_label('thai').')',
1201             'ISO-8859-13'   => 'ISO-8859-13 ('.rcube_label('baltic').')',
1202             'ISO-8859-14'   => 'ISO-8859-14 ('.rcube_label('celtic').')',
1203             'ISO-8859-15'   => 'ISO-8859-15 ('.rcube_label('westerneuropean').')',
1204             'ISO-8859-16'   => 'ISO-8859-16 ('.rcube_label('southeasterneuropean').')',
1205             'WINDOWS-1250' => 'Windows-1250 ('.rcube_label('easterneuropean').')',
1206             'WINDOWS-1251' => 'Windows-1251 ('.rcube_label('cyrillic').')',
1207             'WINDOWS-1252' => 'Windows-1252 ('.rcube_label('westerneuropean').')',
1208             'WINDOWS-1253' => 'Windows-1253 ('.rcube_label('greek').')',
1209             'WINDOWS-1254' => 'Windows-1254 ('.rcube_label('turkish').')',
1210             'WINDOWS-1255' => 'Windows-1255 ('.rcube_label('hebrew').')',
1211             'WINDOWS-1256' => 'Windows-1256 ('.rcube_label('arabic').')',
1212             'WINDOWS-1257' => 'Windows-1257 ('.rcube_label('baltic').')',
1213             'WINDOWS-1258' => 'Windows-1258 ('.rcube_label('vietnamese').')',
1214             'ISO-2022-JP'  => 'ISO-2022-JP ('.rcube_label('japanese').')',
1215             'ISO-2022-KR'  => 'ISO-2022-KR ('.rcube_label('korean').')',
1216             'ISO-2022-CN'  => 'ISO-2022-CN ('.rcube_label('chinese').')',
1217             'EUC-JP'       => 'EUC-JP ('.rcube_label('japanese').')',
1218             'EUC-KR'       => 'EUC-KR ('.rcube_label('korean').')',
1219             'EUC-CN'       => 'EUC-CN ('.rcube_label('chinese').')',
1220             'BIG5'         => 'BIG5 ('.rcube_label('chinese').')',
1221             'GB2312'       => 'GB2312 ('.rcube_label('chinese').')',
1222         );
47124c 1223
e55ab0 1224         if (!empty($_POST['_charset']))
2eb794 1225             $set = $_POST['_charset'];
A 1226         else if (!empty($attrib['selected']))
1227             $set = $attrib['selected'];
1228         else
1229             $set = $this->get_charset();
47124c 1230
2eb794 1231         $set = strtoupper($set);
A 1232         if (!isset($charsets[$set]))
1233             $charsets[$set] = $set;
e55ab0 1234
A 1235         $select = new html_select($field_attrib);
1236         $select->add(array_values($charsets), array_keys($charsets));
1237
1238         return $select->show($set);
47124c 1239     }
T 1240
1241 }  // end class rcube_template
1242
1243