From 60226a75d8e4a3ee9504da6eab6d8f329bb32e7b Mon Sep 17 00:00:00 2001
From: Thomas Bruederli
Date: Tue, 27 Nov 2012 12:13:33 +0100
Subject: Separate the very application-specific output classes from the
Roundcube framework; add autoloader for rmail* classes
---
program/include/bc.php | 4 +-
program/include/iniset.php | 22 +-
program/include/rcmail.php | 20 +-
program/include/rcmail_output.php | 120 ++
program/include/rcmail_output_html.php | 1775 ++++++++++++++++++++++++++++
program/include/rcmail_output_json.php | 258 ++++
program/include/rcmail_string_replacer.php | 54 +
7 files changed, 2238 insertions(+), 15 deletions(-)
create mode 100644 program/include/rcmail_output.php
create mode 100644 program/include/rcmail_output_html.php
create mode 100644 program/include/rcmail_output_json.php
create mode 100644 program/include/rcmail_string_replacer.php
(limited to 'program/include')
diff --git a/program/include/bc.php b/program/include/bc.php
index b589135ff..40524be50 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -31,7 +31,7 @@ define('RCUBE_INPUT_GET', rcube_utils::INPUT_GET);
define('RCUBE_INPUT_POST', rcube_utils::INPUT_POST);
define('RCUBE_INPUT_GPC', rcube_utils::INPUT_GPC);
-define('JS_OBJECT_NAME', rcmail::JS_OBJECT_NAME);
+define('JS_OBJECT_NAME', rcmail_output::JS_OBJECT_NAME);
define('RCMAIL_CHARSET', RCUBE_CHARSET);
function get_table_name($table)
@@ -126,7 +126,7 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
- return rcube_utils::get_edit_field($col, $value, $attrib, $type);
+ return rcube_output::get_edit_field($col, $value, $attrib, $type);
}
function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 25ae0189b..be71fc084 100644
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -72,8 +72,26 @@ if (set_include_path($include_path) === false) {
// include Roundcube Framework
require_once 'Roundcube/bootstrap.php';
-// backward compatybility (to be removed)
-require_once INSTALL_PATH . 'program/include/rcmail.php';
+// register autoloader for rcmail app classes
+spl_autoload_register('rcmail_autoload');
// backward compatybility (to be removed)
require_once INSTALL_PATH . 'program/include/bc.php';
+
+
+/**
+ * PHP5 autoloader routine for dynamic class loading
+ */
+function rcmail_autoload($classname)
+{
+ if (strpos($classname, 'rcmail') === 0) {
+ $filepath = INSTALL_PATH . "program/include/$classname.php";
+ if (is_readable($filepath)) {
+ include_once $filepath;
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 3a8c62cec..8e01a2155 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -56,8 +56,6 @@ class rcmail extends rcube
private $action_map = array();
- const JS_OBJECT_NAME = 'rcmail';
-
const ERROR_STORAGE = -2;
const ERROR_INVALID_REQUEST = 1;
const ERROR_INVALID_HOST = 2;
@@ -321,17 +319,17 @@ class rcmail extends rcube
/**
* Init output object for GUI and add common scripts.
- * This will instantiate a rcube_output_html object and set
+ * This will instantiate a rcmail_output_html object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
- * @return rcube_output_html Reference to HTML output object
+ * @return rcube_output Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
- if (!($this->output instanceof rcube_output_html))
- $this->output = new rcube_output_html($this->task, $framed);
+ if (!($this->output instanceof rcmail_output_html))
+ $this->output = new rcmail_output_html($this->task, $framed);
// set refresh interval
$this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0));
@@ -357,12 +355,12 @@ class rcmail extends rcube
/**
* Create an output object for JSON responses
*
- * @return rcube_output_json Reference to JSON output object
+ * @return rcube_output Reference to JSON output object
*/
public function json_init()
{
- if (!($this->output instanceof rcube_output_json))
- $this->output = new rcube_output_json($this->task);
+ if (!($this->output instanceof rcmail_output_json))
+ $this->output = new rcmail_output_json($this->task);
return $this->output;
}
@@ -1566,7 +1564,7 @@ class rcmail extends rcube
$html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
$link_attrib = $folder['virtual'] ? array() : array(
'href' => $this->url(array('_mbox' => $folder['id'])),
- 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail::JS_OBJECT_NAME, $js_name),
+ 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
);
@@ -1579,7 +1577,7 @@ class rcmail extends rcube
(!empty($folder['folders']) ? html::div(array(
'class' => ($is_collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute",
- 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail::JS_OBJECT_NAME, $js_name)
+ 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail_output::JS_OBJECT_NAME, $js_name)
), ' ') : ''));
$jslist[$folder_id] = array(
diff --git a/program/include/rcmail_output.php b/program/include/rcmail_output.php
new file mode 100644
index 000000000..36512ad48
--- /dev/null
+++ b/program/include/rcmail_output.php
@@ -0,0 +1,120 @@
+ |
+ | Author: Aleksander Machniak |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Class for output generation
+ *
+ * @package Core
+ * @subpackage View
+ */
+abstract class rcmail_output extends rcube_output
+{
+ const JS_OBJECT_NAME = 'rcmail';
+
+ public $type = 'html';
+ public $ajax_call = false;
+ public $framed = false;
+
+ protected $pagetitle = '';
+ protected $object_handlers = array();
+
+
+ /**
+ * Object constructor
+ */
+ public function __construct($task = null, $framed = false)
+ {
+ parent::__construct();
+ }
+
+
+ /**
+ * Setter for page title
+ *
+ * @param string $title Page title
+ */
+ public function set_pagetitle($title)
+ {
+ $this->pagetitle = $title;
+ }
+
+
+ /**
+ * Getter for the current skin path property
+ */
+ public function get_skin_path()
+ {
+ return $this->config->get('skin_path');
+ }
+
+
+ /**
+ * Delete all stored env variables and commands
+ */
+ public function reset()
+ {
+ parent::reset();
+
+ $this->object_handlers = array();
+ $this->pagetitle = '';
+ }
+
+
+ /**
+ * Call a client method
+ *
+ * @param string Method to call
+ * @param ... Additional arguments
+ */
+ abstract function command();
+
+
+ /**
+ * Add a localized label to the client environment
+ */
+ abstract function add_label();
+
+
+ /**
+ * Register a template object handler
+ *
+ * @param string Object name
+ * @param string Function name to call
+ * @return void
+ */
+ public function add_handler($obj, $func)
+ {
+ $this->object_handlers[$obj] = $func;
+ }
+
+
+ /**
+ * Register a list of template object handlers
+ *
+ * @param array Hash array with object=>handler pairs
+ * @return void
+ */
+ public function add_handlers($arr)
+ {
+ $this->object_handlers = array_merge($this->object_handlers, $arr);
+ }
+
+}
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
new file mode 100644
index 000000000..1290e173e
--- /dev/null
+++ b/program/include/rcmail_output_html.php
@@ -0,0 +1,1775 @@
+ |
+ +-----------------------------------------------------------------------+
+*/
+
+
+/**
+ * Class to create HTML page output using a skin template
+ *
+ * @package Core
+ * @subpackage View
+ */
+class rcmail_output_html extends rcmail_output
+{
+ public $type = 'html';
+
+ protected $message = null;
+ protected $js_env = array();
+ protected $js_labels = array();
+ protected $js_commands = array();
+ protected $skin_paths = array();
+ protected $template_name;
+ protected $scripts_path = '';
+ protected $script_files = array();
+ protected $css_files = array();
+ protected $scripts = array();
+ protected $default_template = "\n\n\n";
+ protected $header = '';
+ protected $footer = '';
+ protected $body = '';
+ protected $base_path = '';
+
+ // deprecated names of templates used before 0.5
+ protected $deprecated_templates = array(
+ 'contact' => 'showcontact',
+ 'contactadd' => 'addcontact',
+ 'contactedit' => 'editcontact',
+ 'identityedit' => 'editidentity',
+ 'messageprint' => 'printmessage',
+ );
+
+ /**
+ * Constructor
+ *
+ * @todo Replace $this->config with the real rcube_config object
+ */
+ public function __construct($task = null, $framed = false)
+ {
+ parent::__construct();
+
+ //$this->framed = $framed;
+ $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)
+ $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 '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
+
+ // don't wait for page onload. Call init at the bottom of the page (delayed)
+ $this->add_script(self::JS_OBJECT_NAME.'.init();', 'docready');
+
+ $this->scripts_path = 'program/js/';
+ $this->include_script('jquery.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'),
+ 'aboutcontent' => array($this, 'about_content'),
+ ));
+ }
+
+
+ /**
+ * Set environment variable
+ *
+ * @param string Property name
+ * @param mixed Property value
+ * @param boolean True if this property should be added to client environment
+ */
+ public function set_env($name, $value, $addtojs = true)
+ {
+ $this->env[$name] = $value;
+ if ($addtojs || isset($this->js_env[$name])) {
+ $this->js_env[$name] = $value;
+ }
+ }
+
+
+ /**
+ * Getter for the current page title
+ *
+ * @return string The page title
+ */
+ protected function get_pagetitle()
+ {
+ if (!empty($this->pagetitle)) {
+ $title = $this->pagetitle;
+ }
+ else if ($this->env['task'] == 'login') {
+ $title = $this->app->gettext(array(
+ 'name' => 'welcome',
+ 'vars' => array('product' => $this->config->get('product_name')
+ )));
+ }
+ else {
+ $title = ucfirst($this->env['task']);
+ }
+
+ return $title;
+ }
+
+
+ /**
+ * Set skin
+ */
+ public function set_skin($skin)
+ {
+ $valid = false;
+
+ if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin)) {
+ $skin_path = 'skins/'.$skin;
+ $valid = true;
+ }
+ else {
+ $skin_path = $this->config->get('skin_path');
+ if (!$skin_path) {
+ $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']);
+ }
+ }
+
+
+ /**
+ * Check if a specific template exists
+ *
+ * @param string Template name
+ * @return boolean True if template exists
+ */
+ public function template_exists($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;
+ }
+
+
+ /**
+ * Register a GUI object to the client script
+ *
+ * @param string Object name
+ * @param string Object ID
+ * @return void
+ */
+ public function add_gui_object($obj, $id)
+ {
+ $this->add_script(self::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
+ }
+
+
+ /**
+ * Call a client method
+ *
+ * @param string Method to call
+ * @param ... Additional arguments
+ */
+ public function command()
+ {
+ $cmd = func_get_args();
+ if (strpos($cmd[0], 'plugin.') !== false)
+ $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
+ else
+ $this->js_commands[] = $cmd;
+ }
+
+
+ /**
+ * Add a localized label to the client environment
+ */
+ public function add_label()
+ {
+ $args = func_get_args();
+ if (count($args) == 1 && is_array($args[0]))
+ $args = $args[0];
+
+ foreach ($args as $name) {
+ $this->js_labels[$name] = $this->app->gettext($name);
+ }
+ }
+
+
+ /**
+ * Invoke display_message command
+ *
+ * @param string $message Message to display
+ * @param string $type Message type [notice|confirm|error]
+ * @param array $vars Key-value pairs to be replaced in localized text
+ * @param boolean $override Override last set message
+ * @param int $timeout Message display time in seconds
+ * @uses self::command()
+ */
+ public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
+ {
+ if ($override || !$this->message) {
+ if ($this->app->text_exists($message)) {
+ if (!empty($vars))
+ $vars = array_map('Q', $vars);
+ $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
+ }
+ else
+ $msgtext = $message;
+
+ $this->message = $message;
+ $this->command('display_message', $msgtext, $type, $timeout * 1000);
+ }
+ }
+
+
+ /**
+ * Delete all stored env variables and commands
+ */
+ public function reset()
+ {
+ parent::reset();
+ $this->js_env = array();
+ $this->js_labels = array();
+ $this->js_commands = array();
+ $this->script_files = array();
+ $this->scripts = array();
+ $this->header = '';
+ $this->footer = '';
+ $this->body = '';
+ }
+
+
+ /**
+ * Redirect to a certain url
+ *
+ * @param mixed $p Either a string with the action or url parameters as key-value pairs
+ * @param int $delay Delay in seconds
+ */
+ public function redirect($p = array(), $delay = 1)
+ {
+ if ($this->env['extwin'])
+ $p['extwin'] = 1;
+ $location = $this->app->url($p);
+ header('Location: ' . $location);
+ exit;
+ }
+
+
+ /**
+ * Send the request output to the client.
+ * This will either parse a skin tempalte or send an AJAX response
+ *
+ * @param string Template name
+ * @param boolean True if script should terminate (default)
+ */
+ public function send($templ = null, $exit = true)
+ {
+ if ($templ != 'iframe') {
+ // prevent from endless loops
+ if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
+ rcube::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;
+ }
+ }
+
+
+ /**
+ * Process template and write to stdOut
+ *
+ * @param string $template HTML template content
+ */
+ public function write($template = '')
+ {
+ // unlock interface after iframe load
+ $unlock = preg_replace('/[^a-z0-9]/i', '', $_REQUEST['_unlock']);
+ if ($this->framed) {
+ array_unshift($this->js_commands, array('set_busy', false, null, $unlock));
+ }
+ else if ($unlock) {
+ array_unshift($this->js_commands, array('hide_message', $unlock));
+ }
+
+ if (!empty($this->script_files))
+ $this->set_env('request_token', $this->app->get_request_token());
+
+ // 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');
+
+ // send clickjacking protection headers
+ $iframe = $this->framed || !empty($_REQUEST['_framed']);
+ if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')))
+ header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
+
+ // call super method
+ $this->_write($template, $this->config->get('skin_path'));
+ }
+
+
+ /**
+ * Parse a specific skin template and deliver to stdout (or return)
+ *
+ * @param string Template name
+ * @param boolean Exit script
+ * @param boolean Don't write to stdout, return parsed content instead
+ *
+ * @link http://php.net/manual/en/function.exit.php
+ */
+ function parse($name = 'main', $exit = true, $write = true)
+ {
+ $plugin = false;
+ $realname = $name;
+ $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');
+
+ // 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';
+ $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;
+ }
+ }
+
+ // read template file
+ 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, $write);
+ 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' => $realname, 'content' => $output));
+
+ // save some memory
+ $output = $hook['content'];
+ unset($hook['content']);
+
+ // make sure all
') {
+ $hpos++;
+ }
+ $hpos++;
+ }
+ $page_header = "\n$page_title\n$page_header\n\n";
+ }
+
+ // add page hader
+ if ($hpos) {
+ $output = substr_replace($output, $page_header, $hpos, 0);
+ }
+ else {
+ $output = $page_header . $output;
+ }
+
+ // add page footer
+ if (($fpos = strripos($output, '