From 30b30226e6569f13e444cdcb513cd2bfc24318d7 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Thu, 04 Nov 2010 10:03:26 -0400
Subject: [PATCH] - Add possibility to force mailbox selection. There're situations where we're invoking   STATUS (for all messages count) and SELECT later for other operations. If we   call SELECT first, the STATUS will be not needed.

---
 program/include/rcube_template.php |  617 +++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 408 insertions(+), 209 deletions(-)

diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php
index fb65411..a30c4f3 100755
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_template.php
@@ -4,8 +4,8 @@
  +-----------------------------------------------------------------------+
  | program/include/rcube_template.php                                    |
  |                                                                       |
- | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2006-2008, RoundCube Dev. - Switzerland                 |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2006-2010, Roundcube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -30,15 +30,18 @@
  */
 class rcube_template extends rcube_html_page
 {
-    var $app;
-    var $config;
-    var $framed = false;
-    var $pagetitle = '';
-    var $env = array();
-    var $js_env = array();
-    var $js_commands = array();
-    var $object_handlers = array();
+    private $app;
+    private $config;
+    private $pagetitle = '';
+    private $message = null;
+    private $js_env = array();
+    private $js_commands = array();
+    private $object_handlers = array();
 
+    public $browser;
+    public $framed = false;
+    public $env = array();
+    public $type = 'html';
     public $ajax_call = false;
 
     /**
@@ -53,9 +56,11 @@
 
         $this->app = rcmail::get_instance();
         $this->config = $this->app->config->all();
-        
+        $this->browser = new rcube_browser();
+
         //$this->framed = $framed;
         $this->set_env('task', $task);
+        $this->set_env('request_token', $this->app->get_request_token());
 
         // load the correct skin (in case user-defined)
         $this->set_skin($this->config['skin']);
@@ -64,17 +69,19 @@
         $javascript = 'var '.JS_OBJECT_NAME.' = new rcube_webmail();';
 
         // don't wait for page onload. Call init at the bottom of the page (delayed)
-        $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');";
+        $javascript_foot = '$(document).ready(function(){ '.JS_OBJECT_NAME.'.init(); });';
 
         $this->add_script($javascript, 'head_top');
         $this->add_script($javascript_foot, 'foot');
         $this->scripts_path = 'program/js/';
+        $this->include_script('jquery-1.4.min.js');
         $this->include_script('common.js');
         $this->include_script('app.js');
 
         // register common UI objects
         $this->add_handlers(array(
             'loginform'       => array($this, 'login_form'),
+            'preloader'       => array($this, 'preloader'),
             'username'        => array($this, 'current_username'),
             'message'         => array($this, 'message_container'),
             'charsetselector' => array($this, 'charset_selector'),
@@ -105,18 +112,48 @@
         $this->pagetitle = $title;
     }
 
+
+    /**
+     * Getter for the current page title
+     *
+     * @return string The page title
+     */
+    public function get_pagetitle()
+    {
+        if (!empty($this->pagetitle)) {
+            $title = $this->pagetitle;
+        }
+        else if ($this->env['task'] == 'login') {
+            $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
+        }
+        else {
+            $title = ucfirst($this->env['task']);
+        }
+
+        return $title;
+    }
+
+
     /**
      * Set skin
      */
     public function set_skin($skin)
     {
-        if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin))
+        $valid = false;
+
+        if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin)) {
             $skin_path = 'skins/'.$skin;
-        else
+            $valid = true;
+        }
+        else {
             $skin_path = $this->config['skin_path'] ? $this->config['skin_path'] : 'skins/default';
+            $valid = !$skin;
+        }
 
         $this->app->config->set('skin_path', $skin_path);
         $this->config['skin_path'] = $skin_path;
+
+        return $valid;
     }
 
     /**
@@ -175,7 +212,9 @@
      */
     public function command()
     {
-        $this->js_commands[] = func_get_args();
+        $cmd = func_get_args();
+        if (strpos($cmd[0], 'plugin.') === false)
+          $this->js_commands[] = $cmd;
     }
 
 
@@ -184,8 +223,11 @@
      */
     public function add_label()
     {
-        $arg_list = func_get_args();
-        foreach ($arg_list as $i => $name) {
+        $args = func_get_args();
+        if (count($args) == 1 && is_array($args[0]))
+          $args = $args[0];
+
+        foreach ($args as $name) {
             $this->command('add_label', $name, rcube_label($name));
         }
     }
@@ -197,14 +239,18 @@
      * @param string Message to display
      * @param string Message type [notice|confirm|error]
      * @param array Key-value pairs to be replaced in localized text
+     * @param boolean Override last set message
      * @uses self::command()
      */
-    public function show_message($message, $type='notice', $vars=NULL)
+    public function show_message($message, $type='notice', $vars=null, $override=true)
     {
-        $this->command(
-            'display_message',
-            rcube_label(array('name' => $message, 'vars' => $vars)),
-            $type);
+        if ($override || !$this->message) {
+            $this->message = $message;
+            $this->command(
+                'display_message',
+                rcube_label(array('name' => $message, 'vars' => $vars)),
+                $type);
+        }
     }
 
 
@@ -252,12 +298,23 @@
     public function send($templ = null, $exit = true)
     {
         if ($templ != 'iframe') {
+            // prevent from endless loops
+            if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
+                raise_error(array('code' => 505, 'type' => 'php',
+                  'file' => __FILE__, 'line' => __LINE__,
+                  'message' => 'Recursion alert: ignoring output->send()'), true, false);
+                return;
+            }
             $this->parse($templ, false);
         }
         else {
             $this->framed = $templ == 'iframe' ? true : $this->framed;
             $this->write();
         }
+
+        // set output asap
+        ob_flush();
+        flush();
 
         if ($exit) {
             exit;
@@ -274,13 +331,21 @@
     public function write($template = '')
     {
         // unlock interface after iframe load
+        $unlock = preg_replace('/[^a-z0-9]/i', '', $_GET['_unlock']);
         if ($this->framed) {
-            array_unshift($this->js_commands, array('set_busy', false));
+            array_unshift($this->js_commands, array('set_busy', false, null, $unlock));
+        }
+        else if ($unlock) {
+            array_unshift($this->js_commands, array('hide_message', $unlock));
         }
         // write all env variables to client
         $js = $this->framed ? "if(window.parent) {\n" : '';
         $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
         $this->add_script($js, 'head_top');
+
+        // make sure all <form> tags have a valid request token
+        $template = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $template);
+        $this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
 
         // call super method
         parent::write($template, $this->config['skin_path']);
@@ -299,37 +364,57 @@
     private function parse($name = 'main', $exit = true)
     {
         $skin_path = $this->config['skin_path'];
+        $plugin = false;
+
+        $temp = explode(".", $name, 2);
+        if (count($temp) > 1) {
+            $plugin = $temp[0];
+            $name = $temp[1];
+            $skin_dir = $plugin . '/skins/' . $this->config['skin'];
+            $skin_path = $this->app->plugins->dir . $skin_dir;
+            if (!is_dir($skin_path)) {  // fallback to default skin
+                $skin_dir = $plugin . '/skins/default';
+                $skin_path = $this->app->plugins->dir . $skin_dir;
+            }
+        }
+
         $path = "$skin_path/templates/$name.html";
 
         // read template file
-	if (($templ = file_get_contents($path)) === false) {
-            ob_start();
-            file_get_contents($path);
-            $message = ob_get_contents();
-            ob_end_clean();
+        if (($templ = @file_get_contents($path)) === false) {
             raise_error(array(
                 'code' => 501,
                 'type' => 'php',
                 'line' => __LINE__,
                 'file' => __FILE__,
-                'message' => 'Error loading template for '.$name.': '.$message
+                'message' => 'Error loading template for '.$name
                 ), true, true);
             return false;
+        }
+
+        // replace all path references to plugins/... with the configured plugins dir
+        // and /this/ to the current plugin skin directory
+        if ($plugin) {
+            $templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
         }
 
         // parse for specialtags
         $output = $this->parse_conditions($templ);
         $output = $this->parse_xml($output);
 
+        // trigger generic hook where plugins can put additional content to the page
+        $hook = $this->app->plugins->exec_hook("render_page", array('template' => $name, 'content' => $output));
+
         // add debug console
         if ($this->config['debug_level'] & 8) {
-            $this->add_footer('<div style="position:absolute;top:5px;left:5px;width:400px;padding:0.2em;background:white;opacity:0.8;z-index:9000">
-                <a href="#toggle" onclick="con=document.getElementById(\'dbgconsole\');con.style.display=(con.style.display==\'none\'?\'block\':\'none\');return false">console</a>
-                <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>'
+            $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;">
+                <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
+                <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>'
             );
         }
-        $output = $this->parse_with_globals($output);
-        $this->write(trim($output), $skin_path);
+
+        $output = $this->parse_with_globals($hook['content']);
+        $this->write(trim($output));
         if ($exit) {
             exit;
         }
@@ -355,18 +440,12 @@
             $parent = $this->framed || preg_match('/^parent\./', $method);
             $out .= sprintf(
                 "%s.%s(%s);\n",
-            ($parent ? 'parent.' : '') . JS_OBJECT_NAME,
-            preg_replace('/^parent\./', '', $method),
-            implode(',', $args)
+                ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME,
+                preg_replace('/^parent\./', '', $method),
+                implode(',', $args)
             );
         }
-        // add command to set page title
-        if ($this->ajax_call && !empty($this->pagetitle)) {
-            $out .= sprintf(
-                "this.set_pagetitle('%s');\n",
-                JQ((!empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '') . $this->pagetitle)
-            );
-        }
+
         return $out;
     }
 
@@ -378,7 +457,10 @@
      */
     public function abs_url($str)
     {
-        return preg_replace('/^\//', $this->config['skin_path'].'/', $str);
+        if ($str[0] == '/')
+            return $this->config['skin_path'] . $str;
+        else
+            return $str;
     }
 
 
@@ -390,8 +472,18 @@
      */
     private function parse_with_globals($input)
     {
+        $GLOBALS['__version'] = Q(RCMAIL_VERSION);
         $GLOBALS['__comm_path'] = Q($this->app->comm_path);
-        return preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
+        return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
+	    array($this, 'globals_callback'), $input);
+    }
+
+    /**
+     * Callback funtion for preg_replace_callback() in parse_with_globals()
+     */
+    private function globals_callback($matches)
+    {
+        return $GLOBALS[$matches[1]];
     }
 
     /**
@@ -451,26 +543,57 @@
      * @todo   Get rid off eval() once I understand what this does.
      * @todo   Extend this to allow real conditions, not just "set"
      * @param  string Condition statement
-     * @return boolean True if condition is met, False is not
+     * @return boolean True if condition is met, False if not
      */
     private function check_condition($condition)
     {
-        $condition = preg_replace(
+        return eval("return (".$this->parse_expression($condition).");");
+    }
+
+
+    /**
+     * Inserts hidden field with CSRF-prevention-token into POST forms
+     */
+    private function alter_form_tag($matches)
+    {
+        $out = $matches[0];
+        $attrib  = parse_attrib_string($matches[1]);
+
+        if (strtolower($attrib['method']) == 'post') {
+            $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
+            $out .= "\n" . $hidden->show();
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Parses expression and replaces variables
+     *
+     * @param  string Expression statement
+     * @return string Expression value
+     */
+    private function parse_expression($expression)
+    {
+        return preg_replace(
             array(
                 '/session:([a-z0-9_]+)/i',
-                '/config:([a-z0-9_]+)/i',
+                '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
                 '/env:([a-z0-9_]+)/i',
-                '/request:([a-z0-9_]+)/ie'
+                '/request:([a-z0-9_]+)/i',
+                '/cookie:([a-z0-9_]+)/i',
+                '/browser:([a-z0-9_]+)/i'
             ),
             array(
                 "\$_SESSION['\\1']",
-                "\$this->config['\\1']",
+                "\$this->app->config->get('\\1',get_boolean('\\3'))",
                 "\$this->env['\\1']",
-                "get_input_value('\\1', RCUVE_INPUT_GPC)"
+                "get_input_value('\\1', RCUBE_INPUT_GPC)",
+                "\$_COOKIE['\\1']",
+                "\$this->browser->{'\\1'}"
             ),
-            $condition);
-            
-            return eval("return (".$condition.");");
+            $expression);
     }
 
 
@@ -480,25 +603,26 @@
      *
      * @param  string Input string to parse
      * @return string Altered input string
+     * @todo   Use DOM-parser to traverse template HTML
      * @todo   Maybe a cache.
      */
     private function parse_xml($input)
     {
-        return preg_replace('/<roundcube:([-_a-z]+)\s+([^>]+)>/Uie', "\$this->xml_command('\\1', '\\2')", $input);
+        return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input);
     }
 
 
     /**
-     * Convert a xml command tag into real content
+     * Callback function for parsing an xml command tag
+     * and turn it into real html content
      *
-     * @param  string Tag command: object,button,label, etc.
-     * @param  string Attribute string
+     * @param  array Matches array of preg_replace_callback
      * @return string Tag/Object content
      */
-    private function xml_command($command, $str_attrib, $add_attrib = array())
+    private function xml_command($matches)
     {
-        $command = strtolower($command);
-        $attrib  = parse_attrib_string($str_attrib) + $add_attrib;
+        $command = strtolower($matches[1]);
+        $attrib  = parse_attrib_string($matches[2]);
 
         // empty output if required condition is not met
         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
@@ -529,72 +653,73 @@
                         $incl = $this->include_php($path);
                     }
                     else {
-		        $incl = file_get_contents($path);
-		    }
+                      $incl = file_get_contents($path);
+                    }
+                    $incl = $this->parse_conditions($incl);
                     return $this->parse_xml($incl);
                 }
                 break;
 
             case 'plugin.include':
-                //rcube::tfk_debug(var_export($this->config['skin_path'], true));
-                $path = realpath($this->config['skin_path'].$attrib['file']);
-                if (!$path) {
-                    //rcube::tfk_debug("Does not exist:");
-                    //rcube::tfk_debug($this->config['skin_path']);
-                    //rcube::tfk_debug($attrib['file']);
-                    //rcube::tfk_debug($path);
-                }
-                $incl = file_get_contents($path);
-                if ($incl) {
-                    return $this->parse_xml($incl);
+                $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
+                return $hook['content'];
+                break;
+
+            // define a container block
+            case 'container':
+                if ($attrib['name'] && $attrib['id']) {
+                    $this->command('gui_container', $attrib['name'], $attrib['id']);
+                    // let plugins insert some content here
+                    $hook = $this->app->plugins->exec_hook("template_container", $attrib);
+                    return $hook['content'];
                 }
                 break;
 
             // return code for a specific application object
             case 'object':
                 $object = strtolower($attrib['name']);
+                $content = '';
 
                 // we are calling a class/method
                 if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
                     if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
                     (is_string($handler[0]) && class_exists($handler[0])))
-                    return call_user_func($handler, $attrib);
+                    $content = call_user_func($handler, $attrib);
                 }
+                // execute object handler function
                 else if (function_exists($handler)) {
-                    // execute object handler function
-                    return call_user_func($handler, $attrib);
+                    $content = call_user_func($handler, $attrib);
                 }
-
-                if ($object=='productname') {
-                    $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail';
-                    return Q($name);
+                else if ($object == 'productname') {
+                    $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail';
+                    $content = Q($name);
                 }
-                if ($object=='version') {
+                else if ($object == 'version') {
                     $ver = (string)RCMAIL_VERSION;
                     if (is_file(INSTALL_PATH . '.svn/entries')) {
                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
                           $ver .= ' [SVN r'.$regs[1].']';
                     }
-                    return $ver;
+                    $content = Q($ver);
                 }
-                if ($object=='pagetitle') {
-                    $task  = $this->env['task'];
+                else if ($object == 'steptitle') {
+                  $content = Q($this->get_pagetitle());
+                }
+                else if ($object == 'pagetitle') {
                     $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
-
-                    if (!empty($this->pagetitle)) {
-                        $title .= $this->pagetitle;
-                    }
-                    else if ($task == 'login') {
-                        $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
-                    }
-                    else {
-                        $title .= ucfirst($task);
-                    }
-
-                    return Q($title);
+                    $title .= $this->get_pagetitle();
+                    $content = Q($title);
                 }
-                break;
-            
+
+                // exec plugin hooks for this template object
+                $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
+                return $hook['content'];
+
+            // return code for a specified eval expression
+            case 'exp':
+                $value = $this->parse_expression($attrib['expression']);
+                return eval("return Q($value);");
+
             // return variable
             case 'var':
                 $var = explode(':', $attrib['name']);
@@ -616,6 +741,12 @@
                         break;
                     case 'session':
                         $value = $_SESSION[$name];
+                        break;
+                    case 'cookie':
+                        $value = htmlspecialchars($_COOKIE[$name]);
+                        break;
+                    case 'browser':
+                        $value = $this->browser->{$name};
                         break;
                 }
 
@@ -653,19 +784,16 @@
      * @todo   Remove all inline JS calls and use jQuery instead.
      * @todo   Remove all sprintf()'s - they are pretty, but also slow.
      */
-    private function button($attrib)
+    public function button($attrib)
     {
-        static $sa_buttons = array();
         static $s_button_count = 100;
 
         // these commands can be called directly via url
-        $a_static_commands = array('compose', 'list');
+        $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
 
         if (!($attrib['command'] || $attrib['name'])) {
             return '';
         }
-
-        $browser   = new rcube_browser();
 
         // try to find out the button type
         if ($attrib['type']) {
@@ -674,48 +802,33 @@
         else {
             $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
         }
+
         $command = $attrib['command'];
 
-        // take the button from the stack
-        if ($attrib['name'] && $sa_buttons[$attrib['name']]) {
-            $attrib = $sa_buttons[$attrib['name']];
-        }
-        else if($attrib['image'] || $attrib['imageact'] || $attrib['imagepas'] || $attrib['class']) {
-            // add button to button stack
-            if (!$attrib['name']) {
-                $attrib['name'] = $command;
-            }
-            if (!$attrib['image']) {
-                $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
-            }
-            $sa_buttons[$attrib['name']] = $attrib;
-        }
-        else if ($command && $sa_buttons[$command]) {
-            // get saved button for this command/name
-            $attrib = $sa_buttons[$command];
+        if ($attrib['task'])
+          $command = $attrib['task'] . '.' . $command;
+
+        if (!$attrib['image']) {
+            $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
         }
 
-        // set border to 0 because of the link arround the button
-        if ($attrib['type']=='image' && !isset($attrib['border'])) {
-            $attrib['border'] = 0;
-        }
         if (!$attrib['id']) {
             $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
         }
         // get localized text for labels and titles
         if ($attrib['title']) {
-            $attrib['title'] = Q(rcube_label($attrib['title']));
+            $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain']));
         }
         if ($attrib['label']) {
-            $attrib['label'] = Q(rcube_label($attrib['label']));
+            $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain']));
         }
         if ($attrib['alt']) {
-            $attrib['alt'] = Q(rcube_label($attrib['alt']));
+            $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
         }
+
         // set title to alt attribute for IE browsers
-        if ($browser->ie && $attrib['title'] && !$attrib['alt']) {
-            $attrib['alt'] = $attrib['title'];
-            unset($attrib['title']);
+        if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
+            $attrib['title'] = $attrib['alt'];
         }
 
         // add empty alt attribute for XHTML compatibility
@@ -738,10 +851,16 @@
 
             // make valid href to specific buttons
             if (in_array($attrib['command'], rcmail::$main_tasks)) {
-                $attrib['href'] = Q(rcmail_url(null, null, $attrib['command']));
+                $attrib['href'] = rcmail_url(null, null, $attrib['command']);
+            }
+            else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
+                $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']);
             }
             else if (in_array($attrib['command'], $a_static_commands)) {
-                $attrib['href'] = Q(rcmail_url($attrib['command']));
+                $attrib['href'] = rcmail_url($attrib['command']);
+            }
+            else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
+              $attrib['href'] = $this->env['permaurl'];
             }
         }
 
@@ -749,41 +868,16 @@
         if (!$attrib['href']) {
             $attrib['href'] = '#';
         }
-        if ($command) {
+        if ($attrib['task']) {
+            if ($attrib['classact'])
+                $attrib['class'] = $attrib['classact'];
+        }
+        else if ($command && !$attrib['onclick']) {
             $attrib['onclick'] = sprintf(
                 "return %s.command('%s','%s',this)",
                 JS_OBJECT_NAME,
                 $command,
                 $attrib['prop']
-            );
-        }
-        if ($command && $attrib['imageover']) {
-            $attrib['onmouseover'] = sprintf(
-                "return %s.button_over('%s','%s')",
-                JS_OBJECT_NAME,
-                $command,
-                $attrib['id']
-            );
-            $attrib['onmouseout'] = sprintf(
-                "return %s.button_out('%s','%s')",
-                JS_OBJECT_NAME,
-                $command,
-                $attrib['id']
-            );
-        }
-
-        if ($command && $attrib['imagesel']) {
-            $attrib['onmousedown'] = sprintf(
-                "return %s.button_sel('%s','%s')",
-                JS_OBJECT_NAME,
-                $command,
-                $attrib['id']
-            );
-            $attrib['onmouseup'] = sprintf(
-                "return %s.button_out('%s','%s')",
-                JS_OBJECT_NAME,
-                $command,
-                $attrib['id']
             );
         }
 
@@ -794,20 +888,19 @@
             $attrib_str = html::attrib_string(
                 $attrib,
                 array(
-                    'style', 'class', 'id', 'width',
-                    'height', 'border', 'hspace',
-                    'vspace', 'align', 'alt', 'tabindex'
+                    'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
+                    'vspace', 'align', 'alt', 'tabindex', 'title'
                 )
             );
             $btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
             if ($attrib['label']) {
                 $btn_content .= ' '.$attrib['label'];
             }
-            $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title');
+            $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
         }
         else if ($attrib['type']=='link') {
-            $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command'];
-            $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex');
+            $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
+            $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
         }
         else if ($attrib['type']=='input') {
             $attrib['type'] = 'button';
@@ -819,8 +912,7 @@
             $attrib_str = html::attrib_string(
                 $attrib,
                 array(
-                    'type', 'value', 'onclick',
-                    'id', 'class', 'style', 'tabindex'
+                    'type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex'
                 )
             );
             $out = sprintf('<input%s disabled="disabled" />', $attrib_str);
@@ -847,17 +939,46 @@
      */
     public function form_tag($attrib, $content = null)
     {
-      if ($this->framed) {
+      if ($this->framed || !empty($_REQUEST['_framed'])) {
         $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
         $hidden = $hiddenfield->show();
       }
-      
+
       if (!$content)
         $attrib['noclose'] = true;
-      
+
       return html::tag('form',
         $attrib + array('action' => "./", 'method' => "get"),
-        $hidden . $content);
+        $hidden . $content,
+        array('id','class','style','name','method','action','enctype','onsubmit'));
+    }
+
+
+    /**
+     * Build a form tag with a unique request token
+     *
+     * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
+     * @param string Form content
+     * @return string HTML code for the form
+     */
+    public function request_form($attrib, $content = '')
+    {
+        $hidden = new html_hiddenfield();
+        if ($attrib['task']) {
+            $hidden->add(array('name' => '_task', 'value' => $attrib['task']));
+        }
+        if ($attrib['action']) {
+            $hidden->add(array('name' => '_action', 'value' => $attrib['action']));
+        }
+
+        unset($attrib['task'], $attrib['request']);
+        $attrib['action'] = './';
+
+        // we already have a <form> tag
+        if ($attrib['form'])
+            return $hidden->show() . $content;
+        else
+            return $this->form_tag($attrib, $hidden->show() . $content);
     }
 
 
@@ -877,15 +998,19 @@
             return $username;
         }
 
-        // get e-mail address form default identity
-        if ($sql_arr = $this->app->user->get_identity()) {
+        // Current username is an e-mail address
+        if (strpos($_SESSION['username'], '@')) {
+            $username = $_SESSION['username'];
+        }
+        // get e-mail address from default identity
+        else if ($sql_arr = $this->app->user->get_identity()) {
             $username = $sql_arr['email'];
         }
         else {
             $username = $this->app->user->get_username();
         }
 
-        return $username;
+        return idn_to_utf8($username);
     }
 
 
@@ -902,12 +1027,19 @@
 
         $_SESSION['temp'] = true;
 
-        $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30) + $attrib);
-        $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'size' => 30) + $attrib);
+        // save original url
+        $url = get_input_value('_url', RCUBE_INPUT_POST);
+        if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
+            $url = $_SERVER['QUERY_STRING'];
+
+        $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser') + $attrib);
+        $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd') + $attrib);
         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
+        $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
+        $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
         $input_host   = null;
 
-        if (is_array($default_host)) {
+        if (is_array($default_host) && count($default_host) > 1) {
             $input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
 
             foreach ($default_host as $key => $value) {
@@ -920,8 +1052,13 @@
                 }
             }
         }
+        else if (is_array($default_host) && ($host = array_pop($default_host))) {
+            $hide_host = true;
+            $input_host = new html_hiddenfield(array(
+                'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);
+        }
         else if (empty($default_host)) {
-            $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost', 'size' => 30));
+            $input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost') + $attrib);
         }
 
         $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form';
@@ -937,20 +1074,50 @@
         $table->add(null, $input_pass->show());
 
         // add host selection row
-        if (is_object($input_host)) {
+        if (is_object($input_host) && !$hide_host) {
             $table->add('title', html::label('rcmloginhost', Q(rcube_label('server'))));
             $table->add(null, $input_host->show(get_input_value('_host', RCUBE_INPUT_POST)));
         }
 
         $out = $input_action->show();
+        $out .= $input_tzone->show();
+        $out .= $input_url->show();
         $out .= $table->show();
+
+        if ($hide_host) {
+            $out .= $input_host->show();
+        }
 
         // surround html output with a form tag
         if (empty($attrib['form'])) {
-            $out = $this->form_tag(array('name' => $form_name, 'method' => "post"), $out);
+            $out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
         }
 
         return $out;
+    }
+
+
+    /**
+     * GUI object 'preloader'
+     * Loads javascript code for images preloading
+     *
+     * @param array Named parameters
+     * @return void
+     */
+    private function preloader($attrib)
+    {
+        $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
+        $images = array_map(array($this, 'abs_url'), $images);
+
+        if (empty($images) || $this->app->task == 'logout')
+            return;
+
+        $this->add_script('$(document).ready(function(){
+            var images = ' . json_serialize($images) .';
+            for (var i=0; i<images.length; i++) {
+                img = new Image();
+                img.src = images[i];
+            }});', 'foot');
     }
 
 
@@ -971,6 +1138,10 @@
         if (empty($attrib['id'])) {
             $attrib['id'] = 'rcmqsearchbox';
         }
+        if ($attrib['type'] == 'search' && !$this->browser->khtml) {
+            unset($attrib['type'], $attrib['results']);
+        }
+
         $input_q = new html_inputfield($attrib);
         $out = $input_q->show();
 
@@ -982,7 +1153,7 @@
                 'name' => "rcmqsearchform",
                 'onsubmit' => JS_OBJECT_NAME . ".command('search');return false;",
                 'style' => "display:inline"),
-              $out);
+                $out);
         }
 
         return $out;
@@ -1012,39 +1183,67 @@
      * @param array Named parameters for the select tag
      * @return string HTML code for the gui object
      */
-    static function charset_selector($attrib)
+    function charset_selector($attrib)
     {
         // pass the following attributes to the form class
         $field_attrib = array('name' => '_charset');
         foreach ($attrib as $attr => $value) {
-            if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex'))) {
+            if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
                 $field_attrib[$attr] = $value;
             }
         }
+
         $charsets = array(
-            'US-ASCII'     => 'ASCII (English)',
-            'EUC-JP'       => 'EUC-JP (Japanese)',
-            'EUC-KR'       => 'EUC-KR (Korean)',
-            'BIG5'         => 'BIG5 (Chinese)',
-            'GB2312'       => 'GB2312 (Chinese)',
-            'ISO-2022-JP'  => 'ISO-2022-JP (Japanese)',
-            'ISO-8859-1'   => 'ISO-8859-1 (Latin-1)',
-            'ISO-8859-2'   => 'ISO-8895-2 (Central European)',
-            'ISO-8859-7'   => 'ISO-8859-7 (Greek)',
-            'ISO-8859-9'   => 'ISO-8859-9 (Turkish)',
-            'Windows-1251' => 'Windows-1251 (Cyrillic)',
-            'Windows-1252' => 'Windows-1252 (Western)',
-            'Windows-1255' => 'Windows-1255 (Hebrew)',
-            'Windows-1256' => 'Windows-1256 (Arabic)',
-            'Windows-1257' => 'Windows-1257 (Baltic)',
-            'UTF-8'        => 'UTF-8'
-            );
+            'UTF-8'        => 'UTF-8 ('.rcube_label('unicode').')',
+            'US-ASCII'     => 'ASCII ('.rcube_label('english').')',
+            'ISO-8859-1'   => 'ISO-8859-1 ('.rcube_label('westerneuropean').')',
+            'ISO-8859-2'   => 'ISO-8895-2 ('.rcube_label('easterneuropean').')',
+            'ISO-8859-4'   => 'ISO-8895-4 ('.rcube_label('baltic').')',
+            'ISO-8859-5'   => 'ISO-8859-5 ('.rcube_label('cyrillic').')',
+            'ISO-8859-6'   => 'ISO-8859-6 ('.rcube_label('arabic').')',
+            'ISO-8859-7'   => 'ISO-8859-7 ('.rcube_label('greek').')',
+            'ISO-8859-8'   => 'ISO-8859-8 ('.rcube_label('hebrew').')',
+            'ISO-8859-9'   => 'ISO-8859-9 ('.rcube_label('turkish').')',
+            'ISO-8859-10'   => 'ISO-8859-10 ('.rcube_label('nordic').')',
+            'ISO-8859-11'   => 'ISO-8859-11 ('.rcube_label('thai').')',
+            'ISO-8859-13'   => 'ISO-8859-13 ('.rcube_label('baltic').')',
+            'ISO-8859-14'   => 'ISO-8859-14 ('.rcube_label('celtic').')',
+            'ISO-8859-15'   => 'ISO-8859-15 ('.rcube_label('westerneuropean').')',
+            'ISO-8859-16'   => 'ISO-8859-16 ('.rcube_label('southeasterneuropean').')',
+            'WINDOWS-1250' => 'Windows-1250 ('.rcube_label('easterneuropean').')',
+            'WINDOWS-1251' => 'Windows-1251 ('.rcube_label('cyrillic').')',
+            'WINDOWS-1252' => 'Windows-1252 ('.rcube_label('westerneuropean').')',
+            'WINDOWS-1253' => 'Windows-1253 ('.rcube_label('greek').')',
+            'WINDOWS-1254' => 'Windows-1254 ('.rcube_label('turkish').')',
+            'WINDOWS-1255' => 'Windows-1255 ('.rcube_label('hebrew').')',
+            'WINDOWS-1256' => 'Windows-1256 ('.rcube_label('arabic').')',
+            'WINDOWS-1257' => 'Windows-1257 ('.rcube_label('baltic').')',
+            'WINDOWS-1258' => 'Windows-1258 ('.rcube_label('vietnamese').')',
+            'ISO-2022-JP'  => 'ISO-2022-JP ('.rcube_label('japanese').')',
+            'ISO-2022-KR'  => 'ISO-2022-KR ('.rcube_label('korean').')',
+            'ISO-2022-CN'  => 'ISO-2022-CN ('.rcube_label('chinese').')',
+            'EUC-JP'       => 'EUC-JP ('.rcube_label('japanese').')',
+            'EUC-KR'       => 'EUC-KR ('.rcube_label('korean').')',
+            'EUC-CN'       => 'EUC-CN ('.rcube_label('chinese').')',
+            'BIG5'         => 'BIG5 ('.rcube_label('chinese').')',
+            'GB2312'       => 'GB2312 ('.rcube_label('chinese').')',
+        );
 
-            $select = new html_select($field_attrib);
-            $select->add(array_values($charsets), array_keys($charsets));
+        if (!empty($_POST['_charset']))
+	        $set = $_POST['_charset'];
+	    else if (!empty($attrib['selected']))
+	        $set = $attrib['selected'];
+	    else
+	        $set = $this->get_charset();
 
-            $set = $_POST['_charset'] ? $_POST['_charset'] : $this->get_charset();
-            return $select->show($set);
+	    $set = strtoupper($set);
+	    if (!isset($charsets[$set]))
+	        $charsets[$set] = $set;
+
+        $select = new html_select($field_attrib);
+        $select->add(array_values($charsets), array_keys($charsets));
+
+        return $select->show($set);
     }
 
 }  // end class rcube_template

--
Gitblit v1.9.1