]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcmail_template.inc
Imported Upstream version 0.1~rc1~dfsg
[roundcube.git] / program / include / rcmail_template.inc
1 <?php
2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcmail_template.inc                                   |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
8  | Copyright (C) 2007, RoundCube Dev. - Switzerland                      |
9  | Licensed under the GNU GPL                                            |
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 require_once('include/rcube_shared.inc');
24
25
26 class rcmail_template extends rcube_html_page
27 {
28   var $config;
29   var $task = '';
30   var $framed = false;
31   var $ajax_call = false;
32   var $pagetitle = '';
33   var $env = array();
34   var $js_env = array();
35   var $js_commands = array();
36   var $object_handlers = array();
37
38
39   // PHP 5 constructor
40   function __construct(&$config, $task)
41   {
42     parent::__construct();
43     
44     $this->task = $task;
45     $this->config = $config;
46     $this->ajax_call = !empty($_GET['_remote']) || !empty($_POST['_remote']);
47     
48     // add common javascripts
49     if (!$this->ajax_call)
50     {
51       $javascript = "var ".JS_OBJECT_NAME." = new rcube_webmail();";
52
53       // don't wait for page onload. Call init at the bottom of the page (delayed)
54       $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');";
55
56       $this->add_script($javascript, 'head_top');
57       $this->add_script($javascript_foot, 'foot');
58       $this->scripts_path = 'program/js/';
59       $this->include_script('common.js');
60       $this->include_script('app.js');
61     }
62   }
63
64   // PHP 4 compatibility
65   function rcmail_template(&$config, $task)
66   {
67     $this->__construct($config, $task);
68   }
69   
70   
71   /**
72    * Set environment variable
73    */
74   function set_env($name, $value, $addtojs=true)
75   {
76     $this->env[$name] = $value;
77     if ($addtojs || isset($this->js_env[$name]))
78       $this->js_env[$name] = $value;
79   }
80
81
82   /**
83    * Set page title variable
84    */
85   function set_pagetitle($title)
86   {
87     $this->pagetitle = $title;
88   }
89
90
91   /**
92    * Register a template object handler
93    *
94    * @param string Object name
95    * @param string Function name to call
96    */
97   function add_handler($obj, $func)
98   {
99     $this->object_handlers[$obj] = $func;
100   }
101
102   /**
103    * Register a list of template object handlers
104    *
105    * @param array Hash array with object=>handler pairs
106    */
107   function add_handlers($arr)
108   {
109     $this->object_handlers = array_merge($this->object_handlers, $arr);
110   }
111
112   /**
113    * Register a GUI object to the client script
114    *
115    * @param string Object name
116    * @param string Object ID
117    */
118   function add_gui_object($obj, $id)
119   {
120     $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');");
121   }
122
123
124   /**
125    * Call a client method
126    *
127    * @param string Method to call
128    * @param ... Additional arguments
129    */
130   function command()
131   {
132     $this->js_commands[] = func_get_args();
133   }
134
135
136   /**
137    * Invoke display_message command
138    */
139   function show_message($message, $type='notice', $vars=NULL)
140   {
141     $this->command(
142       'display_message',
143       rcube_label(array('name' => $message, 'vars' => $vars)),
144       $type);
145   }
146
147
148   /**
149    * Delete all stored env variables and commands
150    */
151   function reset()
152   {
153     $this->env = array();
154     $this->js_env = array();
155     $this->js_commands = array();
156     $this->object_handlers = array();    
157     parent::reset();
158   }
159
160   /**
161    * Send the request output to the client.
162    * This will either parse a skin tempalte or send an AJAX response
163    *
164    * @param string  Template name
165    * @param boolean True if script should terminate (default)
166    */
167   function send($templ=null, $exit=true)
168   {
169     if ($this->ajax_call)
170       $this->remote_response('', !$exit);
171     else if ($templ != 'iframe')
172       $this->parse($templ, false);
173     else
174     {
175       $this->framed = $templ == 'iframe' ? true : $this->framed;
176       $this->write();
177     }
178     
179     if ($exit)
180       exit;
181   }
182
183
184   /**
185    * Send an AJAX response with executable JS code
186    * 
187    * @param string  Additional JS code
188    * @param boolean True if output buffer should be flushed
189    */
190   function remote_response($add='', $flush=false)
191   {
192     static $s_header_sent = FALSE;
193
194     if (!$s_header_sent)
195     {
196       $s_header_sent = TRUE;
197       send_nocacheing_headers();
198       header('Content-Type: application/x-javascript; charset='.RCMAIL_CHARSET);
199       print '/** ajax response ['.date('d/M/Y h:i:s O')."] **/\n";
200     }
201     
202     // unset default env vars
203     unset($this->js_env['task'], $this->js_env['action'], $this->js_env['comm_path']);
204
205     // send response code
206     print rcube_charset_convert($this->get_js_commands() . $add, RCMAIL_CHARSET, $this->get_charset());
207
208     if ($flush)  // flush the output buffer
209       flush();
210   }
211   
212   
213   /**
214    * @override
215    */
216   function write($template='')
217   {
218     // write all env variables to client
219     $js = $this->framed ? "if(window.parent) {\n" : '';
220     $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
221     $this->add_script($js, 'head_top');
222
223     // call super method
224     parent::write($template, $this->config['skin_path']);
225   }
226
227
228   /**
229    * Parse a specific skin template and deliver to stdout
230    *
231    * @param string  Template name
232    * @param boolean Exit script
233    */  
234   function parse($name='main', $exit=true)
235   {
236     $skin_path = $this->config['skin_path'];
237
238     // read template file
239     $templ = '';
240     $path = "$skin_path/templates/$name.html";
241
242     if($fp = @fopen($path, 'r'))
243     {
244       $templ = fread($fp, filesize($path));
245       fclose($fp);
246     }
247     else
248     {
249       raise_error(array(
250         'code' => 501,
251         'type' => 'php',
252         'line' => __LINE__,
253         'file' => __FILE__,
254         'message' => "Error loading template for '$name'"), TRUE, TRUE);
255       return FALSE;
256     }
257
258     // parse for specialtags
259     $output = $this->parse_xml($this->parse_conditions($templ));
260
261     // add debug console
262     if ($this->config['debug_level'] & 8)
263       $this->add_footer('<div style="position:absolute;top:5px;left:5px;width:400px;padding:0.2em;background:white;opacity:0.8;z-index:9000">
264         <a href="#toggle" onclick="con=document.getElementById(\'dbgconsole\');con.style.display=(con.style.display==\'none\'?\'block\':\'none\');return false">console</a>
265         <form action="/" name="debugform"><textarea name="console" id="dbgconsole" rows="20" cols="40" wrap="off" style="display:none;width:400px;border:none;font-size:x-small"></textarea></form></div>');
266
267     $this->write(trim($this->parse_with_globals($output)), $skin_path);
268
269     if ($exit)
270       exit;
271   }
272
273
274   /**
275    * Return executable javascript code for all registered commands
276    * @private
277    */
278   function get_js_commands()
279   {
280     $out = '';
281     if (!$this->framed)
282       $out .= ($this->ajax_call ? 'this' : JS_OBJECT_NAME) . '.set_env('.json_serialize($this->js_env).");\n";
283     
284     foreach ($this->js_commands as $i => $args)
285     {
286       $method = array_shift($args);
287       foreach ($args as $i => $arg)
288         $args[$i] = json_serialize($arg);
289
290       $parent = $this->framed || preg_match('/^parent\./', $method);
291       $out .= sprintf(
292         "%s.%s(%s);\n",
293         $this->ajax_call ? 'this' : ($parent ? 'parent.' : '') . JS_OBJECT_NAME,
294         preg_replace('/^parent\./', '', $method),
295         join(',', $args));
296     }
297     
298     return $out;
299   }
300   
301   /**
302    * Make URLs starting with a slash point to skin directory
303    */
304   function abs_url($str)
305   {
306     return preg_replace('/^\//', $this->config['skin_path'].'/', $str);
307   }
308
309
310
311   /*****  Template parsing methods  *****/
312   
313   /**
314    * Replace all strings ($varname) with the content
315    * of the according global variable.
316    */
317   function parse_with_globals($input)
318   {
319     $GLOBALS['__comm_path'] = $GLOBALS['COMM_PATH'];
320     return preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
321   }
322   
323   
324   /**
325    * Parse for conditional tags
326    */
327   function parse_conditions($input)
328   {
329     if (($matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE)) && count($matches)==4)
330     {
331       if (preg_match('/^(else|endif)$/i', $matches[1]))
332         return $matches[0] . $this->parse_conditions($matches[3]);
333       else
334       {
335         $attrib = parse_attrib_string($matches[2]);
336         if (isset($attrib['condition']))
337         {
338           $condmet = $this->check_condition($attrib['condition']);
339           $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
340
341           if ($condmet)
342             $result = $submatches[0] . ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>/Uis', '', $submatches[3], 1) : $submatches[3]);
343           else
344             $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
345
346           return $matches[0] . $this->parse_conditions($result);
347         }
348         else
349         {
350           raise_error(array('code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
351                             'message' => "Unable to parse conditional tag " . $matches[2]), TRUE, FALSE);
352         }
353       }
354     }
355
356     return $input;
357   }
358
359
360   /**
361    * Determines if a given condition is met
362    *
363    * @return True if condition is valid, False is not
364    */
365   function check_condition($condition)
366   {
367     $condition = preg_replace(
368         array('/session:([a-z0-9_]+)/i', '/config:([a-z0-9_]+)/i', '/env:([a-z0-9_]+)/i', '/request:([a-z0-9_]+)/ie'),
369         array("\$_SESSION['\\1']", "\$this->config['\\1']", "\$this->env['\\1']", "get_input_value('\\1', RCUBE_INPUT_GPC)"),
370         $condition);
371
372     return @eval("return (".$condition.");");
373   }
374
375
376   /**
377    * Search for special tags in input and replace them
378    * with the appropriate content
379    *
380    * @param string Input string to parse
381    * @return Altered input string
382    */
383   function parse_xml($input)
384   {
385     return preg_replace('/<roundcube:([-_a-z]+)\s+([^>]+)>/Uie', "\$this->xml_command('\\1', '\\2')", $input);
386   }
387
388
389   /**
390    * Convert a xml command tag into real content
391    *
392    * @param string Tag command: object,button,label, etc.
393    * @param string Attribute string
394    * @return Tag/Object content string
395    */
396   function xml_command($command, $str_attrib, $add_attrib=array())
397     {
398     $command = strtolower($command);
399     $attrib = parse_attrib_string($str_attrib) + $add_attrib;
400
401     // empty output if required condition is not met
402     if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition']))
403       return '';
404
405     // execute command
406     switch ($command)
407     {
408       // return a button
409       case 'button':
410         if ($attrib['command'])
411           return $this->button($attrib);
412         break;
413
414       // show a label
415       case 'label':
416         if ($attrib['name'] || $attrib['command'])
417           return Q(rcube_label($attrib + array('vars' => array('product' => $this->config['product_name']))));
418         break;
419
420       // include a file 
421       case 'include':
422         $path = realpath($this->config['skin_path'].$attrib['file']);
423         if ($fp = @fopen($path, 'r'))
424         {
425           $incl = fread($fp, filesize($path));
426           fclose($fp);        
427           return $this->parse_xml($incl);
428         }
429         break;
430
431       // return code for a specific application object
432       case 'object':
433         $object = strtolower($attrib['name']);
434
435         // execute object handler function
436         if ($this->object_handlers[$object] && function_exists($this->object_handlers[$object]))
437           return call_user_func($this->object_handlers[$object], $attrib);
438
439         else if ($object=='productname')
440           {
441           $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail';
442           return Q($name);
443           }
444         else if ($object=='version')
445           {
446           return (string)RCMAIL_VERSION;
447           }
448         else if ($object=='pagetitle')
449           {
450           $task = $this->task;
451           $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
452
453           if (!empty($this->pagetitle))
454             $title .= $this->pagetitle;
455           else if ($task == 'login')
456             $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
457           else
458             $title .= ucfirst($task);
459
460           return Q($title);
461           }
462
463         break;
464       }
465
466     return '';
467     }
468
469
470   /**
471    * Create and register a button
472    *
473    * @param array Button attributes
474    * @return HTML button
475    */
476   function button($attrib)
477     {
478     global $CONFIG, $OUTPUT, $BROWSER, $MAIN_TASKS;
479     static $sa_buttons = array();
480     static $s_button_count = 100;
481
482     // these commands can be called directly via url
483     $a_static_commands = array('compose', 'list');
484
485     $skin_path = $this->config['skin_path'];
486
487     if (!($attrib['command'] || $attrib['name']))
488       return '';
489
490     // try to find out the button type
491     if ($attrib['type'])
492       $attrib['type'] = strtolower($attrib['type']);
493     else
494       $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
495
496     $command = $attrib['command'];
497
498     // take the button from the stack
499     if($attrib['name'] && $sa_buttons[$attrib['name']])
500       $attrib = $sa_buttons[$attrib['name']];
501
502     // add button to button stack
503     else if($attrib['image'] || $attrib['imageact'] || $attrib['imagepas'] || $attrib['class'])
504     {
505       if (!$attrib['name'])
506         $attrib['name'] = $command;
507
508       if (!$attrib['image'])
509         $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
510
511       $sa_buttons[$attrib['name']] = $attrib;
512     }
513
514     // get saved button for this command/name
515     else if ($command && $sa_buttons[$command])
516       $attrib = $sa_buttons[$command];
517
518     //else
519     //  return '';
520
521
522     // set border to 0 because of the link arround the button
523     if ($attrib['type']=='image' && !isset($attrib['border']))
524       $attrib['border'] = 0;
525
526     if (!$attrib['id'])
527       $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
528
529     // get localized text for labels and titles
530     if ($attrib['title'])
531       $attrib['title'] = Q(rcube_label($attrib['title']));
532     if ($attrib['label'])
533       $attrib['label'] = Q(rcube_label($attrib['label']));
534
535     if ($attrib['alt'])
536       $attrib['alt'] = Q(rcube_label($attrib['alt']));
537
538     // set title to alt attribute for IE browsers
539     if ($BROWSER['ie'] && $attrib['title'] && !$attrib['alt'])
540     {
541       $attrib['alt'] = $attrib['title'];
542       unset($attrib['title']);
543     }
544
545     // add empty alt attribute for XHTML compatibility
546     if (!isset($attrib['alt']))
547       $attrib['alt'] = '';
548
549
550     // register button in the system
551     if ($attrib['command'])
552     {
553       $this->add_script(sprintf(
554         "%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
555         JS_OBJECT_NAME,
556         $command,
557         $attrib['id'],
558         $attrib['type'],
559         $attrib['imageact'] ? $skin_path.$attrib['imageact'] : $attrib['classact'],
560         $attrib['imagesel'] ? $skin_path.$attrib['imagesel'] : $attrib['classsel'],
561         $attrib['imageover'] ? $skin_path.$attrib['imageover'] : '')
562       );
563
564       // make valid href to specific buttons
565       if (in_array($attrib['command'], $MAIN_TASKS))
566         $attrib['href'] = Q(rcmail_url(null, null, $attrib['command']));
567       else if (in_array($attrib['command'], $a_static_commands))
568         $attrib['href'] = Q(rcmail_url($attrib['command']));
569     }
570
571     // overwrite attributes
572     if (!$attrib['href'])
573       $attrib['href'] = '#';
574
575     if ($command)
576       $attrib['onclick'] = sprintf("return %s.command('%s','%s',this)", JS_OBJECT_NAME, $command, $attrib['prop']);
577
578     if ($command && $attrib['imageover'])
579     {
580       $attrib['onmouseover'] = sprintf("return %s.button_over('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
581       $attrib['onmouseout'] = sprintf("return %s.button_out('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
582     }
583
584     if ($command && $attrib['imagesel'])
585     {
586       $attrib['onmousedown'] = sprintf("return %s.button_sel('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
587       $attrib['onmouseup'] = sprintf("return %s.button_out('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
588     }
589
590     $out = '';
591
592     // generate image tag
593     if ($attrib['type']=='image')
594     {
595       $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'align', 'alt'));
596       $img_tag = sprintf('<img src="%%s"%s />', $attrib_str);
597       $btn_content = sprintf($img_tag, $skin_path.$attrib['image']);
598       if ($attrib['label'])
599         $btn_content .= ' '.$attrib['label'];
600
601       $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title');
602     }
603     else if ($attrib['type']=='link')
604     {
605       $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command'];
606       $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style');
607     }
608     else if ($attrib['type']=='input')
609     {
610       $attrib['type'] = 'button';
611
612       if ($attrib['label'])
613         $attrib['value'] = $attrib['label'];
614
615       $attrib_str = create_attrib_string($attrib, array('type', 'value', 'onclick', 'id', 'class', 'style'));
616       $out = sprintf('<input%s disabled />', $attrib_str);
617     }
618
619     // generate html code for button
620     if ($btn_content)
621     {
622       $attrib_str = create_attrib_string($attrib, $link_attrib);
623       $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
624     }
625
626     return $out;
627   }
628
629 }
630
631 ?>