alecpl
2010-10-13 ad334a12579f21e4e04a47e257b9d0ac71d50b98
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();
ad334a 60
197601 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         }
ad334a 132
f645ce 133         return $title;
T 134     }
135
136
e58df3 137     /**
A 138      * Set skin
139      */
140     public function set_skin($skin)
141     {
62c791 142         $valid = false;
ad334a 143
62c791 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;
ad334a 155
62c791 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];
ad334a 229
cc97ea 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();
ad334a 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
ad334a 334         $unlock = preg_replace('/[^a-z0-9]/i', '', $_GET['_unlock']);
47124c 335         if ($this->framed) {
ad334a 336             array_unshift($this->js_commands, array('set_busy', false, null, $unlock));
A 337         }
338         else if ($unlock) {
339             array_unshift($this->js_commands, array('hide_message', $unlock));
47124c 340         }
T 341         // write all env variables to client
342         $js = $this->framed ? "if(window.parent) {\n" : '';
343         $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
344         $this->add_script($js, 'head_top');
ad334a 345
549933 346         // make sure all <form> tags have a valid request token
T 347         $template = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $template);
efbe9e 348         $this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
47124c 349
T 350         // call super method
351         parent::write($template, $this->config['skin_path']);
352     }
353
354     /**
355      * Parse a specific skin template and deliver to stdout
356      *
357      * Either returns nothing, or exists hard (exit();)
358      *
359      * @param  string  Template name
360      * @param  boolean Exit script
361      * @return void
362      * @link   http://php.net/manual/en/function.exit.php
363      */
364     private function parse($name = 'main', $exit = true)
365     {
366         $skin_path = $this->config['skin_path'];
e7008c 367         $plugin = false;
ad334a 368
e7008c 369         $temp = explode(".", $name, 2);
T 370         if (count($temp) > 1) {
371             $plugin = $temp[0];
372             $name = $temp[1];
20d50d 373             $skin_dir = $plugin . '/skins/' . $this->config['skin'];
7dbe2f 374             $skin_path = $this->app->plugins->dir . $skin_dir;
20d50d 375             if (!is_dir($skin_path)) {  // fallback to default skin
T 376                 $skin_dir = $plugin . '/skins/default';
7dbe2f 377                 $skin_path = $this->app->plugins->dir . $skin_dir;
20d50d 378             }
e7008c 379         }
ad334a 380
e7008c 381         $path = "$skin_path/templates/$name.html";
47124c 382
ff73e0 383         // read template file
7f22f2 384         if (($templ = @file_get_contents($path)) === false) {
f92b2f 385             raise_error(array(
47124c 386                 'code' => 501,
T 387                 'type' => 'php',
388                 'line' => __LINE__,
389                 'file' => __FILE__,
7f22f2 390                 'message' => 'Error loading template for '.$name
47124c 391                 ), true, true);
T 392             return false;
393         }
ad334a 394
66f68e 395         // replace all path references to plugins/... with the configured plugins dir
T 396         // and /this/ to the current plugin skin directory
e7008c 397         if ($plugin) {
20d50d 398             $templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
e7008c 399         }
47124c 400
T 401         // parse for specialtags
402         $output = $this->parse_conditions($templ);
403         $output = $this->parse_xml($output);
ad334a 404
742d61 405         // trigger generic hook where plugins can put additional content to the page
T 406         $hook = $this->app->plugins->exec_hook("render_page", array('template' => $name, 'content' => $output));
47124c 407
T 408         // add debug console
409         if ($this->config['debug_level'] & 8) {
09839a 410             $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;">
fe7d78 411                 <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
A 412                 <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 413             );
T 414         }
ad334a 415
742d61 416         $output = $this->parse_with_globals($hook['content']);
66f68e 417         $this->write(trim($output));
47124c 418         if ($exit) {
T 419             exit;
420         }
421     }
422
423
424     /**
425      * Return executable javascript code for all registered commands
426      *
427      * @return string $out
428      */
429     private function get_js_commands()
430     {
431         $out = '';
432         if (!$this->framed && !empty($this->js_env)) {
2717f9 433             $out .= JS_OBJECT_NAME . '.set_env('.json_serialize($this->js_env).");\n";
47124c 434         }
T 435         foreach ($this->js_commands as $i => $args) {
436             $method = array_shift($args);
437             foreach ($args as $i => $arg) {
2717f9 438                 $args[$i] = json_serialize($arg);
47124c 439             }
T 440             $parent = $this->framed || preg_match('/^parent\./', $method);
441             $out .= sprintf(
442                 "%s.%s(%s);\n",
cc97ea 443                 ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME,
T 444                 preg_replace('/^parent\./', '', $method),
445                 implode(',', $args)
47124c 446             );
T 447         }
ad334a 448
47124c 449         return $out;
T 450     }
451
452     /**
453      * Make URLs starting with a slash point to skin directory
454      *
455      * @param  string Input string
456      * @return string
457      */
458     public function abs_url($str)
459     {
2aa2b3 460         if ($str[0] == '/')
A 461             return $this->config['skin_path'] . $str;
462         else
463             return $str;
47124c 464     }
T 465
466
467     /*****  Template parsing methods  *****/
468
469     /**
470      * Replace all strings ($varname)
471      * with the content of the according global variable.
472      */
473     private function parse_with_globals($input)
474     {
533e86 475         $GLOBALS['__version'] = Q(RCMAIL_VERSION);
197601 476         $GLOBALS['__comm_path'] = Q($this->app->comm_path);
5740c0 477         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
A 478         array($this, 'globals_callback'), $input);
479     }
480
481     /**
482      * Callback funtion for preg_replace_callback() in parse_with_globals()
483      */
484     private function globals_callback($matches)
485     {
486         return $GLOBALS[$matches[1]];
47124c 487     }
T 488
489     /**
490      * Public wrapper to dipp into template parsing.
491      *
492      * @param  string $input
493      * @return string
494      * @uses   rcube_template::parse_xml()
495      * @since  0.1-rc1
496      */
497     public function just_parse($input)
498     {
499         return $this->parse_xml($input);
500     }
501
502     /**
503      * Parse for conditional tags
504      *
505      * @param  string $input
506      * @return string
507      */
508     private function parse_conditions($input)
509     {
510         $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
511         if ($matches && count($matches) == 4) {
512             if (preg_match('/^(else|endif)$/i', $matches[1])) {
513                 return $matches[0] . $this->parse_conditions($matches[3]);
514             }
515             $attrib = parse_attrib_string($matches[2]);
516             if (isset($attrib['condition'])) {
517                 $condmet = $this->check_condition($attrib['condition']);
518                 $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
519                 if ($condmet) {
520                     $result = $submatches[0];
521                     $result.= ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>/Uis', '', $submatches[3], 1) : $submatches[3]);
522                 }
523                 else {
524                     $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
525                 }
526                 return $matches[0] . $this->parse_conditions($result);
527             }
f92b2f 528             raise_error(array(
47124c 529                 'code' => 500,
T 530                 'type' => 'php',
531                 'line' => __LINE__,
532                 'file' => __FILE__,
533                 'message' => "Unable to parse conditional tag " . $matches[2]
534             ), true, false);
535         }
536         return $input;
537     }
538
539
540     /**
541      * Determines if a given condition is met
542      *
543      * @todo   Get rid off eval() once I understand what this does.
544      * @todo   Extend this to allow real conditions, not just "set"
545      * @param  string Condition statement
8e1d4a 546      * @return boolean True if condition is met, False if not
47124c 547      */
T 548     private function check_condition($condition)
549     {
549933 550         return eval("return (".$this->parse_expression($condition).");");
T 551     }
ad334a 552
A 553
549933 554     /**
2eb794 555      * Inserts hidden field with CSRF-prevention-token into POST forms
549933 556      */
T 557     private function alter_form_tag($matches)
558     {
559         $out = $matches[0];
560         $attrib  = parse_attrib_string($matches[1]);
ad334a 561
549933 562         if (strtolower($attrib['method']) == 'post') {
T 563             $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
564             $out .= "\n" . $hidden->show();
565         }
ad334a 566
549933 567         return $out;
8e1d4a 568     }
A 569
570
571     /**
572      * Parses expression and replaces variables
573      *
574      * @param  string Expression statement
030db5 575      * @return string Expression value
8e1d4a 576      */
A 577     private function parse_expression($expression)
578     {
579         return preg_replace(
47124c 580             array(
T 581                 '/session:([a-z0-9_]+)/i',
f645ce 582                 '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
40353f 583                 '/env:([a-z0-9_]+)/i',
A 584                 '/request:([a-z0-9_]+)/i',
585                 '/cookie:([a-z0-9_]+)/i',
a17fe6 586                 '/browser:([a-z0-9_]+)/i'
47124c 587             ),
T 588             array(
589                 "\$_SESSION['\\1']",
f645ce 590                 "\$this->app->config->get('\\1',get_boolean('\\3'))",
47124c 591                 "\$this->env['\\1']",
ea373f 592                 "get_input_value('\\1', RCUBE_INPUT_GPC)",
a17fe6 593                 "\$_COOKIE['\\1']",
A 594                 "\$this->browser->{'\\1'}"
47124c 595             ),
8e1d4a 596             $expression);
47124c 597     }
T 598
599
600     /**
601      * Search for special tags in input and replace them
602      * with the appropriate content
603      *
604      * @param  string Input string to parse
605      * @return string Altered input string
06655a 606      * @todo   Use DOM-parser to traverse template HTML
47124c 607      * @todo   Maybe a cache.
T 608      */
609     private function parse_xml($input)
610     {
cc97ea 611         return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input);
6f488b 612     }
A 613
614
615     /**
cc97ea 616      * Callback function for parsing an xml command tag
T 617      * and turn it into real html content
47124c 618      *
cc97ea 619      * @param  array Matches array of preg_replace_callback
47124c 620      * @return string Tag/Object content
T 621      */
cc97ea 622     private function xml_command($matches)
47124c 623     {
cc97ea 624         $command = strtolower($matches[1]);
T 625         $attrib  = parse_attrib_string($matches[2]);
47124c 626
T 627         // empty output if required condition is not met
628         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
629             return '';
630         }
631
632         // execute command
633         switch ($command) {
634             // return a button
635             case 'button':
875a48 636                 if ($attrib['name'] || $attrib['command']) {
47124c 637                     return $this->button($attrib);
T 638                 }
639                 break;
640
641             // show a label
642             case 'label':
643                 if ($attrib['name'] || $attrib['command']) {
644                     return Q(rcube_label($attrib + array('vars' => array('product' => $this->config['product_name']))));
645                 }
646                 break;
647
648             // include a file
649             case 'include':
650                 $path = realpath($this->config['skin_path'].$attrib['file']);
ff73e0 651                 if (is_readable($path)) {
47124c 652                     if ($this->config['skin_include_php']) {
T 653                         $incl = $this->include_php($path);
654                     }
ff73e0 655                     else {
cc97ea 656                       $incl = file_get_contents($path);
T 657                     }
b4f7c6 658                     $incl = $this->parse_conditions($incl);
47124c 659                     return $this->parse_xml($incl);
T 660                 }
661                 break;
662
663             case 'plugin.include':
cc97ea 664                 $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
T 665                 return $hook['content'];
666                 break;
ad334a 667
cc97ea 668             // define a container block
T 669             case 'container':
670                 if ($attrib['name'] && $attrib['id']) {
671                     $this->command('gui_container', $attrib['name'], $attrib['id']);
672                     // let plugins insert some content here
673                     $hook = $this->app->plugins->exec_hook("template_container", $attrib);
674                     return $hook['content'];
47124c 675                 }
T 676                 break;
677
678             // return code for a specific application object
679             case 'object':
680                 $object = strtolower($attrib['name']);
cc97ea 681                 $content = '';
47124c 682
T 683                 // we are calling a class/method
684                 if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
685                     if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
686                     (is_string($handler[0]) && class_exists($handler[0])))
cc97ea 687                     $content = call_user_func($handler, $attrib);
47124c 688                 }
cc97ea 689                 // execute object handler function
47124c 690                 else if (function_exists($handler)) {
cc97ea 691                     $content = call_user_func($handler, $attrib);
47124c 692                 }
cc97ea 693                 else if ($object == 'productname') {
e019f2 694                     $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail';
cc97ea 695                     $content = Q($name);
47124c 696                 }
cc97ea 697                 else if ($object == 'version') {
c8fb2b 698                     $ver = (string)RCMAIL_VERSION;
T 699                     if (is_file(INSTALL_PATH . '.svn/entries')) {
700                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
701                           $ver .= ' [SVN r'.$regs[1].']';
702                     }
cc97ea 703                     $content = Q($ver);
47124c 704                 }
cc97ea 705                 else if ($object == 'steptitle') {
T 706                   $content = Q($this->get_pagetitle());
f645ce 707                 }
cc97ea 708                 else if ($object == 'pagetitle') {
47124c 709                     $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
f645ce 710                     $title .= $this->get_pagetitle();
cc97ea 711                     $content = Q($title);
47124c 712                 }
ad334a 713
cc97ea 714                 // exec plugin hooks for this template object
T 715                 $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
716                 return $hook['content'];
8e1d4a 717
A 718             // return code for a specified eval expression
719             case 'exp':
cc97ea 720                 $value = $this->parse_expression($attrib['expression']);
8e1d4a 721                 return eval("return Q($value);");
ad334a 722
47124c 723             // return variable
T 724             case 'var':
725                 $var = explode(':', $attrib['name']);
726                 $name = $var[1];
727                 $value = '';
728
729                 switch ($var[0]) {
730                     case 'env':
731                         $value = $this->env[$name];
732                         break;
733                     case 'config':
734                         $value = $this->config[$name];
735                         if (is_array($value) && $value[$_SESSION['imap_host']]) {
736                             $value = $value[$_SESSION['imap_host']];
737                         }
738                         break;
739                     case 'request':
740                         $value = get_input_value($name, RCUBE_INPUT_GPC);
741                         break;
742                     case 'session':
743                         $value = $_SESSION[$name];
744                         break;
d4273b 745                     case 'cookie':
A 746                         $value = htmlspecialchars($_COOKIE[$name]);
747                         break;
a17fe6 748                     case 'browser':
A 749                         $value = $this->browser->{$name};
750                         break;
47124c 751                 }
T 752
753                 if (is_array($value)) {
754                     $value = implode(', ', $value);
755                 }
756
757                 return Q($value);
758                 break;
759         }
760         return '';
761     }
762
763     /**
764      * Include a specific file and return it's contents
765      *
766      * @param string File path
767      * @return string Contents of the processed file
768      */
769     private function include_php($file)
770     {
771         ob_start();
772         include $file;
773         $out = ob_get_contents();
774         ob_end_clean();
775
776         return $out;
777     }
778
779     /**
780      * Create and register a button
781      *
782      * @param  array Named button attributes
783      * @return string HTML button
784      * @todo   Remove all inline JS calls and use jQuery instead.
785      * @todo   Remove all sprintf()'s - they are pretty, but also slow.
786      */
ed132e 787     public function button($attrib)
47124c 788     {
T 789         static $s_button_count = 100;
790
791         // these commands can be called directly via url
cc97ea 792         $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
47124c 793
T 794         if (!($attrib['command'] || $attrib['name'])) {
795             return '';
796         }
ae0c82 797
47124c 798         // try to find out the button type
T 799         if ($attrib['type']) {
800             $attrib['type'] = strtolower($attrib['type']);
801         }
802         else {
803             $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
804         }
e9b5a6 805
47124c 806         $command = $attrib['command'];
T 807
e9b5a6 808         if ($attrib['task'])
T 809           $command = $attrib['task'] . '.' . $command;
ad334a 810
af3cf8 811         if (!$attrib['image']) {
T 812             $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
813         }
47124c 814
T 815         if (!$attrib['id']) {
816             $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
817         }
818         // get localized text for labels and titles
819         if ($attrib['title']) {
1c932d 820             $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain']));
47124c 821         }
T 822         if ($attrib['label']) {
1c932d 823             $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain']));
47124c 824         }
T 825         if ($attrib['alt']) {
1c932d 826             $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
47124c 827         }
9b2ccd 828
47124c 829         // set title to alt attribute for IE browsers
c9e9fe 830         if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
A 831             $attrib['title'] = $attrib['alt'];
47124c 832         }
T 833
834         // add empty alt attribute for XHTML compatibility
835         if (!isset($attrib['alt'])) {
836             $attrib['alt'] = '';
837         }
838
839         // register button in the system
840         if ($attrib['command']) {
841             $this->add_script(sprintf(
842                 "%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
843                 JS_OBJECT_NAME,
844                 $command,
845                 $attrib['id'],
846                 $attrib['type'],
ae0c82 847                 $attrib['imageact'] ? $this->abs_url($attrib['imageact']) : $attrib['classact'],
A 848                 $attrib['imagesel'] ? $this->abs_url($attrib['imagesel']) : $attrib['classsel'],
849                 $attrib['imageover'] ? $this->abs_url($attrib['imageover']) : ''
47124c 850             ));
T 851
852             // make valid href to specific buttons
197601 853             if (in_array($attrib['command'], rcmail::$main_tasks)) {
203ee4 854                 $attrib['href'] = rcmail_url(null, null, $attrib['command']);
47124c 855             }
e9b5a6 856             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
T 857                 $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']);
858             }
47124c 859             else if (in_array($attrib['command'], $a_static_commands)) {
203ee4 860                 $attrib['href'] = rcmail_url($attrib['command']);
a25d39 861             }
29f977 862             else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
T 863               $attrib['href'] = $this->env['permaurl'];
864             }
47124c 865         }
T 866
867         // overwrite attributes
868         if (!$attrib['href']) {
869             $attrib['href'] = '#';
870         }
e9b5a6 871         if ($attrib['task']) {
T 872             if ($attrib['classact'])
873                 $attrib['class'] = $attrib['classact'];
874         }
875         else if ($command && !$attrib['onclick']) {
47124c 876             $attrib['onclick'] = sprintf(
T 877                 "return %s.command('%s','%s',this)",
878                 JS_OBJECT_NAME,
879                 $command,
880                 $attrib['prop']
881             );
882         }
883
884         $out = '';
885
886         // generate image tag
887         if ($attrib['type']=='image') {
888             $attrib_str = html::attrib_string(
889                 $attrib,
890                 array(
c9e9fe 891                     'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
A 892                     'vspace', 'align', 'alt', 'tabindex', 'title'
47124c 893                 )
T 894             );
ae0c82 895             $btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
47124c 896             if ($attrib['label']) {
T 897                 $btn_content .= ' '.$attrib['label'];
898             }
c9e9fe 899             $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
47124c 900         }
T 901         else if ($attrib['type']=='link') {
356a67 902             $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
203ee4 903             $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
47124c 904         }
T 905         else if ($attrib['type']=='input') {
906             $attrib['type'] = 'button';
907
908             if ($attrib['label']) {
909                 $attrib['value'] = $attrib['label'];
910             }
911
912             $attrib_str = html::attrib_string(
913                 $attrib,
914                 array(
c9e9fe 915                     'type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex'
47124c 916                 )
T 917             );
918             $out = sprintf('<input%s disabled="disabled" />', $attrib_str);
919         }
920
921         // generate html code for button
922         if ($btn_content) {
923             $attrib_str = html::attrib_string($attrib, $link_attrib);
924             $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
925         }
926
927         return $out;
928     }
929
930
931     /*  ************* common functions delivering gui objects **************  */
932
933
934     /**
197601 935      * Create a form tag with the necessary hidden fields
T 936      *
937      * @param array Named tag parameters
938      * @return string HTML code for the form
939      */
940     public function form_tag($attrib, $content = null)
941     {
57f0c8 942       if ($this->framed || !empty($_REQUEST['_framed'])) {
197601 943         $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
T 944         $hidden = $hiddenfield->show();
945       }
ad334a 946
197601 947       if (!$content)
T 948         $attrib['noclose'] = true;
ad334a 949
197601 950       return html::tag('form',
T 951         $attrib + array('action' => "./", 'method' => "get"),
57f0c8 952         $hidden . $content,
T 953         array('id','class','style','name','method','action','enctype','onsubmit'));
954     }
ad334a 955
A 956
57f0c8 957     /**
T 958      * Build a form tag with a unique request token
959      *
960      * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
961      * @param string Form content
962      * @return string HTML code for the form
963      */
747797 964     public function request_form($attrib, $content = '')
57f0c8 965     {
T 966         $hidden = new html_hiddenfield();
967         if ($attrib['task']) {
968             $hidden->add(array('name' => '_task', 'value' => $attrib['task']));
969         }
970         if ($attrib['action']) {
971             $hidden->add(array('name' => '_action', 'value' => $attrib['action']));
972         }
ad334a 973
57f0c8 974         unset($attrib['task'], $attrib['request']);
T 975         $attrib['action'] = './';
ad334a 976
57f0c8 977         // we already have a <form> tag
T 978         if ($attrib['form'])
979             return $hidden->show() . $content;
980         else
981             return $this->form_tag($attrib, $hidden->show() . $content);
197601 982     }
T 983
984
985     /**
47124c 986      * GUI object 'username'
T 987      * Showing IMAP username of the current session
988      *
989      * @param array Named tag parameters (currently not used)
990      * @return string HTML code for the gui object
991      */
197601 992     public function current_username($attrib)
47124c 993     {
T 994         static $username;
995
996         // alread fetched
997         if (!empty($username)) {
998             return $username;
999         }
1000
e99991 1001         // Current username is an e-mail address
A 1002         if (strpos($_SESSION['username'], '@')) {
1003             $username = $_SESSION['username'];
1004         }
929a50 1005         // get e-mail address from default identity
e99991 1006         else if ($sql_arr = $this->app->user->get_identity()) {
7e9cec 1007             $username = $sql_arr['email'];
47124c 1008         }
T 1009         else {
7e9cec 1010             $username = $this->app->user->get_username();
47124c 1011         }
T 1012
e99991 1013         return idn_to_utf8($username);
47124c 1014     }
T 1015
1016
1017     /**
1018      * GUI object 'loginform'
1019      * Returns code for the webmail login form
1020      *
1021      * @param array Named parameters
1022      * @return string HTML code for the gui object
1023      */
1024     private function login_form($attrib)
1025     {
197601 1026         $default_host = $this->config['default_host'];
47124c 1027
T 1028         $_SESSION['temp'] = true;
ad334a 1029
cc97ea 1030         // save original url
T 1031         $url = get_input_value('_url', RCUBE_INPUT_POST);
3a2b27 1032         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
cc97ea 1033             $url = $_SERVER['QUERY_STRING'];
47124c 1034
6a486e 1035         $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser') + $attrib);
A 1036         $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd') + $attrib);
47124c 1037         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
c8ae24 1038         $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
cc97ea 1039         $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
47124c 1040         $input_host   = null;
T 1041
f3e101 1042         if (is_array($default_host) && count($default_host) > 1) {
47124c 1043             $input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
T 1044
1045             foreach ($default_host as $key => $value) {
1046                 if (!is_array($value)) {
1047                     $input_host->add($value, (is_numeric($key) ? $value : $key));
1048                 }
1049                 else {
1050                     $input_host = null;
1051                     break;
1052                 }
1053             }
f3e101 1054         }
A 1055         else if (is_array($default_host) && ($host = array_pop($default_host))) {
1056             $hide_host = true;
1057             $input_host = new html_hiddenfield(array(
1058                 'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);
47124c 1059         }
e3e597 1060         else if (empty($default_host)) {
6a486e 1061             $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost') + $attrib);
47124c 1062         }
T 1063
1064         $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form';
1065         $this->add_gui_object('loginform', $form_name);
1066
1067         // create HTML table with two cols
1068         $table = new html_table(array('cols' => 2));
1069
1070         $table->add('title', html::label('rcmloginuser', Q(rcube_label('username'))));
e3e597 1071         $table->add(null, $input_user->show(get_input_value('_user', RCUBE_INPUT_POST)));
47124c 1072
T 1073         $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password'))));
1074         $table->add(null, $input_pass->show());
1075
1076         // add host selection row
f3e101 1077         if (is_object($input_host) && !$hide_host) {
47124c 1078             $table->add('title', html::label('rcmloginhost', Q(rcube_label('server'))));
e3e597 1079             $table->add(null, $input_host->show(get_input_value('_host', RCUBE_INPUT_POST)));
47124c 1080         }
T 1081
197601 1082         $out = $input_action->show();
c8ae24 1083         $out .= $input_tzone->show();
cc97ea 1084         $out .= $input_url->show();
47124c 1085         $out .= $table->show();
ad334a 1086
f3e101 1087         if ($hide_host) {
A 1088             $out .= $input_host->show();
1089         }
47124c 1090
T 1091         // surround html output with a form tag
1092         if (empty($attrib['form'])) {
f3e101 1093             $out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
47124c 1094         }
T 1095
1096         return $out;
1097     }
1098
1099
1100     /**
8e211a 1101      * GUI object 'preloader'
A 1102      * Loads javascript code for images preloading
1103      *
1104      * @param array Named parameters
1105      * @return void
1106      */
1107     private function preloader($attrib)
1108     {
1109         $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
1110         $images = array_map(array($this, 'abs_url'), $images);
1111
1112         if (empty($images) || $this->app->task == 'logout')
1113             return;
1114
1115         $this->add_script('$(document).ready(function(){
1116             var images = ' . json_serialize($images) .';
1117             for (var i=0; i<images.length; i++) {
1118                 img = new Image();
1119                 img.src = images[i];
1120             }});', 'foot');
1121     }
1122
1123
1124     /**
47124c 1125      * GUI object 'searchform'
T 1126      * Returns code for search function
1127      *
1128      * @param array Named parameters
1129      * @return string HTML code for the gui object
1130      */
1131     private function search_form($attrib)
1132     {
1133         // add some labels to client
1134         $this->add_label('searching');
1135
1136         $attrib['name'] = '_q';
1137
1138         if (empty($attrib['id'])) {
1139             $attrib['id'] = 'rcmqsearchbox';
1140         }
a3f149 1141         if ($attrib['type'] == 'search' && !$this->browser->khtml) {
2eb794 1142             unset($attrib['type'], $attrib['results']);
a3f149 1143         }
ad334a 1144
47124c 1145         $input_q = new html_inputfield($attrib);
T 1146         $out = $input_q->show();
1147
1148         $this->add_gui_object('qsearchbox', $attrib['id']);
1149
1150         // add form tag around text field
1151         if (empty($attrib['form'])) {
197601 1152             $out = $this->form_tag(array(
T 1153                 'name' => "rcmqsearchform",
1154                 'onsubmit' => JS_OBJECT_NAME . ".command('search');return false;",
1155                 'style' => "display:inline"),
2eb794 1156                 $out);
47124c 1157         }
T 1158
1159         return $out;
1160     }
1161
1162
1163     /**
1164      * Builder for GUI object 'message'
1165      *
1166      * @param array Named tag parameters
1167      * @return string HTML code for the gui object
1168      */
1169     private function message_container($attrib)
1170     {
1171         if (isset($attrib['id']) === false) {
1172             $attrib['id'] = 'rcmMessageContainer';
1173         }
1174
1175         $this->add_gui_object('message', $attrib['id']);
1176         return html::div($attrib, "");
1177     }
1178
1179
1180     /**
1181      * GUI object 'charsetselector'
1182      *
1183      * @param array Named parameters for the select tag
1184      * @return string HTML code for the gui object
1185      */
e55ab0 1186     function charset_selector($attrib)
47124c 1187     {
T 1188         // pass the following attributes to the form class
1189         $field_attrib = array('name' => '_charset');
1190         foreach ($attrib as $attr => $value) {
e55ab0 1191             if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
47124c 1192                 $field_attrib[$attr] = $value;
T 1193             }
1194         }
e55ab0 1195
47124c 1196         $charsets = array(
e55ab0 1197             'UTF-8'        => 'UTF-8 ('.rcube_label('unicode').')',
A 1198             'US-ASCII'     => 'ASCII ('.rcube_label('english').')',
1199             'ISO-8859-1'   => 'ISO-8859-1 ('.rcube_label('westerneuropean').')',
1200             'ISO-8859-2'   => 'ISO-8895-2 ('.rcube_label('easterneuropean').')',
1201             'ISO-8859-4'   => 'ISO-8895-4 ('.rcube_label('baltic').')',
1202             'ISO-8859-5'   => 'ISO-8859-5 ('.rcube_label('cyrillic').')',
1203             'ISO-8859-6'   => 'ISO-8859-6 ('.rcube_label('arabic').')',
1204             'ISO-8859-7'   => 'ISO-8859-7 ('.rcube_label('greek').')',
1205             'ISO-8859-8'   => 'ISO-8859-8 ('.rcube_label('hebrew').')',
1206             'ISO-8859-9'   => 'ISO-8859-9 ('.rcube_label('turkish').')',
1207             'ISO-8859-10'   => 'ISO-8859-10 ('.rcube_label('nordic').')',
1208             'ISO-8859-11'   => 'ISO-8859-11 ('.rcube_label('thai').')',
1209             'ISO-8859-13'   => 'ISO-8859-13 ('.rcube_label('baltic').')',
1210             'ISO-8859-14'   => 'ISO-8859-14 ('.rcube_label('celtic').')',
1211             'ISO-8859-15'   => 'ISO-8859-15 ('.rcube_label('westerneuropean').')',
1212             'ISO-8859-16'   => 'ISO-8859-16 ('.rcube_label('southeasterneuropean').')',
1213             'WINDOWS-1250' => 'Windows-1250 ('.rcube_label('easterneuropean').')',
1214             'WINDOWS-1251' => 'Windows-1251 ('.rcube_label('cyrillic').')',
1215             'WINDOWS-1252' => 'Windows-1252 ('.rcube_label('westerneuropean').')',
1216             'WINDOWS-1253' => 'Windows-1253 ('.rcube_label('greek').')',
1217             'WINDOWS-1254' => 'Windows-1254 ('.rcube_label('turkish').')',
1218             'WINDOWS-1255' => 'Windows-1255 ('.rcube_label('hebrew').')',
1219             'WINDOWS-1256' => 'Windows-1256 ('.rcube_label('arabic').')',
1220             'WINDOWS-1257' => 'Windows-1257 ('.rcube_label('baltic').')',
1221             'WINDOWS-1258' => 'Windows-1258 ('.rcube_label('vietnamese').')',
1222             'ISO-2022-JP'  => 'ISO-2022-JP ('.rcube_label('japanese').')',
1223             'ISO-2022-KR'  => 'ISO-2022-KR ('.rcube_label('korean').')',
1224             'ISO-2022-CN'  => 'ISO-2022-CN ('.rcube_label('chinese').')',
1225             'EUC-JP'       => 'EUC-JP ('.rcube_label('japanese').')',
1226             'EUC-KR'       => 'EUC-KR ('.rcube_label('korean').')',
1227             'EUC-CN'       => 'EUC-CN ('.rcube_label('chinese').')',
1228             'BIG5'         => 'BIG5 ('.rcube_label('chinese').')',
1229             'GB2312'       => 'GB2312 ('.rcube_label('chinese').')',
1230         );
47124c 1231
e55ab0 1232         if (!empty($_POST['_charset']))
2eb794 1233             $set = $_POST['_charset'];
A 1234         else if (!empty($attrib['selected']))
1235             $set = $attrib['selected'];
1236         else
1237             $set = $this->get_charset();
47124c 1238
2eb794 1239         $set = strtoupper($set);
A 1240         if (!isset($charsets[$set]))
1241             $charsets[$set] = $set;
e55ab0 1242
A 1243         $select = new html_select($field_attrib);
1244         $select->add(array_values($charsets), array_keys($charsets));
1245
1246         return $select->show($set);
47124c 1247     }
T 1248
1249 }  // end class rcube_template
1250
1251