From be9aacaa5296dfca63fb3a01c2dc52538d1546aa Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sat, 17 Nov 2012 12:31:31 -0500
Subject: [PATCH] Bring back lost localization for the about page

---
 program/include/rcube_output_html.php |  319 ++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 243 insertions(+), 76 deletions(-)

diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php
index fc6b3a2..6dd58fd 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcube_output_html.php
@@ -23,7 +23,8 @@
 /**
  * Class to create HTML page output using a skin template
  *
- * @package View
+ * @package    Framework
+ * @subpackage View
  */
 class rcube_output_html extends rcube_output
 {
@@ -33,7 +34,7 @@
     protected $js_env = array();
     protected $js_labels = array();
     protected $js_commands = array();
-    protected $plugin_skin_path;
+    protected $skin_paths = array();
     protected $template_name;
     protected $scripts_path = '';
     protected $script_files = array();
@@ -67,8 +68,18 @@
         $this->set_env('task', $task);
         $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
 
+        // add cookie info
+        $this->set_env('cookie_domain', ini_get('session.cookie_domain'));
+        $this->set_env('cookie_path', ini_get('session.cookie_path'));
+        $this->set_env('cookie_secure', ini_get('session.cookie_secure'));
+
         // load the correct skin (in case user-defined)
-        $this->set_skin($this->config->get('skin'));
+        $skin = $this->config->get('skin');
+        $this->set_skin($skin);
+        $this->set_env('skin', $skin);
+
+        if (!empty($_REQUEST['_extwin']))
+          $this->set_env('extwin', 1);
 
         // add common javascripts
         $this->add_script('var '.rcmail::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
@@ -147,14 +158,32 @@
         else {
             $skin_path = $this->config->get('skin_path');
             if (!$skin_path) {
-                $skin_path = 'skins/default';
+                $skin_path = 'skins/' . rcube_config::DEFAULT_SKIN;
             }
             $valid = !$skin;
         }
 
         $this->config->set('skin_path', $skin_path);
 
+        // register skin path(s)
+        $this->skin_paths = array();
+        $this->load_skin($skin_path);
+
         return $valid;
+    }
+
+    /**
+     * Helper method to recursively read skin meta files and register search paths
+     */
+    private function load_skin($skin_path)
+    {
+        $this->skin_paths[] = $skin_path;
+
+        // read meta file and check for dependecies
+        $meta = @json_decode(@file_get_contents($skin_path.'/meta.json'), true);
+        if ($meta['extends'] && is_dir('skins/' . $meta['extends'])) {
+            $this->load_skin('skins/' . $meta['extends']);
+        }
     }
 
 
@@ -166,8 +195,39 @@
      */
     public function template_exists($name)
     {
-        $filename = $this->config->get('skin_path') . '/templates/' . $name . '.html';
-        return (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
+        $found = false;
+        foreach ($this->skin_paths as $skin_path) {
+            $filename = $skin_path . '/templates/' . $name . '.html';
+            $found = (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
+            if ($found)
+                break;
+        }
+        return $found;
+    }
+
+
+    /**
+     * Find the given file in the current skin path stack
+     *
+     * @param string File name/path to resolve (starting with /)
+     * @param string Reference to the base path of the matching skin
+     * @param string Additional path to search in
+     * @return mixed Relative path to the requested file or False if not found
+     */
+    public function get_skin_file($file, &$skin_path, $add_path = null)
+    {
+        $skin_paths = $this->skin_paths;
+        if ($add_path)
+            array_unshift($skin_paths, $add_path);
+
+        foreach ($skin_paths as $skin_path) {
+            $path = realpath($skin_path . $file);
+            if (is_file($path)) {
+                return $skin_path . $file;
+            }
+        }
+
+        return false;
     }
 
 
@@ -267,6 +327,8 @@
      */
     public function redirect($p = array(), $delay = 1)
     {
+        if ($this->env['extwin'])
+            $p['extwin'] = 1;
         $location = $this->app->url($p);
         header('Location: ' . $location);
         exit;
@@ -352,48 +414,70 @@
      */
     function parse($name = 'main', $exit = true, $write = true)
     {
-        $skin_path = $this->config->get('skin_path');
         $plugin    = false;
         $realname  = $name;
-        $temp      = explode('.', $name, 2);
+        $this->template_name = $realname;
 
-        $this->plugin_skin_path = null;
-        $this->template_name    = $realname;
-
+        $temp = explode('.', $name, 2);
         if (count($temp) > 1) {
             $plugin    = $temp[0];
             $name      = $temp[1];
             $skin_dir  = $plugin . '/skins/' . $this->config->get('skin');
-            $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
 
-            // fallback to default skin
-            if (!is_dir($skin_path)) {
+            // apply skin search escalation list to plugin directory
+            $plugin_skin_paths = array();
+            foreach ($this->skin_paths as $skin_path) {
+                $plugin_skin_paths[] = $this->app->plugins->url . $plugin . '/' . $skin_path;
+            }
+
+            // add fallback to default skin
+            if (is_dir($this->app->plugins->dir . $plugin . '/skins/default')) {
                 $skin_dir = $plugin . '/skins/default';
-                $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
+                $plugin_skin_paths[] = $this->app->plugins->url . $skin_dir;
+            }
+
+            // add plugin skin paths to search list
+            $this->skin_paths = array_merge($plugin_skin_paths, $this->skin_paths);
+        }
+
+        // find skin template
+        $path = false;
+        foreach ($this->skin_paths as $skin_path) {
+            $path = "$skin_path/templates/$name.html";
+
+            // fallback to deprecated template names
+            if (!is_readable($path) && $this->deprecated_templates[$realname]) {
+                $path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html";
+
+                if (is_readable($path)) {
+                    rcube::raise_error(array(
+                        'code' => 502, 'type' => 'php',
+                        'file' => __FILE__, 'line' => __LINE__,
+                        'message' => "Using deprecated template '" . $this->deprecated_templates[$realname]
+                            . "' in $skin_path/templates. Please rename to '$realname'"),
+                        true, false);
+                }
+            }
+
+            if (is_readable($path)) {
+                $this->config->set('skin_path', $skin_path);
+                $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path);  // set base_path to core skin directory (not plugin's skin)
+                break;
+            }
+            else {
+                $path = false;
             }
         }
 
-        $path = "$skin_path/templates/$name.html";
-
-        if (!is_readable($path) && $this->deprecated_templates[$realname]) {
-            $path = "$skin_path/templates/".$this->deprecated_templates[$realname].".html";
-            if (is_readable($path))
-                rcube::raise_error(array('code' => 502, 'type' => 'php',
-                    'file' => __FILE__, 'line' => __LINE__,
-                    'message' => "Using deprecated template '".$this->deprecated_templates[$realname]
-                        ."' in $skin_path/templates. Please rename to '".$realname."'"),
-                true, false);
-        }
-
         // read template file
-        if (($templ = @file_get_contents($path)) === false) {
+        if (!$path || ($templ = @file_get_contents($path)) === false) {
             rcube::raise_error(array(
                 'code' => 501,
                 'type' => 'php',
                 'line' => __LINE__,
                 'file' => __FILE__,
                 'message' => 'Error loading template for '.$realname
-                ), true, true);
+                ), true, $write);
             return false;
         }
 
@@ -413,8 +497,6 @@
         // save some memory
         $output = $hook['content'];
         unset($hook['content']);
-
-        $output = $this->parse_with_globals($output);
 
         // make sure all <form> tags have a valid request token
         $output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
@@ -481,12 +563,17 @@
      * Make URLs starting with a slash point to skin directory
      *
      * @param  string Input string
+     * @param  boolean True if URL should be resolved using the current skin path stack
      * @return string
      */
-    public function abs_url($str)
+    public function abs_url($str, $search_path = false)
     {
-        if ($str[0] == '/')
-            return $this->config->get('skin_path') . $str;
+        if ($str[0] == '/') {
+            if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path)))
+                return $file_url;
+
+            return $this->base_path . $str;
+        }
         else
             return $str;
     }
@@ -520,7 +607,7 @@
     {
         $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
         $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
-        $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
+        $GLOBALS['__skin_path'] = html::quote($this->base_path);
 
         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
             array($this, 'globals_callback'), $input);
@@ -533,6 +620,43 @@
     protected function globals_callback($matches)
     {
         return $GLOBALS[$matches[1]];
+    }
+
+
+    /**
+     * Correct absolute paths in images and other tags
+     * add timestamp to .js and .css filename
+     */
+    protected function fix_paths($output)
+    {
+        return preg_replace_callback(
+            '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
+            array($this, 'file_callback'), $output);
+    }
+
+
+    /**
+     * Callback function for preg_replace_callback in write()
+     *
+     * @return string Parsed string
+     */
+    protected function file_callback($matches)
+    {
+        $file = $matches[3];
+
+        // correct absolute paths
+        if ($file[0] == '/') {
+            $file = $this->base_path . $file;
+        }
+
+        // add file modification timestamp
+        if (preg_match('/\.(js|css)$/', $file)) {
+            if ($fs = @filemtime($file)) {
+                $file .= '?s=' . $fs;
+            }
+        }
+
+        return $matches[1] . '=' . $matches[2] . $file . $matches[4];
     }
 
 
@@ -691,20 +815,52 @@
                 }
                 break;
 
+            // frame
+            case 'frame':
+                return $this->frame($attrib);
+                break;
+
             // show a label
             case 'label':
+                if ($attrib['expression'])
+                    $attrib['name'] = eval("return " . $this->parse_expression($attrib['expression']) .";");
+
                 if ($attrib['name'] || $attrib['command']) {
+                    // @FIXME: 'noshow' is useless, remove?
+                    if ($attrib['noshow']) {
+                        return '';
+                    }
+
                     $vars = $attrib + array('product' => $this->config->get('product_name'));
                     unset($vars['name'], $vars['command']);
-                    $label = $this->app->gettext($attrib + array('vars' => $vars));
-                    return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : html::quote($label)) : '';
+
+                    $label   = $this->app->gettext($attrib + array('vars' => $vars));
+                    $quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (get_boolean((string)$attrib['html']) ? 'no' : '');
+
+                    switch ($quoting) {
+                        case 'no':
+                        case 'raw':
+                            break;
+                        case 'javascript':
+                        case 'js':
+                            $label = rcmail::JQ($label);
+                            break;
+                        default:
+                            $label = html::quote($label);
+                            break;
+                    }
+
+                    return $label;
                 }
                 break;
 
             // include a file
             case 'include':
-                if (!$this->plugin_skin_path || !is_file($path = realpath($this->plugin_skin_path . $attrib['file'])))
-                    $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config->get('skin_path')).$attrib['file']);
+                $old_base_path = $this->base_path;
+                if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) {
+                    $this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path);  // set base_path to core skin directory (not plugin's skin)
+                    $path = realpath($path);
+                }
 
                 if (is_readable($path)) {
                     if ($this->config->get('skin_include_php')) {
@@ -714,14 +870,16 @@
                       $incl = file_get_contents($path);
                     }
                     $incl = $this->parse_conditions($incl);
-                    return $this->parse_xml($incl);
+                    $incl = $this->parse_xml($incl);
+                    $incl = $this->fix_paths($incl);
+                    $this->base_path = $old_base_path;
+                    return $incl;
                 }
                 break;
 
             case 'plugin.include':
                 $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
                 return $hook['content'];
-                break;
 
             // define a container block
             case 'container':
@@ -933,7 +1091,7 @@
             // make valid href to specific buttons
             if (in_array($attrib['command'], rcmail::$main_tasks)) {
                 $attrib['href']    = $this->app->url(array('task' => $attrib['command']));
-                $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", rcmail::JS_OBJECT_NAME, $attrib['command']);
+                $attrib['onclick'] = sprintf("return %s.command('switch-task','%s',this,event)", rcmail::JS_OBJECT_NAME, $attrib['command']);
             }
             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
                 $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
@@ -941,7 +1099,7 @@
             else if (in_array($attrib['command'], $a_static_commands)) {
                 $attrib['href'] = $this->app->url(array('action' => $attrib['command']));
             }
-            else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
+            else if (($attrib['command'] == 'permaurl' || $attrib['command'] == 'extwin') && !empty($this->env['permaurl'])) {
               $attrib['href'] = $this->env['permaurl'];
             }
         }
@@ -956,7 +1114,7 @@
         }
         else if ($command && !$attrib['onclick']) {
             $attrib['onclick'] = sprintf(
-                "return %s.command('%s','%s',this)",
+                "return %s.command('%s','%s',this,event)",
                 rcmail::JS_OBJECT_NAME,
                 $command,
                 $attrib['prop']
@@ -1201,13 +1359,7 @@
             $output = substr_replace($output, $css, $pos, 0);
         }
 
-        $this->base_path = $base_path;
-
-        // correct absolute paths in images and other tags
-        // add timestamp to .js and .css filename
-        $output = preg_replace_callback(
-            '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
-            array($this, 'file_callback'), $output);
+        $output = $this->parse_with_globals($this->fix_paths($output));
 
         // trigger hook with final HTML content to be sent
         $hook = $this->app->plugins->exec_hook("send_page", array('content' => $output));
@@ -1223,27 +1375,30 @@
 
 
     /**
-     * Callback function for preg_replace_callback in write()
+     * Returns iframe object, registers some related env variables
      *
-     * @return string Parsed string
+     * @param array $attrib HTML attributes
+     * @param boolean $is_contentframe Register this iframe as the 'contentframe' gui object
+     * @return string IFRAME element
      */
-    protected function file_callback($matches)
+    public function frame($attrib, $is_contentframe = false)
     {
-	    $file = $matches[3];
+        static $idcount = 0;
 
-        // correct absolute paths
-	    if ($file[0] == '/') {
-	        $file = $this->base_path . $file;
+        if (!$attrib['id']) {
+            $attrib['id'] = 'rcmframe' . ++$idcount;
         }
 
-        // add file modification timestamp
-	    if (preg_match('/\.(js|css)$/', $file)) {
-            if ($fs = @filemtime($file)) {
-                $file .= '?s=' . $fs;
-            }
+        $attrib['name'] = $attrib['id'];
+        $attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif';
+
+        // register as 'contentframe' object
+        if ($is_contentframe || $attrib['contentframe']) {
+            $this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']);
+            $this->set_env('blankpage', $attrib['src']);
         }
 
-	    return $matches[1] . '=' . $matches[2] . $file . $matches[4];
+        return html::iframe($attrib);
     }
 
 
@@ -1260,6 +1415,10 @@
     {
       if ($this->framed || !empty($_REQUEST['_framed'])) {
         $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
+        $hidden = $hiddenfield->show();
+      }
+      if ($this->env['extwin']) {
+        $hiddenfield = new html_hiddenfield(array('name' => '_extwin', 'value' => '1'));
         $hidden = $hiddenfield->show();
       }
 
@@ -1355,6 +1514,9 @@
         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
             $url = $_SERVER['QUERY_STRING'];
 
+        // Disable autocapitalization on iPad/iPhone (#1488609)
+        $attrib['autocapitalize'] = 'off';
+
         // set atocomplete attribute
         $user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
         $host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
@@ -1363,7 +1525,6 @@
         $input_task   = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
         $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
-        $input_dst    = new html_hiddenfield(array('name' => '_dstactive', 'id' => 'rcmlogindst', 'value' => '_default_'));
         $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
         $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser')
             + $attrib + $user_attrib);
@@ -1415,7 +1576,6 @@
         $out  = $input_task->show();
         $out .= $input_action->show();
         $out .= $input_tzone->show();
-        $out .= $input_dst->show();
         $out .= $input_url->show();
         $out .= $table->show();
 
@@ -1427,6 +1587,9 @@
         if (empty($attrib['form'])) {
             $out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
         }
+
+        // include script for timezone detection
+        $this->include_script('jstz.min.js');
 
         return $out;
     }
@@ -1485,7 +1648,7 @@
         if (empty($attrib['form'])) {
             $out = $this->form_tag(array(
                 'name' => "rcmqsearchform",
-                'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search');return false;",
+                'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search'); return false",
                 'style' => "display:inline"),
                 $out);
         }
@@ -1564,16 +1727,20 @@
             'GB2312'       => 'GB2312 ('.$this->app->gettext('chinese').')',
         );
 
-        if (!empty($_POST['_charset']))
-	        $set = $_POST['_charset'];
-	    else if (!empty($attrib['selected']))
-	        $set = $attrib['selected'];
-	    else
-	        $set = $this->get_charset();
+        if (!empty($_POST['_charset'])) {
+            $set = $_POST['_charset'];
+        }
+        else if (!empty($attrib['selected'])) {
+            $set = $attrib['selected'];
+        }
+        else {
+            $set = $this->get_charset();
+        }
 
-	    $set = strtoupper($set);
-	    if (!isset($charsets[$set]))
-	        $charsets[$set] = $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));

--
Gitblit v1.9.1