diff options
46 files changed, 5335 insertions, 4411 deletions
@@ -1,6 +1,17 @@ CHANGELOG Roundcube Webmail =========================== +- Roundcube Framework: + Add possibility to replace IMAP driver with custom class + Add IMAP auto-connection feature, improving performance with caching enabled + Replace imap_init hook with storage_init (with additional 'driver' argument) + Improved performance by caching IMAP server's capabilities in session + Unified global functions naming (rcube_ prefix) + Move global functions from main.inc and rcube_shared.inc into classes + Better classes separation + +RELEASE 0.8-rc +---------------- - Set flexible width to login form fields (#1488418) - Fix re-draw bug on list columns change in IE8 (#1487822) - Allow mass-removal of addresses from a group (#1487748) diff --git a/bin/msgexport.sh b/bin/msgexport.sh index c876f5f10..e6c180188 100755 --- a/bin/msgexport.sh +++ b/bin/msgexport.sh @@ -34,7 +34,7 @@ function export_mailbox($mbox, $filename) $IMAP->set_folder($mbox); $index = $IMAP->index($mbox, null, 'ASC'); - $count = $index->countMessages(); + $count = $index->count(); $index = $index->get(); vputs("Getting message list of {$mbox}..."); @@ -2,7 +2,7 @@ /* +-------------------------------------------------------------------------+ | Roundcube Webmail IMAP Client | - | Version 0.8-svn | + | Version 0.9-svn | | | | Copyright (C) 2005-2012, The Roundcube Dev Team | | | @@ -45,7 +45,7 @@ require_once 'program/include/iniset.php'; $RCMAIL = rcmail::get_instance(); // Make the whole PHP output non-cacheable (#1487797) -send_nocacheing_headers(); +$RCMAIL->output->nocacheing_headers(); // turn on output buffering ob_start(); @@ -67,14 +67,14 @@ if ($err_str = $RCMAIL->db->is_error()) { } // error steps -if ($RCMAIL->action=='error' && !empty($_GET['_code'])) { +if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) { raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE); } // check if https is required (for login) and redirect if necessary if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) { $https_port = is_bool($force_https) ? 443 : $force_https; - if (!rcube_https_check($https_port)) { + if (!rcube_ui::https_check($https_port)) { $host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']); $host .= ($https_port != 443 ? ':' . $https_port : ''); header('Location: https://' . $host . $_SERVER['REQUEST_URI']); @@ -89,15 +89,15 @@ $RCMAIL->action = $startup['action']; // try to log in if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { - $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(RCUBE_INPUT_POST, 'login'); + $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_ui::INPUT_POST, 'login'); // purge the session in case of new login when a session already exists $RCMAIL->kill_session(); $auth = $RCMAIL->plugins->exec_hook('authenticate', array( 'host' => $RCMAIL->autoselect_host(), - 'user' => trim(get_input_value('_user', RCUBE_INPUT_POST)), - 'pass' => get_input_value('_pass', RCUBE_INPUT_POST, true, + 'user' => trim(rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST)), + 'pass' => rcube_ui::get_input_value('_pass', rcube_ui::INPUT_POST, true, $RCMAIL->config->get('password_charset', 'ISO-8859-1')), 'cookiecheck' => true, 'valid' => $request_valid, @@ -119,11 +119,11 @@ if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { $RCMAIL->session->set_auth_cookie(); // log successful login - rcmail_log_login(); + $RCMAIL->log_login(); // restore original request parameters $query = array(); - if ($url = get_input_value('_url', RCUBE_INPUT_POST)) { + if ($url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST)) { parse_str($url, $query); // prevent endless looping on login page @@ -149,7 +149,7 @@ if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') { } // end session (after optional referer check) -else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcube_check_referer())) { +else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcmail::check_referer())) { $userdata = array( 'user' => $_SESSION['username'], 'host' => $_SESSION['storage_host'], @@ -172,7 +172,8 @@ else if ($RCMAIL->task != 'login' && $_SESSION['user_id'] && $RCMAIL->action != // not logged in -> show login page if (empty($RCMAIL->user->ID)) { // log session failures - if (($task = get_input_value('_task', RCUBE_INPUT_GPC)) && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) { + $task = rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC); + if ($task && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) { $RCMAIL->session->log("Aborted session " . $sess_id . "; no valid session data found"); $session_error = true; } @@ -208,7 +209,7 @@ else { // check client X-header to verify request origin if ($OUTPUT->ajax_call) { - if (rc_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) { + if (rcube_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) { header('HTTP/1.1 403 Forbidden'); die("Invalid Request"); } @@ -220,7 +221,7 @@ else { } // check referer if configured - if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcube_check_referer()) { + if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcmail::check_referer()) { raise_error(array( 'code' => 403, 'type' => 'php', diff --git a/installer/index.php b/installer/index.php index 65e84a3b4..842b90054 100644 --- a/installer/index.php +++ b/installer/index.php @@ -53,6 +53,8 @@ $include_path .= ini_get('include_path'); set_include_path($include_path); require_once 'utils.php'; +require_once 'rcube_shared.inc'; +// deprecated aliases (to be removed) require_once 'main.inc'; session_start(); diff --git a/installer/utils.php b/installer/utils.php index d559df14e..ca2577c14 100644 --- a/installer/utils.php +++ b/installer/utils.php @@ -45,26 +45,16 @@ function __autoload($classname) include_once $filename. '.php'; } - -/** - * Fake internal error handler to catch errors - */ -function raise_error($p) -{ - $rci = rcube_install::get_instance(); - $rci->raise_error($p); -} - /** * Local callback function for PEAR errors */ -function rcube_pear_error($err) +function __pear_error($err) { - raise_error(array( + rcmail::raise_error(array( 'code' => $err->getCode(), 'message' => $err->getMessage(), )); } // set PEAR error handling (will also load the PEAR main class) -PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error'); +PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, '__pear_error'); diff --git a/program/include/clisetup.php b/program/include/clisetup.php index c5f8dd1cc..22ffbbc92 100644 --- a/program/include/clisetup.php +++ b/program/include/clisetup.php @@ -55,7 +55,7 @@ function get_opt($aliases=array()) continue; $args[$key] = preg_replace(array('/^["\']/', '/["\']$/'), '', $value); - + if ($alias = $aliases[$key]) $args[$alias] = $args[$key]; } diff --git a/program/include/html.php b/program/include/html.php index 0e89d778f..305a39781 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -277,7 +277,7 @@ class html $attrib_arr = array(); foreach ($attrib as $key => $value) { // skip size if not numeric - if (($key=='size' && !is_numeric($value))) { + if ($key == 'size' && !is_numeric($value)) { continue; } @@ -297,17 +297,57 @@ class html $attrib_arr[] = $key . '="' . $key . '"'; } } - else if ($key=='value') { - $attrib_arr[] = $key . '="' . Q($value, 'strict', false) . '"'; - } else { - $attrib_arr[] = $key . '="' . Q($value) . '"'; + $attrib_arr[] = $key . '="' . self::quote($value) . '"'; } } + return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : ''; } + + /** + * Convert a HTML attribute string attributes to an associative array (name => value) + * + * @param string Input string + * @return array Key-value pairs of parsed attributes + */ + public static function parse_attrib_string($str) + { + $attrib = array(); + $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui'; + + preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER); + + // convert attributes to an associative array (name => value) + if ($regs) { + foreach ($regs as $attr) { + $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]); + } + } + + return $attrib; + } + + /** + * Replacing specials characters in html attribute value + * + * @param string $str Input string + * + * @return string The quoted string + */ + public static function quote($str) + { + $str = htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET); + + // avoid douple quotation of & + // @TODO: get rid of it? + $str = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $str); + + return $str; + } } + /** * Class to create an HTML input field * @@ -317,9 +357,11 @@ class html_inputfield extends html { protected $tagname = 'input'; protected $type = 'text'; - protected $allowed = array('type','name','value','size','tabindex', + protected $allowed = array( + 'type','name','value','size','tabindex', 'autocomplete','checked','onchange','onclick','disabled','readonly', - 'spellcheck','results','maxlength','src','multiple','placeholder'); + 'spellcheck','results','maxlength','src','multiple','placeholder', + ); /** * Object constructor @@ -517,11 +559,11 @@ class html_textarea extends html } if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) { - $value = Q($value, 'strict', false); + $value = self::quote($value); } return self::tag($this->tagname, $this->attrib, $value, - array_merge(self::$common_attrib, $this->allowed)); + array_merge(self::$common_attrib, $this->allowed)); } } @@ -550,7 +592,7 @@ class html_select extends html protected $options = array(); protected $allowed = array('name','size','tabindex','autocomplete', 'multiple','onchange','disabled','rel'); - + /** * Add a new option to this drop-down * @@ -591,8 +633,9 @@ class html_select extends html 'selected' => (in_array($option['value'], $select, true) || in_array($option['text'], $select, true)) ? 1 : null); - $this->content .= self::tag('option', $attr, Q($option['text'])); + $this->content .= self::tag('option', $attr, self::quote($option['text'])); } + return parent::show(); } } @@ -803,4 +846,3 @@ class html_table extends html } } - diff --git a/program/include/iniset.php b/program/include/iniset.php index 5feca7d9c..3715c21ef 100644 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -40,7 +40,7 @@ foreach ($crit_opts as $optname => $optval) { } // application constants -define('RCMAIL_VERSION', '0.8-svn'); +define('RCMAIL_VERSION', '0.9-svn'); define('RCMAIL_CHARSET', 'UTF-8'); define('JS_OBJECT_NAME', 'rcmail'); define('RCMAIL_START', microtime(true)); @@ -53,11 +53,6 @@ if (!defined('RCMAIL_CONFIG_DIR')) { define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config'); } -// make sure path_separator is defined -if (!defined('PATH_SEPARATOR')) { - define('PATH_SEPARATOR', (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') ? ';' : ':'); -} - // RC include folders MUST be included FIRST to avoid other // possible not compatible libraries (i.e PEAR) to be included // instead the ones provided by RC @@ -80,59 +75,14 @@ if (extension_loaded('mbstring')) { @mb_regex_encoding(RCMAIL_CHARSET); } -/** - * Use PHP5 autoload for dynamic class loading - * - * @todo Make Zend, PEAR etc play with this - * @todo Make our classes conform to a more straight forward CS. - */ -function rcube_autoload($classname) -{ - $filename = preg_replace( - array( - '/MDB2_(.+)/', - '/Mail_(.+)/', - '/Net_(.+)/', - '/Auth_(.+)/', - '/^html_.+/', - '/^utf8$/', - ), - array( - 'MDB2/\\1', - 'Mail/\\1', - 'Net/\\1', - 'Auth/\\1', - 'html', - 'utf8.class', - ), - $classname - ); - - if ($fp = @fopen("$filename.php", 'r', true)) { - fclose($fp); - include_once("$filename.php"); - return true; - } - - return false; -} +// include global functions +require_once INSTALL_PATH . 'program/include/rcube_shared.inc'; +// Register autoloader spl_autoload_register('rcube_autoload'); -/** - * Local callback function for PEAR errors - */ -function rcube_pear_error($err) -{ - error_log(sprintf("%s (%s): %s", - $err->getMessage(), - $err->getCode(), - $err->getUserinfo()), 0); -} - // set PEAR error handling (will also load the PEAR main class) PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error'); -// include global functions +// backward compatybility (to be removed) require_once INSTALL_PATH . 'program/include/main.inc'; -require_once INSTALL_PATH . 'program/include/rcube_shared.inc'; diff --git a/program/include/main.inc b/program/include/main.inc index 3f502753e..791e657b4 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -5,14 +5,14 @@ | program/include/main.inc | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2011, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | | See the README file for a full license statement. | | | | PURPOSE: | - | Provide basic functions for the webmail package | + | Provide deprecated functions aliases for backward compatibility | | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | @@ -23,2224 +23,324 @@ */ /** - * Roundcube Webmail common functions + * Roundcube Webmail deprecated functions * * @package Core * @author Thomas Bruederli <roundcube@gmail.com> */ -require_once INSTALL_PATH . 'program/include/rcube_shared.inc'; +// constants for input reading +define('RCUBE_INPUT_GET', rcube_ui::INPUT_GET); +define('RCUBE_INPUT_POST', rcube_ui::INPUT_POST); +define('RCUBE_INPUT_GPC', rcube_ui::INPUT_GPC); -// define constannts for input reading -define('RCUBE_INPUT_GET', 0x0101); -define('RCUBE_INPUT_POST', 0x0102); -define('RCUBE_INPUT_GPC', 0x0103); - - -/** - * Return correct name for a specific database table - * - * @param string Table name - * @return string Translated table name - */ function get_table_name($table) - { - global $CONFIG; - - // return table name if configured - $config_key = 'db_table_'.$table; - - if (strlen($CONFIG[$config_key])) - return $CONFIG[$config_key]; - - return $table; - } - +{ + return rcmail::get_instance()->db->table_name($table); +} -/** - * Return correct name for a specific database sequence - * (used for Postgres only) - * - * @param string Secuence name - * @return string Translated sequence name - */ function get_sequence_name($sequence) - { - // return sequence name if configured - $config_key = 'db_sequence_'.$sequence; - $opt = rcmail::get_instance()->config->get($config_key); - - if (!empty($opt)) - return $opt; - - return $sequence; - } - +{ + return rcmail::get_instance()->db->sequence_name($sequence); +} -/** - * Get localized text in the desired language - * It's a global wrapper for rcmail::gettext() - * - * @param mixed Named parameters array or label name - * @param string Domain to search in (e.g. plugin name) - * @return string Localized text - * @see rcmail::gettext() - */ function rcube_label($p, $domain=null) { - return rcmail::get_instance()->gettext($p, $domain); + return rcmail::get_instance()->gettext($p, $domain); } - -/** - * Global wrapper of rcmail::text_exists() - * to check whether a text label is defined - * - * @see rcmail::text_exists() - */ function rcube_label_exists($name, $domain=null, &$ref_domain = null) { - return rcmail::get_instance()->text_exists($name, $domain, $ref_domain); + return rcmail::get_instance()->text_exists($name, $domain, $ref_domain); } - -/** - * Overwrite action variable - * - * @param string New action value - */ function rcmail_overwrite_action($action) - { - $app = rcmail::get_instance(); - $app->action = $action; - $app->output->set_env('action', $action); - } - +{ + rcmail::get_instance()->overwrite_action($action); +} -/** - * Compose an URL for a specific action - * - * @param string Request action - * @param array More URL parameters - * @param string Request task (omit if the same) - * @return The application URL - */ function rcmail_url($action, $p=array(), $task=null) { - $app = rcmail::get_instance(); - return $app->url((array)$p + array('_action' => $action, 'task' => $task)); + return rcube_ui::url($action, $p, $task); } - -/** - * Garbage collector function for temp files. - * Remove temp files older than two days - */ function rcmail_temp_gc() { - $rcmail = rcmail::get_instance(); - - $tmp = unslashify($rcmail->config->get('temp_dir')); - $expire = mktime() - 172800; // expire in 48 hours - - if ($dir = opendir($tmp)) { - while (($fname = readdir($dir)) !== false) { - if ($fname{0} == '.') - continue; - - if (filemtime($tmp.'/'.$fname) < $expire) - @unlink($tmp.'/'.$fname); - } - - closedir($dir); - } + $rcmail = rcmail::get_instance()->temp_gc(); } - -// Deprecated function rcube_charset_convert($str, $from, $to=NULL) { return rcube_charset::convert($str, $from, $to); } - -// Deprecated function rc_detect_encoding($string, $failover='') { return rcube_charset::detect($string, $failover); } - -// Deprecated function rc_utf8_clean($input) { return rcube_charset::clean($input); } - -/** - * Convert a variable into a javascript object notation - * - * @param mixed Input value - * @return string Serialized JSON string - */ function json_serialize($input) { - $input = rcube_charset::clean($input); - - // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences - // that's why we have @ here - return @json_encode($input); + return rcube_output::json_serialize($input); } - -/** - * Replacing specials characters to a specific encoding type - * - * @param string Input string - * @param string Encoding type: text|html|xml|js|url - * @param string Replace mode for tags: show|replace|remove - * @param boolean Convert newlines - * @return string The quoted string - */ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) - { - static $html_encode_arr = false; - static $js_rep_table = false; - static $xml_rep_table = false; - - if (!$enctype) - $enctype = $OUTPUT->type; - - // encode for HTML output - if ($enctype=='html') - { - if (!$html_encode_arr) - { - $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS); - unset($html_encode_arr['?']); - } - - $ltpos = strpos($str, '<'); - $encode_arr = $html_encode_arr; - - // don't replace quotes and html tags - if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false) - { - unset($encode_arr['"']); - unset($encode_arr['<']); - unset($encode_arr['>']); - unset($encode_arr['&']); - } - else if ($mode=='remove') - $str = strip_tags($str); - - $out = strtr($str, $encode_arr); - - // avoid douple quotation of & - $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out); - - return $newlines ? nl2br($out) : $out; - } - - // if the replace tables for XML and JS are not yet defined - if ($js_rep_table===false) - { - $js_rep_table = $xml_rep_table = array(); - $xml_rep_table['&'] = '&'; - - for ($c=160; $c<256; $c++) // can be increased to support more charsets - $xml_rep_table[chr($c)] = "&#$c;"; - - $xml_rep_table['"'] = '"'; - $js_rep_table['"'] = '\\"'; - $js_rep_table["'"] = "\\'"; - $js_rep_table["\\"] = "\\\\"; - // Unicode line and paragraph separators (#1486310) - $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '
'; - $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '
'; - } - - // encode for javascript use - if ($enctype=='js') - return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table)); - - // encode for plaintext - if ($enctype=='text') - return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str); - - if ($enctype=='url') - return rawurlencode($str); - - // encode for XML - if ($enctype=='xml') - return strtr($str, $xml_rep_table); - - // no encoding given -> return original string - return $str; - } - -/** - * Quote a given string. - * Shortcut function for rep_specialchars_output - * - * @return string HTML-quoted string - * @see rep_specialchars_output() - */ -function Q($str, $mode='strict', $newlines=TRUE) - { - return rep_specialchars_output($str, 'html', $mode, $newlines); - } - -/** - * Quote a given string for javascript output. - * Shortcut function for rep_specialchars_output - * - * @return string JS-quoted string - * @see rep_specialchars_output() - */ -function JQ($str) - { - return rep_specialchars_output($str, 'js'); - } - - -/** - * Read input value and convert it for internal use - * Performs stripslashes() and charset conversion if necessary - * - * @param string Field name to read - * @param int Source to get value from (GPC) - * @param boolean Allow HTML tags in field value - * @param string Charset to convert into - * @return string Field value or NULL if not available - */ -function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) { - $value = NULL; - - if ($source == RCUBE_INPUT_GET) { - if (isset($_GET[$fname])) - $value = $_GET[$fname]; - } - else if ($source == RCUBE_INPUT_POST) { - if (isset($_POST[$fname])) - $value = $_POST[$fname]; - } - else if ($source == RCUBE_INPUT_GPC) { - if (isset($_POST[$fname])) - $value = $_POST[$fname]; - else if (isset($_GET[$fname])) - $value = $_GET[$fname]; - else if (isset($_COOKIE[$fname])) - $value = $_COOKIE[$fname]; - } - - return parse_input_value($value, $allow_html, $charset); + return rcube_ui::rep_specialchars_output($str, $enctype, $mode, $newlines); } -/** - * Parse/validate input value. See get_input_value() - * Performs stripslashes() and charset conversion if necessary - * - * @param string Input value - * @param boolean Allow HTML tags in field value - * @param string Charset to convert into - * @return string Parsed value - */ -function parse_input_value($value, $allow_html=FALSE, $charset=NULL) +function Q($str, $mode='strict', $newlines=TRUE) { - global $OUTPUT; - - if (empty($value)) - return $value; - - if (is_array($value)) { - foreach ($value as $idx => $val) - $value[$idx] = parse_input_value($val, $allow_html, $charset); - return $value; - } - - // strip single quotes if magic_quotes_sybase is enabled - if (ini_get('magic_quotes_sybase')) - $value = str_replace("''", "'", $value); - // strip slashes if magic_quotes enabled - else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) - $value = stripslashes($value); - - // remove HTML tags if not allowed - if (!$allow_html) - $value = strip_tags($value); - - $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null; - - // remove invalid characters (#1488124) - if ($output_charset == 'UTF-8') - $value = rc_utf8_clean($value); - - // convert to internal charset - if ($charset && $output_charset) - $value = rcube_charset_convert($value, $output_charset, $charset); - - return $value; + return rcube_ui::Q($str, $mode, $newlines); } -/** - * Convert array of request parameters (prefixed with _) - * to a regular array with non-prefixed keys. - * - * @param int Source to get value from (GPC) - * @return array Hash array with all request parameters - */ -function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action') +function JQ($str) { - $out = array(); - $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST); - foreach ($src as $key => $value) { - $fname = $key[0] == '_' ? substr($key, 1) : $key; - if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) - $out[$fname] = get_input_value($key, $mode); - } - - return $out; + return rcube_ui::JQ($str); } -/** - * Remove all non-ascii and non-word chars - * except ., -, _ - */ -function asciiwords($str, $css_id = false, $replace_with = '') +function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) { - $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : ''); - return preg_replace("/[^$allowed]/i", $replace_with, $str); + return rcube_ui::get_input_value($fname, $source, $allow_html, $charset); } -/** - * Convert the given string into a valid HTML identifier - * Same functionality as done in app.js with rcube_webmail.html_identifier() - */ -function html_identifier($str, $encode=false) +function parse_input_value($value, $allow_html=FALSE, $charset=NULL) { - if ($encode) - return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); - else - return asciiwords($str, true, '_'); + return rcube_ui::parse_input_value($value, $allow_html, $charset); } -/** - * Remove single and double quotes from given string - * - * @param string Input value - * @return string Dequoted string - */ -function strip_quotes($str) +function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action') { - return str_replace(array("'", '"'), '', $str); + return rcube_ui::request2param($mode, $ignore); } - -/** - * Remove new lines characters from given string - * - * @param string Input value - * @return string Stripped string - */ -function strip_newlines($str) +function html_identifier($str, $encode=false) { - return preg_replace('/[\r\n]/', '', $str); + return rcube_ui::html_identifier($str, $encode); } - -/** - * Create a HTML table based on the given data - * - * @param array Named table attributes - * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set - * @param array List of cols to show - * @param string Name of the identifier col - * @return string HTML table code - */ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col) { - global $RCMAIL; - - $table = new html_table(/*array('cols' => count($a_show_cols))*/); - - // add table header - if (!$attrib['noheader']) - foreach ($a_show_cols as $col) - $table->add_header($col, Q(rcube_label($col))); - - $c = 0; - if (!is_array($table_data)) - { - $db = $RCMAIL->get_dbh(); - while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) - { - $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col]))); - - // format each col - foreach ($a_show_cols as $col) - $table->add($col, Q($sql_arr[$col])); - - $c++; - } - } - else { - foreach ($table_data as $row_data) - { - $class = !empty($row_data['class']) ? $row_data['class'] : ''; - - $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class)); - - // format each col - foreach ($a_show_cols as $col) - $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); - - $c++; - } - } - - return $table->show($attrib); + return rcube_ui::table_output($attrib, $table_data, $a_show_cols, $id_col); } - -/** - * Create an edit field for inclusion on a form - * - * @param string col field name - * @param string value field value - * @param array attrib HTML element attributes for field - * @param string type HTML element type (default 'text') - * @return string HTML field definition - */ function rcmail_get_edit_field($col, $value, $attrib, $type='text') { - static $colcounts = array(); - - $fname = '_'.$col; - $attrib['name'] = $fname . ($attrib['array'] ? '[]' : ''); - $attrib['class'] = trim($attrib['class'] . ' ff_' . $col); - - if ($type == 'checkbox') { - $attrib['value'] = '1'; - $input = new html_checkbox($attrib); - } - else if ($type == 'textarea') { - $attrib['cols'] = $attrib['size']; - $input = new html_textarea($attrib); - } - else if ($type == 'select') { - $input = new html_select($attrib); - $input->add('---', ''); - $input->add(array_values($attrib['options']), array_keys($attrib['options'])); - } - else if ($attrib['type'] == 'password') { - $input = new html_passwordfield($attrib); - } - else { - if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') - $attrib['type'] = 'text'; - $input = new html_inputfield($attrib); - } - - // use value from post - if (isset($_POST[$fname])) { - $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true); - $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue; - } - - $out = $input->show($value); - - return $out; + return rcube_ui::get_edit_field($col, $value, $attrib, $type); } - -/** - * Replace all css definitions with #container [def] - * and remove css-inlined scripting - * - * @param string CSS source code - * @param string Container ID to use as prefix - * @return string Modified CSS source - */ function rcmail_mod_css_styles($source, $container_id, $allow_remote=false) - { - $last_pos = 0; - $replacements = new rcube_string_replacer; - - // ignore the whole block if evil styles are detected - $source = rcmail_xss_entity_decode($source); - $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); - $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); - if (preg_match("/$evilexpr/i", $stripped)) - return '/* evil! */'; - - // cut out all contents between { and } - while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { - $styles = substr($source, $pos+1, $pos2-($pos+1)); - - // check every line of a style block... - if ($allow_remote) { - $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); - foreach ($a_styles as $line) { - $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); - // ... and only allow strict url() values - if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) { - $a_styles = array('/* evil! */'); - break; - } - } - $styles = join(";\n", $a_styles); - } - - $key = $replacements->add($styles); - $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2); - $last_pos = $pos+2; - } - - // remove html comments and add #container to each tag selector. - // also replace body definition because we also stripped off the <body> tag - $styles = preg_replace( - array( - '/(^\s*<!--)|(-->\s*$)/', - '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im', - '/'.preg_quote($container_id, '/').'\s+body/i', - ), - array( - '', - "\\1#$container_id \\2", - $container_id, - ), - $source); - - // put block contents back in - $styles = $replacements->resolve($styles); - - return $styles; - } - - -/** - * Decode escaped entities used by known XSS exploits. - * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples - * - * @param string CSS content to decode - * @return string Decoded string - */ -function rcmail_xss_entity_decode($content) { - $out = html_entity_decode(html_entity_decode($content)); - $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out); - $out = preg_replace('#/\*.*\*/#Ums', '', $out); - return $out; + return rcube_ui::mod_css_styles($source, $container_id, $allow_remote); } - -/** - * preg_replace_callback callback for rcmail_xss_entity_decode_callback - * - * @param array matches result from preg_replace_callback - * @return string decoded entity - */ -function rcmail_xss_entity_decode_callback($matches) -{ - return chr(hexdec($matches[1])); +function rcmail_xss_entity_decode($content) +{ + return rcube_ui::xss_entity_decode($content); } -/** - * Compose a valid attribute string for HTML tags - * - * @param array Named tag attributes - * @param array List of allowed attributes - * @return string HTML formatted attribute string - */ function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style')) - { - // allow the following attributes to be added to the <iframe> tag - $attrib_str = ''; - foreach ($allowed_attribs as $a) - if (isset($attrib[$a])) - $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '"', $attrib[$a])); - - return $attrib_str; - } - - -/** - * Convert a HTML attribute string attributes to an associative array (name => value) - * - * @param string Input string - * @return array Key-value pairs of parsed attributes - */ -function parse_attrib_string($str) - { - $attrib = array(); - preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER); - - // convert attributes to an associative array (name => value) - if ($regs) { - foreach ($regs as $attr) { - $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]); - } - } - - return $attrib; - } - - -/** - * Improved equivalent to strtotime() - * - * @param string Date string - * @return int - */ -function rcube_strtotime($date) { - // check for MS Outlook vCard date format YYYYMMDD - if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) { - return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1])); - } - else if (is_numeric($date)) - return $date; - - // support non-standard "GMTXXXX" literal - $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); - - // if date parsing fails, we have a date in non-rfc format. - // remove token from the end and try again - while ((($ts = @strtotime($date)) === false) || ($ts < 0)) { - $d = explode(' ', $date); - array_pop($d); - if (!$d) break; - $date = implode(' ', $d); - } - - return $ts; + return html::attrib_string($attrib, $allowed_attribs); } - -/** - * Convert the given date to a human readable form - * This uses the date formatting properties from config - * - * @param mixed Date representation (string, timestamp or DateTime object) - * @param string Date format to use - * @param bool Enables date convertion according to user timezone - * - * @return string Formatted date string - */ -function format_date($date, $format=NULL, $convert=true) +function parse_attrib_string($str) { - global $RCMAIL, $CONFIG; - - if (is_object($date) && is_a($date, 'DateTime')) { - $timestamp = $date->format('U'); - } - else { - if (!empty($date)) - $timestamp = rcube_strtotime($date); - - if (empty($timestamp)) - return ''; - - try { - $date = new DateTime("@".$timestamp); - } - catch (Exception $e) { - return ''; - } - } - - if ($convert) { - try { - // convert to the right timezone - $stz = date_default_timezone_get(); - $tz = new DateTimeZone($RCMAIL->config->get('timezone')); - $date->setTimezone($tz); - date_default_timezone_set($tz->getName()); - - $timestamp = $date->format('U'); - } - catch (Exception $e) { - } - } - - // define date format depending on current time - if (!$format) { - $now = time(); - $now_date = getdate($now); - $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']); - $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']); - - if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) { - $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i')); - $today = true; - } - else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now) - $format = $RCMAIL->config->get('date_short', 'D H:i'); - else - $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i'); - } - - // strftime() format - if (preg_match('/%[a-z]+/i', $format)) { - $format = strftime($format, $timestamp); - - if ($convert && $stz) { - date_default_timezone_set($stz); - } - - return $today ? (rcube_label('today') . ' ' . $format) : $format; - } - - // parse format string manually in order to provide localized weekday and month names - // an alternative would be to convert the date() format string to fit with strftime() - $out = ''; - for ($i=0; $i<strlen($format); $i++) { - if ($format[$i]=='\\') // skip escape chars - continue; - - // write char "as-is" - if ($format[$i]==' ' || $format{$i-1}=='\\') - $out .= $format[$i]; - // weekday (short) - else if ($format[$i]=='D') - $out .= rcube_label(strtolower(date('D', $timestamp))); - // weekday long - else if ($format[$i]=='l') - $out .= rcube_label(strtolower(date('l', $timestamp))); - // month name (short) - else if ($format[$i]=='M') - $out .= rcube_label(strtolower(date('M', $timestamp))); - // month name (long) - else if ($format[$i]=='F') - $out .= rcube_label('long'.strtolower(date('M', $timestamp))); - else if ($format[$i]=='x') - $out .= strftime('%x %X', $timestamp); - else - $out .= date($format[$i], $timestamp); - } - - if ($today) { - $label = rcube_label('today'); - // replcae $ character with "Today" label (#1486120) - if (strpos($out, '$') !== false) { - $out = preg_replace('/\$/', $label, $out, 1); - } - else { - $out = $label . ' ' . $out; - } - } - - if ($convert && $stz) { - date_default_timezone_set($stz); - } - - return $out; + return html::parse_attrib_string($str); } - -/** - * Compose a valid representation of name and e-mail address - * - * @param string E-mail address - * @param string Person name - * @return string Formatted string - */ -function format_email_recipient($email, $name='') +function format_date($date, $format=NULL, $convert=true) { - if ($name && $name != $email) { - // Special chars as defined by RFC 822 need to in quoted string (or escaped). - return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email)); - } - - return trim($email); + return rcube_ui::format_date($date, $format, $convert); } - -/** - * Return the mailboxlist in HTML - * - * @param array Named parameters - * @return string HTML code for the gui object - */ function rcmail_mailbox_list($attrib) { - global $RCMAIL; - static $a_mailboxes; - - $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)'); - - // add some labels to client - $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm'); - - $type = $attrib['type'] ? $attrib['type'] : 'ul'; - unset($attrib['type']); - - if ($type=='ul' && !$attrib['id']) - $attrib['id'] = 'rcmboxlist'; - - if (empty($attrib['folder_name'])) - $attrib['folder_name'] = '*'; - - // get mailbox list - $mbox_name = $RCMAIL->storage->get_folder(); - - // build the folders tree - if (empty($a_mailboxes)) { - // get mailbox list - $a_folders = $RCMAIL->storage->list_folders_subscribed('', $attrib['folder_name'], $attrib['folder_filter']); - $delimiter = $RCMAIL->storage->get_hierarchy_delimiter(); - $a_mailboxes = array(); - - foreach ($a_folders as $folder) - rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter); - } - - // allow plugins to alter the folder tree or to localize folder names - $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array( - 'list' => $a_mailboxes, - 'delimiter' => $delimiter, - 'type' => $type, - 'attribs' => $attrib, - )); - - $a_mailboxes = $hook['list']; - $attrib = $hook['attribs']; - - if ($type == 'select') { - $select = new html_select($attrib); - - // add no-selection option - if ($attrib['noselection']) - $select->add(rcube_label($attrib['noselection']), ''); - - rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']); - $out = $select->show($attrib['default']); - } - else { - $js_mailboxlist = array(); - $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); - - $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']); - $RCMAIL->output->set_env('mailboxes', $js_mailboxlist); - $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']); - $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders')); - } - - return $out; + return rcube_ui::folder_list($attrib); } - -/** - * Return the mailboxlist as html_select object - * - * @param array Named parameters - * @return html_select HTML drop-down object - */ -function rcmail_mailbox_select($p = array()) +function rcmail_mailbox_select($attrib = array()) { - global $RCMAIL; - - $p += array('maxlength' => 100, 'realnames' => false); - $a_mailboxes = array(); - $storage = $RCMAIL->get_storage(); - - if (empty($p['folder_name'])) { - $p['folder_name'] = '*'; - } - - if ($p['unsubscribed']) - $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); - else - $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); - - $delimiter = $storage->get_hierarchy_delimiter(); - - foreach ($list as $folder) { - if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) - rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter); - } - - $select = new html_select($p); - - if ($p['noselection']) - $select->add($p['noselection'], ''); - - rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p); - - return $select; -} - - -/** - * Create a hierarchical array of the mailbox list - * @access private - * @return void - */ -function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='') -{ - global $RCMAIL; - - // Handle namespace prefix - $prefix = ''; - if (!$path) { - $n_folder = $folder; - $folder = $RCMAIL->storage->mod_folder($folder); - - if ($n_folder != $folder) { - $prefix = substr($n_folder, 0, -strlen($folder)); - } - } - - $pos = strpos($folder, $delm); - - if ($pos !== false) { - $subFolders = substr($folder, $pos+1); - $currentFolder = substr($folder, 0, $pos); - - // sometimes folder has a delimiter as the last character - if (!strlen($subFolders)) - $virtual = false; - else if (!isset($arrFolders[$currentFolder])) - $virtual = true; - else - $virtual = $arrFolders[$currentFolder]['virtual']; - } - else { - $subFolders = false; - $currentFolder = $folder; - $virtual = false; - } - - $path .= $prefix.$currentFolder; - - if (!isset($arrFolders[$currentFolder])) { - $arrFolders[$currentFolder] = array( - 'id' => $path, - 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'), - 'virtual' => $virtual, - 'folders' => array()); - } - else - $arrFolders[$currentFolder]['virtual'] = $virtual; - - if (strlen($subFolders)) - rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm); -} - - -/** - * Return html for a structured list <ul> for the mailbox tree - * @access private - * @return string - */ -function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0) -{ - global $RCMAIL, $CONFIG; - - $maxlength = intval($attrib['maxlength']); - $realnames = (bool)$attrib['realnames']; - $msgcounts = $RCMAIL->storage->get_cache('messagecount'); - - $out = ''; - foreach ($arrFolders as $key => $folder) { - $title = null; - $folder_class = rcmail_folder_classname($folder['id']); - $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false; - $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0; - - if ($folder_class && !$realnames) { - $foldername = rcube_label($folder_class); - } - else { - $foldername = $folder['name']; - - // shorten the folder name to a given length - if ($maxlength && $maxlength > 1) { - $fname = abbreviate_string($foldername, $maxlength); - if ($fname != $foldername) - $title = $foldername; - $foldername = $fname; - } - } - - // make folder name safe for ids and class names - $folder_id = html_identifier($folder['id'], true); - $classes = array('mailbox'); - - // set special class for Sent, Drafts, Trash and Junk - if ($folder_class) - $classes[] = $folder_class; - - if ($folder['id'] == $mbox_name) - $classes[] = 'selected'; - - if ($folder['virtual']) - $classes[] = 'virtual'; - else if ($unread) - $classes[] = 'unread'; - - $js_name = JQ($folder['id']); - $html_name = Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : ''); - $link_attrib = $folder['virtual'] ? array() : array( - 'href' => rcmail_url('', array('_mbox' => $folder['id'])), - 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name), - 'rel' => $folder['id'], - 'title' => $title, - ); - - $out .= html::tag('li', array( - 'id' => "rcmli".$folder_id, - 'class' => join(' ', $classes), - 'noclose' => true), - html::a($link_attrib, $html_name) . - (!empty($folder['folders']) ? html::div(array( - 'class' => ($collapsed ? 'collapsed' : 'expanded'), - 'style' => "position:absolute", - 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name) - ), ' ') : '')); - - $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']); - - if (!empty($folder['folders'])) { - $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)), - rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1)); - } - - $out .= "</li>\n"; - } - - return $out; -} - - -/** - * Return html for a flat list <select> for the mailbox tree - * @access private - * @return string - */ -function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array()) -{ - global $RCMAIL; - - $out = ''; - - foreach ($arrFolders as $key => $folder) { - // skip exceptions (and its subfolders) - if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) { - continue; - } - - // skip folders in which it isn't possible to create subfolders - if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id'])) - && in_array('\\Noinferiors', $attrs) - ) { - continue; - } - - if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id']))) - $foldername = rcube_label($folder_class); - else { - $foldername = $folder['name']; - - // shorten the folder name to a given length - if ($maxlength && $maxlength>1) - $foldername = abbreviate_string($foldername, $maxlength); - } - - $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']); - - if (!empty($folder['folders'])) - $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, - $select, $realnames, $nestLevel+1, $opts); - } - - return $out; + return rcube_ui::folder_selector($attrib); } - -/** - * Return internal name for the given folder if it matches the configured special folders - * @access private - * @return string - */ -function rcmail_folder_classname($folder_id) -{ - global $CONFIG; - - if ($folder_id == 'INBOX') - return 'inbox'; - - // for these mailboxes we have localized labels and css classes - foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx) - { - if ($folder_id == $CONFIG[$smbx.'_mbox']) - return $smbx; - } -} - - -/** - * Try to localize the given IMAP folder name. - * UTF-7 decode it in case no localized text was found - * - * @param string Folder name - * @return string Localized folder name in UTF-8 encoding - */ function rcmail_localize_foldername($name) { - if ($folder_class = rcmail_folder_classname($name)) - return rcube_label($folder_class); - else - return rcube_charset_convert($name, 'UTF7-IMAP'); + return rcube_ui::localize_foldername($name); } - function rcmail_localize_folderpath($path) { - global $RCMAIL; - - $protect_folders = $RCMAIL->config->get('protect_default_folders'); - $default_folders = (array) $RCMAIL->config->get('default_folders'); - $delimiter = $RCMAIL->storage->get_hierarchy_delimiter(); - $path = explode($delimiter, $path); - $result = array(); - - foreach ($path as $idx => $dir) { - $directory = implode($delimiter, array_slice($path, 0, $idx+1)); - if ($protect_folders && in_array($directory, $default_folders)) { - unset($result); - $result[] = rcmail_localize_foldername($directory); - } - else { - $result[] = rcube_charset_convert($dir, 'UTF7-IMAP'); - } - } - - return implode($delimiter, $result); + return rcube_ui::localize_folderpath($path); } - function rcmail_quota_display($attrib) { - global $OUTPUT; - - if (!$attrib['id']) - $attrib['id'] = 'rcmquotadisplay'; - - $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text'; - - $OUTPUT->add_gui_object('quotadisplay', $attrib['id']); - - $quota = rcmail_quota_content($attrib); - - $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready'); - - return html::span($attrib, ''); + return rcube_ui::quota_display($attrib); } - -function rcmail_quota_content($attrib=NULL) +function rcmail_quota_content($attrib = null) { - global $RCMAIL; - - $quota = $RCMAIL->storage->get_quota(); - $quota = $RCMAIL->plugins->exec_hook('quota', $quota); - - $quota_result = (array) $quota; - $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; - - if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) { - $quota_result['title'] = rcube_label('unlimited'); - $quota_result['percent'] = 0; - } - else if ($quota['total']) { - if (!isset($quota['percent'])) - $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100)); - - $title = sprintf('%s / %s (%.0f%%)', - show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024), - $quota_result['percent']); - - $quota_result['title'] = $title; - - if ($attrib['width']) - $quota_result['width'] = $attrib['width']; - if ($attrib['height']) - $quota_result['height'] = $attrib['height']; - } - else { - $quota_result['title'] = rcube_label('unknown'); - $quota_result['percent'] = 0; - } - - return $quota_result; + return rcube_ui::quota_content($attrib); } - -/** - * Outputs error message according to server error/response codes - * - * @param string Fallback message label - * @param string Fallback message label arguments - * - * @return void - */ function rcmail_display_server_error($fallback=null, $fallback_args=null) { - global $RCMAIL; - - $err_code = $RCMAIL->storage->get_error_code(); - $res_code = $RCMAIL->storage->get_response_code(); - - if ($err_code < 0) { - $RCMAIL->output->show_message('storageerror', 'error'); - } - else if ($res_code == rcube_storage::NOPERM) { - $RCMAIL->output->show_message('errornoperm', 'error'); - } - else if ($res_code == rcube_storage::READONLY) { - $RCMAIL->output->show_message('errorreadonly', 'error'); - } - else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) { - // try to detect access rights problem and display appropriate message - if (stripos($err_str, 'Permission denied') !== false) - $RCMAIL->output->show_message('errornoperm', 'error'); - else - $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); - } - else if ($fallback) { - $RCMAIL->output->show_message($fallback, 'error', $fallback_args); - } - - return true; + rcube_ui::display_server_error($fallback, $fallback_args); } - -/** - * Generate CSS classes from mimetype and filename extension - * - * @param string Mimetype - * @param string The filename - * @return string CSS classes separated by space - */ function rcmail_filetype2classname($mimetype, $filename) { - list($primary, $secondary) = explode('/', $mimetype); - - $classes = array($primary ? $primary : 'unknown'); - if ($secondary) { - $classes[] = $secondary; - } - if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) { - $classes[] = $m[1]; - } - - return strtolower(join(" ", $classes)); + return rcube_ui::file2class($mimetype, $filename); } -/** - * Output HTML editor scripts - * - * @param string Editor mode - * @return void - */ function rcube_html_editor($mode='') { - global $RCMAIL; - - $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode)); - - if ($hook['abort']) - return; - - $lang = strtolower($_SESSION['language']); - - // TinyMCE uses two-letter lang codes, with exception of Chinese - if (strpos($lang, 'zh_') === 0) - $lang = str_replace('_', '-', $lang); - else - $lang = substr($lang, 0, 2); - - if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) - $lang = 'en'; - - $RCMAIL->output->include_script('tiny_mce/tiny_mce.js'); - $RCMAIL->output->include_script('editor.js'); - $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)", - json_encode(array( - 'mode' => $mode, - 'lang' => $lang, - 'skin_path' => $RCMAIL->output->get_skin_path(), - 'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')), - 'spelldict' => intval($RCMAIL->config->get('spellcheck_dictionary')), - ))), 'docready'); + rcube_ui::html_editor($mode); } - -/** - * Replaces TinyMCE's emoticon images with plain-text representation - * - * @param string HTML content - * @return string HTML content - */ function rcmail_replace_emoticons($html) { - $emoticons = array( - '8-)' => 'smiley-cool', - ':-#' => 'smiley-foot-in-mouth', - ':-*' => 'smiley-kiss', - ':-X' => 'smiley-sealed', - ':-P' => 'smiley-tongue-out', - ':-@' => 'smiley-yell', - ":'(" => 'smiley-cry', - ':-(' => 'smiley-frown', - ':-D' => 'smiley-laughing', - ':-)' => 'smiley-smile', - ':-S' => 'smiley-undecided', - ':-$' => 'smiley-embarassed', - 'O:-)' => 'smiley-innocent', - ':-|' => 'smiley-money-mouth', - ':-O' => 'smiley-surprised', - ';-)' => 'smiley-wink', - ); - - foreach ($emoticons as $idx => $file) { - // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" /> - $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i'; - $replace[] = $idx; - } - - return preg_replace($search, $replace, $html); + return rcube_ui::replace_emoticons($html); } - -/** - * Send the given message using the configured method - * - * @param object $message Reference to Mail_MIME object - * @param string $from Sender address string - * @param array $mailto Array of recipient address strings - * @param array $smtp_error SMTP error array (reference) - * @param string $body_file Location of file with saved message body (reference), - * used when delay_file_io is enabled - * @param array $smtp_opts SMTP options (e.g. DSN request) - * - * @return boolean Send status. - */ function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null) { - global $CONFIG, $RCMAIL; - - $headers = $message->headers(); - - // send thru SMTP server using custom SMTP library - if ($CONFIG['smtp_server']) { - // generate list of recipients - $a_recipients = array($mailto); - - if (strlen($headers['Cc'])) - $a_recipients[] = $headers['Cc']; - if (strlen($headers['Bcc'])) - $a_recipients[] = $headers['Bcc']; - - // clean Bcc from header for recipients - $send_headers = $headers; - unset($send_headers['Bcc']); - // here too, it because txtHeaders() below use $message->_headers not only $send_headers - unset($message->_headers['Bcc']); - - $smtp_headers = $message->txtHeaders($send_headers, true); - - if ($message->getParam('delay_file_io')) { - // use common temp dir - $temp_dir = $RCMAIL->config->get('temp_dir'); - $body_file = tempnam($temp_dir, 'rcmMsg'); - if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { - raise_error(array('code' => 650, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not create message: ".$mime_result->getMessage()), - TRUE, FALSE); - return false; - } - $msg_body = fopen($body_file, 'r'); - } else { - $msg_body = $message->get(); - } - - // send message - if (!is_object($RCMAIL->smtp)) - $RCMAIL->smtp_init(true); - - $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts); - $smtp_response = $RCMAIL->smtp->get_response(); - $smtp_error = $RCMAIL->smtp->get_error(); - - // log error - if (!$sent) - raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__, - 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE); - } - // send mail using PHP's mail() function - else { - // unset some headers because they will be added by the mail() function - $headers_enc = $message->headers($headers); - $headers_php = $message->_headers; - unset($headers_php['To'], $headers_php['Subject']); - - // reset stored headers and overwrite - $message->_headers = array(); - $header_str = $message->txtHeaders($headers_php); - - // #1485779 - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) { - $headers_enc['To'] = implode(', ', $m[1]); - } - } - - $msg_body = $message->get(); - - if (PEAR::isError($msg_body)) - raise_error(array('code' => 650, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not create message: ".$msg_body->getMessage()), - TRUE, FALSE); - else { - $delim = $RCMAIL->config->header_delimiter(); - $to = $headers_enc['To']; - $subject = $headers_enc['Subject']; - $header_str = rtrim($header_str); - - if ($delim != "\r\n") { - $header_str = str_replace("\r\n", $delim, $header_str); - $msg_body = str_replace("\r\n", $delim, $msg_body); - $to = str_replace("\r\n", $delim, $to); - $subject = str_replace("\r\n", $delim, $subject); - } - - if (ini_get('safe_mode')) - $sent = mail($to, $subject, $msg_body, $header_str); - else - $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); - } - } - - if ($sent) { - $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body)); - - // remove MDN headers after sending - unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); - - // get all recipients - if ($headers['Cc']) - $mailto .= $headers['Cc']; - if ($headers['Bcc']) - $mailto .= $headers['Bcc']; - if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m)) - $mailto = implode(', ', array_unique($m[1])); - - if ($CONFIG['smtp_log']) { - write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", - $RCMAIL->user->get_username(), - $_SERVER['REMOTE_ADDR'], - $mailto, - !empty($smtp_response) ? join('; ', $smtp_response) : '')); - } - } - - if (is_resource($msg_body)) { - fclose($msg_body); - } - - $message->_headers = array(); - $message->headers($headers); - - return $sent; + return rcmail::get_instance()->deliver_message($message, $from, $mailto, $smtp_error, $body_file, $smtp_opts); } - -// Returns unique Message-ID function rcmail_gen_message_id() { - global $RCMAIL; - - $local_part = md5(uniqid('rcmail'.mt_rand(),true)); - $domain_part = $RCMAIL->user->get_username('domain'); - - // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) - if (!preg_match('/\.[a-z]+$/i', $domain_part)) { - if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST'])) - && preg_match('/\.[a-z]+$/i', $host)) { - $domain_part = $host; - } - else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME'])) - && preg_match('/\.[a-z]+$/i', $host)) { - $domain_part = $host; - } - } - - return sprintf('<%s@%s>', $local_part, $domain_part); + return rcmail::get_instance()->gen_message_id(); } - -// Returns RFC2822 formatted current date in user's timezone function rcmail_user_date() { - global $RCMAIL; - - // get user's timezone - try { - $tz = new DateTimeZone($RCMAIL->config->get('timezone')); - $date = new DateTime('now', $tz); - } - catch (Exception $e) { - $date = new DateTime(); - } - - return $date->format('r'); + return rcmail::get_instance()->user_date(); } - -/** - * Check if we can process not exceeding memory_limit - * - * @param integer Required amount of memory - * @return boolean - */ function rcmail_mem_check($need) { - $mem_limit = parse_bytes(ini_get('memory_limit')); - $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB - - return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true; + return rcube_ui::mem_check($need); } - -/** - * Check if working in SSL mode - * - * @param integer HTTPS port number - * @param boolean Enables 'use_https' option checking - * @return boolean - */ function rcube_https_check($port=null, $use_https=true) { - global $RCMAIL; - - if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') - return true; - if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') - return true; - if ($port && $_SERVER['SERVER_PORT'] == $port) - return true; - if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) - return true; - - return false; + return rcube_ui::https_check($port, $use_https); } - -/** - * For backward compatibility. - * - * @global rcmail $RCMAIL - * @param string $var_name Variable name. - * @return void - */ function rcube_sess_unset($var_name=null) { - global $RCMAIL; - - $RCMAIL->session->remove($var_name); + rcmail::get_instance()->session->remove($var_name); } - -/** - * Replaces hostname variables - * - * @param string $name Hostname - * @param string $host Optional IMAP hostname - * @return string - */ function rcube_parse_host($name, $host='') { - // %n - host - $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); - // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld - $d = preg_replace('/^[^\.]+\./', '', $n); - // %h - IMAP host - $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host; - // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld - $z = preg_replace('/^[^\.]+\./', '', $h); - // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided - if ( strpos($name, '%s') !== false ){ - $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true); - if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false ) - return false; - } - - $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name); - return $name; + return rcmail::parse_host($name, $host); } - -/** - * E-mail address validation - * - * @param string $email Email address - * @param boolean $dns_check True to check dns - * @return boolean - */ function check_email($email, $dns_check=true) { - // Check for invalid characters - if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) - return false; - - // Check for length limit specified by RFC 5321 (#1486453) - if (strlen($email) > 254) - return false; - - $email_array = explode('@', $email); - - // Check that there's one @ symbol - if (count($email_array) < 2) - return false; - - $domain_part = array_pop($email_array); - $local_part = implode('@', $email_array); - - // from PEAR::Validate - $regexp = '&^(?: - ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name - ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322) - $&xi'; - - if (!preg_match($regexp, $local_part)) - return false; - - // Check domain part - if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) - return true; // IP address - else { - // If not an IP address - $domain_array = explode('.', $domain_part); - if (sizeof($domain_array) < 2) - return false; // Not enough parts to be a valid domain - - foreach ($domain_array as $part) - if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) - return false; - - if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check')) - return true; - - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) { - $lookup = array(); - @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup); - foreach ($lookup as $line) { - if (strpos($line, 'MX preference')) - return true; - } - return false; - } - - // find MX record(s) - if (getmxrr($domain_part, $mx_records)) - return true; - - // find any DNS record - if (checkdnsrr($domain_part, 'ANY')) - return true; - } - - return false; + return rcmail::get_instance()->check_email($email, $dns_check); } -/* - * Idn_to_ascii wrapper. - * Intl/Idn modules version of this function doesn't work with e-mail address - */ -function rcube_idn_to_ascii($str) -{ - return rcube_idn_convert($str, true); -} - -/* - * Idn_to_ascii wrapper. - * Intl/Idn modules version of this function doesn't work with e-mail address - */ -function rcube_idn_to_utf8($str) -{ - return rcube_idn_convert($str, false); -} - -function rcube_idn_convert($input, $is_utf=false) -{ - if ($at = strpos($input, '@')) { - $user = substr($input, 0, $at); - $domain = substr($input, $at+1); - } - else { - $domain = $input; - } - - $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain); - - if ($domain === false) { - return ''; - } - - return $at ? $user . '@' . $domain : $domain; -} - - -/** - * Helper class to turn relative urls into absolute ones - * using a predefined base - */ -class rcube_base_replacer -{ - private $base_url; - - public function __construct($base) - { - $this->base_url = $base; - } - - public function callback($matches) - { - return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"'; - } - - public function replace($body) - { - return preg_replace_callback(array( - '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', - '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', - ), - array($this, 'callback'), $body); - } - - /** - * Convert paths like ../xxx to an absolute path using a base url - * - * @param string $path Relative path - * @param string $base_url Base URL - * - * @return string Absolute URL - */ - public static function absolute_url($path, $base_url) - { - $host_url = $base_url; - $abs_path = $path; - - // check if path is an absolute URL - if (preg_match('/^[fhtps]+:\/\//', $path)) { - return $path; - } - - // check if path is a content-id scheme - if (strpos($path, 'cid:') === 0) { - return $path; - } - - // cut base_url to the last directory - if (strrpos($base_url, '/') > 7) { - $host_url = substr($base_url, 0, strpos($base_url, '/', 7)); - $base_url = substr($base_url, 0, strrpos($base_url, '/')); - } - - // $path is absolute - if ($path[0] == '/') { - $abs_path = $host_url.$path; - } - else { - // strip './' because its the same as '' - $path = preg_replace('/^\.\//', '', $path); - - if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) { - foreach ($matches as $a_match) { - if (strrpos($base_url, '/')) { - $base_url = substr($base_url, 0, strrpos($base_url, '/')); - } - $path = substr($path, 3); - } - } - - $abs_path = $base_url.'/'.$path; - } - - return $abs_path; - } -} - - -/****** debugging and logging functions ********/ - -/** - * Print or write debug messages - * - * @param mixed Debug message or data - * @return void - */ function console() { - $args = func_get_args(); - - if (class_exists('rcmail', false)) { - $rcmail = rcmail::get_instance(); - if (is_object($rcmail->plugins)) { - $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args)); - if ($plugin['abort']) - return; - $args = $plugin['args']; - } - } - - $msg = array(); - foreach ($args as $arg) - $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; - - write_log('console', join(";\n", $msg)); + call_user_func_array(array('rcmail', 'console'), func_get_args()); } - -/** - * Append a line to a logfile in the logs directory. - * Date will be added automatically to the line. - * - * @param $name name of log file - * @param line Line to append - * @return void - */ function write_log($name, $line) { - global $CONFIG, $RCMAIL; - - if (!is_string($line)) - $line = var_export($line, true); - - if (empty($CONFIG['log_date_format'])) - $CONFIG['log_date_format'] = 'd-M-Y H:i:s O'; - - $date = date($CONFIG['log_date_format']); - - // trigger logging hook - if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) { - $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line)); - $name = $log['name']; - $line = $log['line']; - $date = $log['date']; - if ($log['abort']) - return true; - } - - if ($CONFIG['log_driver'] == 'syslog') { - $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; - syslog($prio, $line); - return true; - } - else { - $line = sprintf("[%s]: %s\n", $date, $line); - - // log_driver == 'file' is assumed here - if (empty($CONFIG['log_dir'])) - $CONFIG['log_dir'] = INSTALL_PATH.'logs'; - - // try to open specific log file for writing - $logfile = $CONFIG['log_dir'].'/'.$name; - if ($fp = @fopen($logfile, 'a')) { - fwrite($fp, $line); - fflush($fp); - fclose($fp); - return true; - } - else - trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING); - } - - return false; + return rcmail::write_log($name, $line); } - -/** - * Write login data (name, ID, IP address) to the 'userlogins' log file. - * - * @return void - */ function rcmail_log_login() { - global $RCMAIL; - - if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user) - return; - - write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s', - $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id())); + return rcmail::get_instance()->log_login(); } - -/** - * Returns remote IP address and forwarded addresses if found - * - * @return string Remote IP address(es) - */ function rcmail_remote_ip() { - $address = $_SERVER['REMOTE_ADDR']; - - // append the NGINX X-Real-IP header, if set - if (!empty($_SERVER['HTTP_X_REAL_IP'])) { - $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP']; - } - // append the X-Forwarded-For header, if set - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR']; - } - - if (!empty($remote_ip)) - $address .= '(' . implode(',', $remote_ip) . ')'; - - return $address; + return rcmail::remote_ip(); } - -/** - * Check whether the HTTP referer matches the current request - * - * @return boolean True if referer is the same host+path, false if not - */ function rcube_check_referer() { - $uri = parse_url($_SERVER['REQUEST_URI']); - $referer = parse_url(rc_request_header('Referer')); - return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path']; + return rcmail::check_referer(); } - -/** - * @access private - * @return mixed - */ function rcube_timer() { - return microtime(true); + return rcmail::timer(); } - -/** - * @access private - * @return void - */ function rcube_print_time($timer, $label='Timer', $dest='console') { - static $print_count = 0; - - $print_count++; - $now = rcube_timer(); - $diff = $now-$timer; - - if (empty($label)) - $label = 'Timer '.$print_count; - - write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); + rcmail::print_timer($timer, $label, $dest); } - -/** - * Throw system error and show error page - * - * @param array Named parameters - * - code: Error code (required) - * - type: Error type [php|db|imap|javascript] (required) - * - message: Error message - * - file: File where error occured - * - line: Line where error occured - * @param boolean True to log the error - * @param boolean Terminate script execution - */ -// may be defined in Installer -if (!function_exists('raise_error')) { function raise_error($arg=array(), $log=false, $terminate=false) { - global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE; - - // report bug (if not incompatible browser) - if ($log && $arg['type'] && $arg['message']) - rcube_log_bug($arg); - - // display error page and terminate script - if ($terminate) { - $ERROR_CODE = $arg['code']; - $ERROR_MESSAGE = $arg['message']; - include INSTALL_PATH . 'program/steps/utils/error.inc'; - exit; - } -} + rcmail::raise_error($arg, $log, $terminate); } - -/** - * Report error according to configured debug_level - * - * @param array Named parameters - * @return void - * @see raise_error() - */ function rcube_log_bug($arg_arr) { - global $CONFIG; - - $program = strtoupper($arg_arr['type']); - $level = $CONFIG['debug_level']; - - // disable errors for ajax requests, write to log instead (#1487831) - if (($level & 4) && !empty($_REQUEST['_remote'])) { - $level = ($level ^ 4) | 1; - } - - // write error to local log file - if ($level & 1) { - $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : ''); - $log_entry = sprintf("%s Error: %s%s (%s %s)", - $program, - $arg_arr['message'], - $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', - $_SERVER['REQUEST_METHOD'], - $_SERVER['REQUEST_URI'] . $post_query); - - if (!write_log('errors', $log_entry)) { - // send error to PHPs error handler if write_log didn't succeed - trigger_error($arg_arr['message']); - } - } - - // report the bug to the global bug reporting system - if ($level & 2) { - // TODO: Send error via HTTP - } - - // show error if debug_mode is on - if ($level & 4) { - print "<b>$program Error"; - - if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) - print " in $arg_arr[file] ($arg_arr[line])"; - - print ':</b> '; - print nl2br($arg_arr['message']); - print '<br />'; - flush(); - } + rcmail::log_bug($arg_arr); } function rcube_upload_progress() { - global $RCMAIL; - - $prefix = ini_get('apc.rfc1867_prefix'); - $params = array( - 'action' => $RCMAIL->action, - 'name' => get_input_value('_progress', RCUBE_INPUT_GET), - ); - - if (function_exists('apc_fetch')) { - $status = apc_fetch($prefix . $params['name']); - - if (!empty($status)) { - $status['percent'] = round($status['current']/$status['total']*100); - $params = array_merge($status, $params); - } - } - - if (isset($params['percent'])) - $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array( - 'percent' => $params['percent'] . '%', - 'current' => show_bytes($params['current']), - 'total' => show_bytes($params['total']) - ))); - - $RCMAIL->output->command('upload_progress_update', $params); - $RCMAIL->output->send(); + rcube_ui::upload_progress(); } function rcube_upload_init() { - global $RCMAIL; - - // Enable upload progress bar - if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) { - if ($field_name = ini_get('apc.rfc1867_name')) { - $RCMAIL->output->set_env('upload_progress_name', $field_name); - $RCMAIL->output->set_env('upload_progress_time', (int) $seconds); - } - } - - // find max filesize value - $max_filesize = parse_bytes(ini_get('upload_max_filesize')); - $max_postsize = parse_bytes(ini_get('post_max_size')); - if ($max_postsize && $max_postsize < $max_filesize) - $max_filesize = $max_postsize; - - $RCMAIL->output->set_env('max_filesize', $max_filesize); - $max_filesize = show_bytes($max_filesize); - $RCMAIL->output->set_env('filesizeerror', rcube_label(array( - 'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize)))); - - return $max_filesize; + return rcube_ui::upload_init(); } -/** - * Initializes client-side autocompletion - */ function rcube_autocomplete_init() { - global $RCMAIL; - static $init; + rcube_ui::autocomplete_init(); +} + +function rcube_fontdefs($font = null) +{ + return rcube_ui::font_defs($font); +} + +function send_nocacheing_headers() +{ + return rcmail::get_instance()->output->nocacheing_headers(); +} - if ($init) - return; +function show_bytes($bytes) +{ + return rcube_ui::show_bytes($bytes); +} - $init = 1; +function rc_wordwrap($string, $width=75, $break="\n", $cut=false) +{ + return rcube_mime::wordwrap($string, $width, $break, $cut); +} - if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) { - $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql'); - if (count($book_types) > 1) { - $RCMAIL->output->set_env('autocomplete_threads', $threads); - $RCMAIL->output->set_env('autocomplete_sources', $book_types); - } - } +function rc_request_header($name) +{ + return rcube_request_header($name); +} - $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15)); - $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length')); - $RCMAIL->output->add_label('autocompletechars', 'autocompletemore'); +function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false) +{ + return rcube_mime::file_content_type($path, $name, $failover, $is_stream); } -function rcube_fontdefs($font = null) +function rc_image_content_type($data) { - $fonts = array( - 'Andale Mono' => '"Andale Mono",Times,monospace', - 'Arial' => 'Arial,Helvetica,sans-serif', - 'Arial Black' => '"Arial Black","Avant Garde",sans-serif', - 'Book Antiqua' => '"Book Antiqua",Palatino,serif', - 'Courier New' => '"Courier New",Courier,monospace', - 'Georgia' => 'Georgia,Palatino,serif', - 'Helvetica' => 'Helvetica,Arial,sans-serif', - 'Impact' => 'Impact,Chicago,sans-serif', - 'Tahoma' => 'Tahoma,Arial,Helvetica,sans-serif', - 'Terminal' => 'Terminal,Monaco,monospace', - 'Times New Roman' => '"Times New Roman",Times,serif', - 'Trebuchet MS' => '"Trebuchet MS",Geneva,sans-serif', - 'Verdana' => 'Verdana,Geneva,sans-serif', - ); - - if ($font) - return $fonts[$font]; - - return $fonts; + return rcube_mime::image_content_type($data); } diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a86729156..bca91e1a8 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -5,8 +5,8 @@ | program/include/rcmail.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2011, The Roundcube Dev Team | - | Copyright (C) 2011, Kolab Systems AG | + | Copyright (C) 2008-2012, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -30,7 +30,7 @@ * * @package Core */ -class rcmail +class rcmail extends rcube { /** * Main tasks. @@ -40,76 +40,6 @@ class rcmail static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy'); /** - * Singleton instace of rcmail - * - * @var rcmail - */ - static private $instance; - - /** - * Stores instance of rcube_config. - * - * @var rcube_config - */ - public $config; - - /** - * Stores rcube_user instance. - * - * @var rcube_user - */ - public $user; - - /** - * Instace of database class. - * - * @var rcube_mdb2 - */ - public $db; - - /** - * Instace of Memcache class. - * - * @var rcube_mdb2 - */ - public $memcache; - - /** - * Instace of rcube_session class. - * - * @var rcube_session - */ - public $session; - - /** - * Instance of rcube_smtp class. - * - * @var rcube_smtp - */ - public $smtp; - - /** - * Instance of rcube_storage class. - * - * @var rcube_storage - */ - public $storage; - - /** - * Instance of rcube_template class. - * - * @var rcube_template - */ - public $output; - - /** - * Instance of rcube_plugin_api. - * - * @var rcube_plugin_api - */ - public $plugins; - - /** * Current task. * * @var string @@ -124,12 +54,8 @@ class rcmail public $action = ''; public $comm_path = './'; - private $texts; private $address_books = array(); - private $caches = array(); private $action_map = array(); - private $shutdown_functions = array(); - private $expunge_cache = false; /** @@ -139,7 +65,7 @@ class rcmail */ static function get_instance() { - if (!self::$instance) { + if (!self::$instance || !is_a(self::$instance, 'rcmail')) { self::$instance = new rcmail(); self::$instance->startup(); // init AFTER object was linked with self::$instance } @@ -149,32 +75,12 @@ class rcmail /** - * Private constructor - */ - private function __construct() - { - // load configuration - $this->config = new rcube_config(); - - register_shutdown_function(array($this, 'shutdown')); - } - - - /** * Initial startup function * to register session, create database and imap connections */ - private function startup() + protected function startup() { - // initialize syslog - if ($this->config->get('log_driver') == 'syslog') { - $syslog_id = $this->config->get('syslog_id', 'roundcube'); - $syslog_facility = $this->config->get('syslog_facility', LOG_USER); - openlog($syslog_id, LOG_ODELAY, $syslog_facility); - } - - // connect to database - $this->get_dbh(); + $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS); // start session $this->session_init(); @@ -186,8 +92,8 @@ class rcmail $this->session_configure(); // set task and action properties - $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC)); - $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC)); + $this->set_task(rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC)); + $this->action = asciiwords(rcube_ui::get_input_value('_action', rcube_ui::INPUT_GPC)); // reset some session parameters when changing task if ($this->task != 'utils') { @@ -203,11 +109,9 @@ class rcmail else $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed'])); - // create plugin API and load plugins - $this->plugins = rcube_plugin_api::get_instance(); - - // init plugins - $this->plugins->init(); + // load plugins + $this->plugins->init($this, $this->task); + $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui')); } @@ -259,144 +163,6 @@ class rcmail /** - * Check the given string and return a valid language code - * - * @param string Language code - * @return string Valid language code - */ - private function language_prop($lang) - { - static $rcube_languages, $rcube_language_aliases; - - // user HTTP_ACCEPT_LANGUAGE if no language is specified - if (empty($lang) || $lang == 'auto') { - $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - $lang = str_replace('-', '_', $accept_langs[0]); - } - - if (empty($rcube_languages)) { - @include(INSTALL_PATH . 'program/localization/index.inc'); - } - - // check if we have an alias for that language - if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { - $lang = $rcube_language_aliases[$lang]; - } - // try the first two chars - else if (!isset($rcube_languages[$lang])) { - $short = substr($lang, 0, 2); - - // check if we have an alias for the short language code - if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { - $lang = $rcube_language_aliases[$short]; - } - // expand 'nn' to 'nn_NN' - else if (!isset($rcube_languages[$short])) { - $lang = $short.'_'.strtoupper($short); - } - } - - if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { - $lang = 'en_US'; - } - - return $lang; - } - - - /** - * Get the current database connection - * - * @return rcube_mdb2 Database connection object - */ - public function get_dbh() - { - if (!$this->db) { - $config_all = $this->config->all(); - - $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); - $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql'; - $this->db->set_debug((bool)$config_all['sql_debug']); - } - - return $this->db; - } - - - /** - * Get global handle for memcache access - * - * @return object Memcache - */ - public function get_memcache() - { - if (!isset($this->memcache)) { - // no memcache support in PHP - if (!class_exists('Memcache')) { - $this->memcache = false; - return false; - } - - $this->memcache = new Memcache; - $this->mc_available = 0; - - // add alll configured hosts to pool - $pconnect = $this->config->get('memcache_pconnect', true); - foreach ($this->config->get('memcache_hosts', array()) as $host) { - list($host, $port) = explode(':', $host); - if (!$port) $port = 11211; - $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure'))); - } - - // test connection and failover (will result in $this->mc_available == 0 on complete failure) - $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist - - if (!$this->mc_available) - $this->memcache = false; - } - - return $this->memcache; - } - - /** - * Callback for memcache failure - */ - public function memcache_failure($host, $port) - { - static $seen = array(); - - // only report once - if (!$seen["$host:$port"]++) { - $this->mc_available--; - raise_error(array('code' => 604, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => "Memcache failure on host $host:$port"), - true, false); - } - } - - - /** - * Initialize and get cache object - * - * @param string $name Cache identifier - * @param string $type Cache type ('db', 'apc' or 'memcache') - * @param int $ttl Expiration time for cache items in seconds - * @param bool $packed Enables/disables data serialization - * - * @return rcube_cache Cache object - */ - public function get_cache($name, $type='db', $ttl=0, $packed=true) - { - if (!isset($this->caches[$name])) { - $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed); - } - - return $this->caches[$name]; - } - - - /** * Return instance of the internal address book class * * @param string Address book identifier @@ -425,7 +191,7 @@ class rcmail $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host'])); } else if ($id === '0') { - $contacts = new rcube_contacts($this->db, $this->user->ID); + $contacts = new rcube_contacts($this->db, $this->get_user_id()); } else { $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable)); @@ -446,7 +212,7 @@ class rcmail } if (!$contacts) { - raise_error(array( + self::raise_error(array( 'code' => 700, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Addressbook source ($id) not found!"), @@ -481,10 +247,10 @@ class rcmail // We are using the DB address book if ($abook_type != 'ldap') { if (!isset($this->address_books['0'])) - $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID); + $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id()); $list['0'] = array( 'id' => '0', - 'name' => rcube_label('personaladrbook'), + 'name' => $this->gettext('personaladrbook'), 'groups' => $this->address_books['0']->groups, 'readonly' => $this->address_books['0']->readonly, 'autocomplete' => in_array('sql', $autocomplete), @@ -532,13 +298,13 @@ class rcmail * environment vars according to the current session and configuration * * @param boolean True if this request is loaded in a (i)frame - * @return rcube_template Reference to HTML output object + * @return rcube_output_html Reference to HTML output object */ public function load_gui($framed = false) { // init output page - if (!($this->output instanceof rcube_template)) - $this->output = new rcube_template($this->task, $framed); + if (!($this->output instanceof rcube_output_html)) + $this->output = new rcube_output_html($this->task, $framed); // set keep-alive/check-recent interval if ($this->session && ($keep_alive = $this->session->get_keep_alive())) { @@ -565,177 +331,18 @@ class rcmail /** * Create an output object for JSON responses * - * @return rcube_json_output Reference to JSON output object + * @return rcube_output_json Reference to JSON output object */ public function json_init() { - if (!($this->output instanceof rcube_json_output)) - $this->output = new rcube_json_output($this->task); + if (!($this->output instanceof rcube_output_json)) + $this->output = new rcube_output_json($this->task); return $this->output; } /** - * Create SMTP object and connect to server - * - * @param boolean True if connection should be established - */ - public function smtp_init($connect = false) - { - $this->smtp = new rcube_smtp(); - - if ($connect) - $this->smtp->connect(); - } - - - /** - * Initialize and get storage object - * - * @return rcube_storage Storage object - */ - public function get_storage() - { - // already initialized - if (!is_object($this->storage)) { - $this->storage_init(); - } - - return $this->storage; - } - - - /** - * Connect to the IMAP server with stored session data. - * - * @return bool True on success, False on error - * @deprecated - */ - public function imap_connect() - { - return $this->storage_connect(); - } - - - /** - * Initialize IMAP object. - * - * @deprecated - */ - public function imap_init() - { - $this->storage_init(); - } - - - /** - * Initialize storage object - */ - public function storage_init() - { - // already initialized - if (is_object($this->storage)) { - return; - } - - $driver = $this->config->get('storage_driver', 'imap'); - $driver_class = "rcube_{$driver}"; - - if (!class_exists($driver_class)) { - raise_error(array( - 'code' => 700, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Storage driver class ($driver) not found!"), - true, true); - } - - // Initialize storage object - $this->storage = new $driver_class; - - // for backward compat. (deprecated, will be removed) - $this->imap = $this->storage; - - // enable caching of mail data - $storage_cache = $this->config->get("{$driver}_cache"); - $messages_cache = $this->config->get('messages_cache'); - // for backward compatybility - if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { - $storage_cache = 'db'; - $messages_cache = true; - } - - if ($storage_cache) - $this->storage->set_caching($storage_cache); - if ($messages_cache) - $this->storage->set_messages_caching(true); - - // set pagesize from config - $pagesize = $this->config->get('mail_pagesize'); - if (!$pagesize) { - $pagesize = $this->config->get('pagesize', 50); - } - $this->storage->set_pagesize($pagesize); - - // set class options - $options = array( - 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), - 'auth_cid' => $this->config->get("{$driver}_auth_cid"), - 'auth_pw' => $this->config->get("{$driver}_auth_pw"), - 'debug' => (bool) $this->config->get("{$driver}_debug"), - 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), - 'timeout' => (int) $this->config->get("{$driver}_timeout"), - 'skip_deleted' => (bool) $this->config->get('skip_deleted'), - 'driver' => $driver, - ); - - if (!empty($_SESSION['storage_host'])) { - $options['host'] = $_SESSION['storage_host']; - $options['user'] = $_SESSION['username']; - $options['port'] = $_SESSION['storage_port']; - $options['ssl'] = $_SESSION['storage_ssl']; - $options['password'] = $this->decrypt($_SESSION['password']); - // set 'imap_host' for backwards compatibility - $_SESSION[$driver.'_host'] = &$_SESSION['storage_host']; - } - - $options = $this->plugins->exec_hook("storage_init", $options); - - $this->storage->set_options($options); - $this->set_storage_prop(); - } - - - /** - * Connect to the mail storage server with stored session data - * - * @return bool True on success, False on error - */ - public function storage_connect() - { - $storage = $this->get_storage(); - - if ($_SESSION['storage_host'] && !$storage->is_connected()) { - $host = $_SESSION['storage_host']; - $user = $_SESSION['username']; - $port = $_SESSION['storage_port']; - $ssl = $_SESSION['storage_ssl']; - $pass = $this->decrypt($_SESSION['password']); - - if (!$storage->connect($host, $user, $pass, $port, $ssl)) { - if ($this->output) - $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error'); - } - else { - $this->set_storage_prop(); - } - } - - return $storage->is_connected(); - } - - - /** * Create session object and start the session. */ public function session_init() @@ -757,7 +364,7 @@ class rcmail ini_set('session.gc_maxlifetime', $lifetime * 2); } - ini_set('session.cookie_secure', rcube_https_check()); + ini_set('session.cookie_secure', rcube_ui::https_check()); ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); @@ -766,7 +373,7 @@ class rcmail // use database for storing session data $this->session = new rcube_session($this->get_dbh(), $this->config); - $this->session->register_gc_handler('rcmail_temp_gc'); + $this->session->register_gc_handler(array($this, 'temp_gc')); $this->session->register_gc_handler(array($this, 'cache_gc')); // start PHP session (if not in CLI mode) @@ -842,7 +449,7 @@ class rcmail if (!$allowed) return false; } - else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host'])) + else if (!empty($config['default_host']) && $host != self::parse_host($config['default_host'])) return false; // parse $host URL @@ -866,9 +473,9 @@ class rcmail // Check if we need to add domain if (!empty($config['username_domain']) && strpos($username, '@') === false) { if (is_array($config['username_domain']) && isset($config['username_domain'][$host])) - $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host); + $username .= '@'.self::parse_host($config['username_domain'][$host], $host); else if (is_string($config['username_domain'])) - $username .= '@'.rcube_parse_host($config['username_domain'], $host); + $username .= '@'.self::parse_host($config['username_domain'], $host); } // Convert username to lowercase. If storage backend @@ -929,7 +536,7 @@ class rcmail $user = $created; } else { - raise_error(array( + self::raise_error(array( 'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to create a user record. Maybe aborted by a plugin?" @@ -937,7 +544,7 @@ class rcmail } } else { - raise_error(array( + self::raise_error(array( 'code' => 621, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Access denied for new user $username. 'auto_create_user' is disabled" @@ -984,28 +591,6 @@ class rcmail /** - * Set storage parameters. - * This must be done AFTER connecting to the server! - */ - private function set_storage_prop() - { - $storage = $this->get_storage(); - - $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET)); - - if ($default_folders = $this->config->get('default_folders')) { - $storage->set_default_folders($default_folders); - } - if (isset($_SESSION['mbox'])) { - $storage->set_folder($_SESSION['mbox']); - } - if (isset($_SESSION['page'])) { - $storage->set_page($_SESSION['page']); - } - } - - - /** * Auto-select IMAP host based on the posted login information * * @return string Selected IMAP host @@ -1016,7 +601,7 @@ class rcmail $host = null; if (is_array($default_host)) { - $post_host = get_input_value('_host', RCUBE_INPUT_POST); + $post_host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST); // direct match in default_host array if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) { @@ -1024,7 +609,7 @@ class rcmail } // try to select host by mail domain - list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST)); + list($user, $domain) = explode('@', rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST)); if (!empty($domain)) { foreach ($default_host as $storage_host => $mail_domains) { if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) { @@ -1045,178 +630,16 @@ class rcmail } } else if (empty($default_host)) { - $host = get_input_value('_host', RCUBE_INPUT_POST); + $host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST); } else - $host = rcube_parse_host($default_host); + $host = self::parse_host($default_host); return $host; } /** - * Get localized text in the desired language - * - * @param mixed $attrib Named parameters array or label name - * @param string $domain Label domain (plugin) name - * - * @return string Localized text - */ - public function gettext($attrib, $domain=null) - { - // load localization files if not done yet - if (empty($this->texts)) - $this->load_language(); - - // extract attributes - if (is_string($attrib)) - $attrib = array('name' => $attrib); - - $name = $attrib['name'] ? $attrib['name'] : ''; - - // attrib contain text values: use them from now - if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) - $this->texts[$name] = $setval; - - // check for text with domain - if ($domain && ($text = $this->texts[$domain.'.'.$name])) - ; - // text does not exist - else if (!($text = $this->texts[$name])) { - return "[$name]"; - } - - // replace vars in text - if (is_array($attrib['vars'])) { - foreach ($attrib['vars'] as $var_key => $var_value) - $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text); - } - - // format output - if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst']) - return ucfirst($text); - else if ($attrib['uppercase']) - return mb_strtoupper($text); - else if ($attrib['lowercase']) - return mb_strtolower($text); - - return strtr($text, array('\n' => "\n")); - } - - - /** - * Check if the given text label exists - * - * @param string $name Label name - * @param string $domain Label domain (plugin) name or '*' for all domains - * @param string $ref_domain Sets domain name if label is found - * - * @return boolean True if text exists (either in the current language or in en_US) - */ - public function text_exists($name, $domain = null, &$ref_domain = null) - { - // load localization files if not done yet - if (empty($this->texts)) - $this->load_language(); - - if (isset($this->texts[$name])) { - $ref_domain = ''; - return true; - } - - // any of loaded domains (plugins) - if ($domain == '*') { - foreach ($this->plugins->loaded_plugins() as $domain) - if (isset($this->texts[$domain.'.'.$name])) { - $ref_domain = $domain; - return true; - } - } - // specified domain - else if ($domain) { - $ref_domain = $domain; - return isset($this->texts[$domain.'.'.$name]); - } - - return false; - } - - /** - * Load a localization package - * - * @param string Language ID - */ - public function load_language($lang = null, $add = array()) - { - $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); - - // load localized texts - if (empty($this->texts) || $lang != $_SESSION['language']) { - $this->texts = array(); - - // handle empty lines after closing PHP tag in localization files - ob_start(); - - // get english labels (these should be complete) - @include(INSTALL_PATH . 'program/localization/en_US/labels.inc'); - @include(INSTALL_PATH . 'program/localization/en_US/messages.inc'); - - if (is_array($labels)) - $this->texts = $labels; - if (is_array($messages)) - $this->texts = array_merge($this->texts, $messages); - - // include user language files - if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { - include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc'); - include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc'); - - if (is_array($labels)) - $this->texts = array_merge($this->texts, $labels); - if (is_array($messages)) - $this->texts = array_merge($this->texts, $messages); - } - - ob_end_clean(); - - $_SESSION['language'] = $lang; - } - - // append additional texts (from plugin) - if (is_array($add) && !empty($add)) - $this->texts += $add; - } - - - /** - * Read directory program/localization and return a list of available languages - * - * @return array List of available localizations - */ - public function list_languages() - { - static $sa_languages = array(); - - if (!sizeof($sa_languages)) { - @include(INSTALL_PATH . 'program/localization/index.inc'); - - if ($dh = @opendir(INSTALL_PATH . 'program/localization')) { - while (($name = readdir($dh)) !== false) { - if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) - continue; - - if ($label = $rcube_languages[$name]) - $sa_languages[$name] = $label; - } - closedir($dh); - } - } - - return $sa_languages; - } - - - /** * Destroy session data and remove cookie */ public function kill_session() @@ -1260,68 +683,6 @@ class rcmail /** - * Function to be executed in script shutdown - * Registered with register_shutdown_function() - */ - public function shutdown() - { - foreach ($this->shutdown_functions as $function) - call_user_func($function); - - if (is_object($this->smtp)) - $this->smtp->disconnect(); - - foreach ($this->address_books as $book) { - if (is_object($book) && is_a($book, 'rcube_addressbook')) - $book->close(); - } - - foreach ($this->caches as $cache) { - if (is_object($cache)) - $cache->close(); - } - - if (is_object($this->storage)) { - if ($this->expunge_cache) - $this->storage->expunge_cache(); - $this->storage->close(); - } - - // before closing the database connection, write session data - if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { - session_write_close(); - } - - // write performance stats to logs/console - if ($this->config->get('devel_mode')) { - if (function_exists('memory_get_usage')) - $mem = show_bytes(memory_get_usage()); - if (function_exists('memory_get_peak_usage')) - $mem .= '/'.show_bytes(memory_get_peak_usage()); - - $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : ''); - if (defined('RCMAIL_START')) - rcube_print_time(RCMAIL_START, $log); - else - console($log); - } - } - - - /** - * Registers shutdown function to be executed on shutdown. - * The functions will be executed before destroying any - * objects like smtp, imap, session, etc. - * - * @param callback Function callback - */ - public function add_shutdown_function($function) - { - $this->shutdown_functions[] = $function; - } - - - /** * Garbage collector for cache entries. * Set flag to expunge caches on shutdown */ @@ -1342,7 +703,7 @@ class rcmail { $sess_id = $_COOKIE[ini_get('session.name')]; if (!$sess_id) $sess_id = session_id(); - $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->user->ID . $this->config->get('des_key') . $sess_id))); + $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id))); return $plugin['value']; } @@ -1353,9 +714,9 @@ class rcmail * @param int Request method * @return boolean True if request token is valid false if not */ - public function check_request($mode = RCUBE_INPUT_POST) + public function check_request($mode = rcube_ui::INPUT_POST) { - $token = get_input_value('_token', $mode); + $token = rcube_ui::get_input_value('_token', $mode); $sess_id = $_COOKIE[ini_get('session.name')]; return !empty($sess_id) && $token == $this->get_request_token(); } @@ -1384,130 +745,6 @@ class rcmail /** - * Encrypt using 3DES - * - * @param string $clear clear text input - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not to base64_encode() the result before returning - * - * @return string encrypted text - */ - public function encrypt($clear, $key = 'des_key', $base64 = true) - { - if (!$clear) - return ''; - /*- - * Add a single canary byte to the end of the clear text, which - * will help find out how much of padding will need to be removed - * upon decryption; see http://php.net/mcrypt_generic#68082 - */ - $clear = pack("a*H2", $clear, "80"); - - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) - { - $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); - $cipher = $iv . mcrypt_generic($td, $clear); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = $this->create_iv($des_iv_size); - $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); - } - else { - raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } - } - - return $base64 ? base64_encode($cipher) : $cipher; - } - - /** - * Decrypt 3DES-encrypted string - * - * @param string $cipher encrypted text - * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' - * @param boolean $base64 whether or not input is base64-encoded - * - * @return string decrypted text - */ - public function decrypt($cipher, $key = 'des_key', $base64 = true) - { - if (!$cipher) - return ''; - - $cipher = $base64 ? base64_decode($cipher) : $cipher; - - if (function_exists('mcrypt_module_open') && - ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) - { - $iv_size = mcrypt_enc_get_iv_size($td); - $iv = substr($cipher, 0, $iv_size); - - // session corruption? (#1485970) - if (strlen($iv) < $iv_size) - return ''; - - $cipher = substr($cipher, $iv_size); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); - $clear = mdecrypt_generic($td, $cipher); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - } - else { - @include_once 'des.inc'; - - if (function_exists('des')) { - $des_iv_size = 8; - $iv = substr($cipher, 0, $des_iv_size); - $cipher = substr($cipher, $des_iv_size); - $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); - } - else { - raise_error(array( - 'code' => 500, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" - ), true, true); - } - } - - /*- - * Trim PHP's padding and the canary byte; see note in - * rcmail::encrypt() and http://php.net/mcrypt_generic#68082 - */ - $clear = substr(rtrim($clear, "\0"), 0, -1); - - return $clear; - } - - /** - * Generates encryption initialization vector (IV) - * - * @param int Vector size - * @return string Vector string - */ - private function create_iv($size) - { - // mcrypt_create_iv() can be slow when system lacks entrophy - // we'll generate IV vector manually - $iv = ''; - for ($i = 0; $i < $size; $i++) - $iv .= chr(mt_rand(0, 255)); - return $iv; - } - - /** * Build a valid URL to this instance of Roundcube * * @param mixed Either a string with the action or url parameters as key-value pairs @@ -1536,50 +773,36 @@ class rcmail /** - * Construct shell command, execute it and return output as string. - * Keywords {keyword} are replaced with arguments - * - * @param $cmd Format string with {keywords} to be replaced - * @param $values (zero, one or more arrays can be passed) - * @return output of command. shell errors not detectable + * Function to be executed in script shutdown */ - public static function exec(/* $cmd, $values1 = array(), ... */) + public function shutdown() { - $args = func_get_args(); - $cmd = array_shift($args); - $values = $replacements = array(); - - // merge values into one array - foreach ($args as $arg) - $values += (array)$arg; - - preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); - foreach ($matches as $tags) { - list(, $tag, $option, $key) = $tags; - $parts = array(); - - if ($option) { - foreach ((array)$values["-$key"] as $key => $value) { - if ($value === true || $value === false || $value === null) - $parts[] = $value ? $key : ""; - else foreach ((array)$value as $val) - $parts[] = "$key " . escapeshellarg($val); - } - } - else { - foreach ((array)$values[$key] as $value) - $parts[] = escapeshellarg($value); - } + parent::shutdown(); - $replacements[$tag] = join(" ", $parts); + foreach ($this->address_books as $book) { + if (is_object($book) && is_a($book, 'rcube_addressbook')) + $book->close(); } - // use strtr behaviour of going through source string once - $cmd = strtr($cmd, $replacements); + // before closing the database connection, write session data + if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { + session_write_close(); + } - return (string)shell_exec($cmd); - } + // write performance stats to logs/console + if ($this->config->get('devel_mode')) { + if (function_exists('memory_get_usage')) + $mem = rcube_ui::show_bytes(memory_get_usage()); + if (function_exists('memory_get_peak_usage')) + $mem .= '/'.rcube_ui::show_bytes(memory_get_peak_usage()); + $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : ''); + if (defined('RCMAIL_START')) + self::print_timer(RCMAIL_START, $log); + else + self::console($log); + } + } /** * Helper method to set a cookie with the current path and host settings @@ -1596,7 +819,7 @@ class rcmail $cookie = session_get_cookie_params(); setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], - rcube_https_check(), true); + rcube_ui::https_check(), true); } /** @@ -1727,4 +950,356 @@ class rcmail $this->set_storage_prop(); } + + /** + * Overwrite action variable + * + * @param string New action value + */ + public function overwrite_action($action) + { + $this->action = $action; + $this->output->set_env('action', $action); + } + + + /** + * Send the given message using the configured method. + * + * @param object $message Reference to Mail_MIME object + * @param string $from Sender address string + * @param array $mailto Array of recipient address strings + * @param array $smtp_error SMTP error array (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled + * @param array $smtp_opts SMTP options (e.g. DSN request) + * + * @return boolean Send status. + */ + public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null) + { + $headers = $message->headers(); + + // send thru SMTP server using custom SMTP library + if ($this->config->get('smtp_server')) { + // generate list of recipients + $a_recipients = array($mailto); + + if (strlen($headers['Cc'])) + $a_recipients[] = $headers['Cc']; + if (strlen($headers['Bcc'])) + $a_recipients[] = $headers['Bcc']; + + // clean Bcc from header for recipients + $send_headers = $headers; + unset($send_headers['Bcc']); + // here too, it because txtHeaders() below use $message->_headers not only $send_headers + unset($message->_headers['Bcc']); + + $smtp_headers = $message->txtHeaders($send_headers, true); + + if ($message->getParam('delay_file_io')) { + // use common temp dir + $temp_dir = $this->config->get('temp_dir'); + $body_file = tempnam($temp_dir, 'rcmMsg'); + if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$mime_result->getMessage()), + TRUE, FALSE); + return false; + } + $msg_body = fopen($body_file, 'r'); + } + else { + $msg_body = $message->get(); + } + + // send message + if (!is_object($this->smtp)) { + $this->smtp_init(true); + } + + $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts); + $smtp_response = $this->smtp->get_response(); + $smtp_error = $this->smtp->get_error(); + + // log error + if (!$sent) { + self::raise_error(array('code' => 800, 'type' => 'smtp', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE); + } + } + // send mail using PHP's mail() function + else { + // unset some headers because they will be added by the mail() function + $headers_enc = $message->headers($headers); + $headers_php = $message->_headers; + unset($headers_php['To'], $headers_php['Subject']); + + // reset stored headers and overwrite + $message->_headers = array(); + $header_str = $message->txtHeaders($headers_php); + + // #1485779 + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) { + $headers_enc['To'] = implode(', ', $m[1]); + } + } + + $msg_body = $message->get(); + + if (PEAR::isError($msg_body)) { + self::raise_error(array('code' => 650, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not create message: ".$msg_body->getMessage()), + TRUE, FALSE); + } + else { + $delim = $this->config->header_delimiter(); + $to = $headers_enc['To']; + $subject = $headers_enc['Subject']; + $header_str = rtrim($header_str); + + if ($delim != "\r\n") { + $header_str = str_replace("\r\n", $delim, $header_str); + $msg_body = str_replace("\r\n", $delim, $msg_body); + $to = str_replace("\r\n", $delim, $to); + $subject = str_replace("\r\n", $delim, $subject); + } + + if (ini_get('safe_mode')) + $sent = mail($to, $subject, $msg_body, $header_str); + else + $sent = mail($to, $subject, $msg_body, $header_str, "-f$from"); + } + } + + if ($sent) { + $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body)); + + // remove MDN headers after sending + unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); + + // get all recipients + if ($headers['Cc']) + $mailto .= $headers['Cc']; + if ($headers['Bcc']) + $mailto .= $headers['Bcc']; + if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m)) + $mailto = implode(', ', array_unique($m[1])); + + if ($this->config->get('smtp_log')) { + self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", + $this->user->get_username(), + $_SERVER['REMOTE_ADDR'], + $mailto, + !empty($smtp_response) ? join('; ', $smtp_response) : '')); + } + } + + if (is_resource($msg_body)) { + fclose($msg_body); + } + + $message->_headers = array(); + $message->headers($headers); + + return $sent; + } + + + /** + * Unique Message-ID generator. + * + * @return string Message-ID + */ + public function gen_message_id() + { + $local_part = md5(uniqid('rcmail'.mt_rand(),true)); + $domain_part = $this->user->get_username('domain'); + + // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) + if (!preg_match('/\.[a-z]+$/i', $domain_part)) { + foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) { + $host = preg_replace('/:[0-9]+$/', '', $host); + if ($host && preg_match('/\.[a-z]+$/i', $host)) { + $domain_part = $host; + } + } + } + + return sprintf('<%s@%s>', $local_part, $domain_part); + } + + + /** + * Returns RFC2822 formatted current date in user's timezone + * + * @return string Date + */ + public function user_date() + { + // get user's timezone + try { + $tz = new DateTimeZone($this->config->get('timezone')); + $date = new DateTime('now', $tz); + } + catch (Exception $e) { + $date = new DateTime(); + } + + return $date->format('r'); + } + + + /** + * E-mail address validation. + * + * @param string $email Email address + * @param boolean $dns_check True to check dns + * + * @return boolean True on success, False if address is invalid + */ + public function check_email($email, $dns_check=true) + { + // Check for invalid characters + if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) { + return false; + } + + // Check for length limit specified by RFC 5321 (#1486453) + if (strlen($email) > 254) { + return false; + } + + $email_array = explode('@', $email); + + // Check that there's one @ symbol + if (count($email_array) < 2) { + return false; + } + + $domain_part = array_pop($email_array); + $local_part = implode('@', $email_array); + + // from PEAR::Validate + $regexp = '&^(?: + ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name + ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322) + $&xi'; + + if (!preg_match($regexp, $local_part)) { + return false; + } + + // Check domain part + if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) { + return true; // IP address + } + else { + // If not an IP address + $domain_array = explode('.', $domain_part); + // Not enough parts to be a valid domain + if (sizeof($domain_array) < 2) { + return false; + } + + foreach ($domain_array as $part) { + if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) { + return false; + } + } + + if (!$dns_check || !$this->config->get('email_dns_check')) { + return true; + } + + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) { + $lookup = array(); + @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup); + foreach ($lookup as $line) { + if (strpos($line, 'MX preference')) { + return true; + } + } + return false; + } + + // find MX record(s) + if (getmxrr($domain_part, $mx_records)) { + return true; + } + + // find any DNS record + if (checkdnsrr($domain_part, 'ANY')) { + return true; + } + } + + return false; + } + + + /** + * Write login data (name, ID, IP address) to the 'userlogins' log file. + */ + public function log_login() + { + if (!$this->config->get('log_logins')) { + return; + } + + $user_name = $this->get_user_name(); + $user_id = $this->get_user_id(); + + if (!$user_id) { + return; + } + + self::write_log('userlogins', + sprintf('Successful login for %s (ID: %d) from %s in session %s', + $user_name, $user_id, self::remote_ip(), session_id())); + } + + + /** + * Check whether the HTTP referer matches the current request + * + * @return boolean True if referer is the same host+path, false if not + */ + public static function check_referer() + { + $uri = parse_url($_SERVER['REQUEST_URI']); + $referer = parse_url(rcube_request_header('Referer')); + return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path']; + } + + + /** + * Garbage collector function for temp files. + * Remove temp files older than two days + */ + public function temp_gc() + { + $tmp = unslashify($this->config->get('temp_dir')); + $expire = mktime() - 172800; // expire in 48 hours + + if ($dir = opendir($tmp)) { + while (($fname = readdir($dir)) !== false) { + if ($fname{0} == '.') { + continue; + } + + if (filemtime($tmp.'/'.$fname) < $expire) { + @unlink($tmp.'/'.$fname); + } + } + + closedir($dir); + } + } + } diff --git a/program/include/rcube.php b/program/include/rcube.php new file mode 100644 index 000000000..bee298620 --- /dev/null +++ b/program/include/rcube.php @@ -0,0 +1,1226 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcmail.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2008-2012, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Framework base class providing core functions and holding | + | instances of all 'global' objects like db- and storage-connections | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + + +/** + * Base class of the Roundcube Framework + * implemented as singleton + * + * @package Core + */ +class rcube +{ + const INIT_WITH_DB = 1; + const INIT_WITH_PLUGINS = 2; + + /** + * Singleton instace of rcmail + * + * @var rcmail + */ + static protected $instance; + + /** + * Stores instance of rcube_config. + * + * @var rcube_config + */ + public $config; + + /** + * Instace of database class. + * + * @var rcube_mdb2 + */ + public $db; + + /** + * Instace of Memcache class. + * + * @var rcube_mdb2 + */ + public $memcache; + + /** + * Instace of rcube_session class. + * + * @var rcube_session + */ + public $session; + + /** + * Instance of rcube_smtp class. + * + * @var rcube_smtp + */ + public $smtp; + + /** + * Instance of rcube_storage class. + * + * @var rcube_storage + */ + public $storage; + + /** + * Instance of rcube_output class. + * + * @var rcube_output + */ + public $output; + + /** + * Instance of rcube_plugin_api. + * + * @var rcube_plugin_api + */ + public $plugins; + + + /* private/protected vars */ + protected $texts; + protected $caches = array(); + protected $shutdown_functions = array(); + protected $expunge_cache = false; + + + /** + * This implements the 'singleton' design pattern + * + * @return rcmail The one and only instance + */ + static function get_instance() + { + if (!self::$instance) { + self::$instance = new rcube(); + } + + return self::$instance; + } + + + /** + * Private constructor + */ + protected function __construct() + { + // load configuration + $this->config = new rcube_config(); + $this->plugins = new rcube_dummy_plugin_api; + + register_shutdown_function(array($this, 'shutdown')); + } + + + /** + * Initial startup function + */ + protected function init($mode = 0) + { + // initialize syslog + if ($this->config->get('log_driver') == 'syslog') { + $syslog_id = $this->config->get('syslog_id', 'roundcube'); + $syslog_facility = $this->config->get('syslog_facility', LOG_USER); + openlog($syslog_id, LOG_ODELAY, $syslog_facility); + } + + // connect to database + if ($mode & self::INIT_WITH_DB) { + $this->get_dbh(); + } + + // create plugin API and load plugins + if ($mode & self::INIT_WITH_PLUGINS) { + $this->plugins = rcube_plugin_api::get_instance(); + } + } + + + /** + * Get the current database connection + * + * @return rcube_mdb2 Database connection object + */ + public function get_dbh() + { + if (!$this->db) { + $config_all = $this->config->all(); + + $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']); + $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql'; + $this->db->set_debug((bool)$config_all['sql_debug']); + } + + return $this->db; + } + + + /** + * Get global handle for memcache access + * + * @return object Memcache + */ + public function get_memcache() + { + if (!isset($this->memcache)) { + // no memcache support in PHP + if (!class_exists('Memcache')) { + $this->memcache = false; + return false; + } + + $this->memcache = new Memcache; + $this->mc_available = 0; + + // add alll configured hosts to pool + $pconnect = $this->config->get('memcache_pconnect', true); + foreach ($this->config->get('memcache_hosts', array()) as $host) { + list($host, $port) = explode(':', $host); + if (!$port) $port = 11211; + $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure'))); + } + + // test connection and failover (will result in $this->mc_available == 0 on complete failure) + $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist + + if (!$this->mc_available) + $this->memcache = false; + } + + return $this->memcache; + } + + + /** + * Callback for memcache failure + */ + public function memcache_failure($host, $port) + { + static $seen = array(); + + // only report once + if (!$seen["$host:$port"]++) { + $this->mc_available--; + self::raise_error(array('code' => 604, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => "Memcache failure on host $host:$port"), + true, false); + } + } + + + /** + * Initialize and get cache object + * + * @param string $name Cache identifier + * @param string $type Cache type ('db', 'apc' or 'memcache') + * @param int $ttl Expiration time for cache items in seconds + * @param bool $packed Enables/disables data serialization + * + * @return rcube_cache Cache object + */ + public function get_cache($name, $type='db', $ttl=0, $packed=true) + { + if (!isset($this->caches[$name])) { + $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed); + } + + return $this->caches[$name]; + } + + + /** + * Create SMTP object and connect to server + * + * @param boolean True if connection should be established + */ + public function smtp_init($connect = false) + { + $this->smtp = new rcube_smtp(); + + if ($connect) + $this->smtp->connect(); + } + + + /** + * Initialize and get storage object + * + * @return rcube_storage Storage object + */ + public function get_storage() + { + // already initialized + if (!is_object($this->storage)) { + $this->storage_init(); + } + + return $this->storage; + } + + + /** + * Initialize storage object + */ + public function storage_init() + { + // already initialized + if (is_object($this->storage)) { + return; + } + + $driver = $this->config->get('storage_driver', 'imap'); + $driver_class = "rcube_{$driver}"; + + if (!class_exists($driver_class)) { + self::raise_error(array( + 'code' => 700, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Storage driver class ($driver) not found!"), + true, true); + } + + // Initialize storage object + $this->storage = new $driver_class; + + // for backward compat. (deprecated, will be removed) + $this->imap = $this->storage; + + // enable caching of mail data + $storage_cache = $this->config->get("{$driver}_cache"); + $messages_cache = $this->config->get('messages_cache'); + // for backward compatybility + if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) { + $storage_cache = 'db'; + $messages_cache = true; + } + + if ($storage_cache) + $this->storage->set_caching($storage_cache); + if ($messages_cache) + $this->storage->set_messages_caching(true); + + // set pagesize from config + $pagesize = $this->config->get('mail_pagesize'); + if (!$pagesize) { + $pagesize = $this->config->get('pagesize', 50); + } + $this->storage->set_pagesize($pagesize); + + // set class options + $options = array( + 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), + 'auth_cid' => $this->config->get("{$driver}_auth_cid"), + 'auth_pw' => $this->config->get("{$driver}_auth_pw"), + 'debug' => (bool) $this->config->get("{$driver}_debug"), + 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'timeout' => (int) $this->config->get("{$driver}_timeout"), + 'skip_deleted' => (bool) $this->config->get('skip_deleted'), + 'driver' => $driver, + ); + + if (!empty($_SESSION['storage_host'])) { + $options['host'] = $_SESSION['storage_host']; + $options['user'] = $_SESSION['username']; + $options['port'] = $_SESSION['storage_port']; + $options['ssl'] = $_SESSION['storage_ssl']; + $options['password'] = $this->decrypt($_SESSION['password']); + } + + $options = $this->plugins->exec_hook("storage_init", $options); + + // for backward compat. (deprecated, to be removed) + $options = $this->plugins->exec_hook("imap_init", $options); + + $this->storage->set_options($options); + $this->set_storage_prop(); + } + + + /** + * Connect to the mail storage server with stored session data + * + * @return bool True on success, False on error + */ + public function storage_connect() + { + $storage = $this->get_storage(); + + if ($_SESSION['storage_host'] && !$storage->is_connected()) { + $host = $_SESSION['storage_host']; + $user = $_SESSION['username']; + $port = $_SESSION['storage_port']; + $ssl = $_SESSION['storage_ssl']; + $pass = $this->decrypt($_SESSION['password']); + + if (!$storage->connect($host, $user, $pass, $port, $ssl)) { + if (is_object($this->output)) + $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error'); + } + else { + $this->set_storage_prop(); + return $storage->is_connected(); + } + } + + return false; + } + + /** + * Set storage parameters. + * This must be done AFTER connecting to the server! + */ + protected function set_storage_prop() + { + $storage = $this->get_storage(); + + $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET)); + + if ($default_folders = $this->config->get('default_folders')) { + $storage->set_default_folders($default_folders); + } + if (isset($_SESSION['mbox'])) { + $storage->set_folder($_SESSION['mbox']); + } + if (isset($_SESSION['page'])) { + $storage->set_page($_SESSION['page']); + } + } + + + /** + * Get localized text in the desired language + * + * @param mixed $attrib Named parameters array or label name + * @param string $domain Label domain (plugin) name + * + * @return string Localized text + */ + public function gettext($attrib, $domain=null) + { + // load localization files if not done yet + if (empty($this->texts)) + $this->load_language(); + + // extract attributes + if (is_string($attrib)) + $attrib = array('name' => $attrib); + + $name = $attrib['name'] ? $attrib['name'] : ''; + + // attrib contain text values: use them from now + if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) + $this->texts[$name] = $setval; + + // check for text with domain + if ($domain && ($text = $this->texts[$domain.'.'.$name])) + ; + // text does not exist + else if (!($text = $this->texts[$name])) { + return "[$name]"; + } + + // replace vars in text + if (is_array($attrib['vars'])) { + foreach ($attrib['vars'] as $var_key => $var_value) + $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text); + } + + // format output + if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst']) + return ucfirst($text); + else if ($attrib['uppercase']) + return mb_strtoupper($text); + else if ($attrib['lowercase']) + return mb_strtolower($text); + + return strtr($text, array('\n' => "\n")); + } + + + /** + * Check if the given text label exists + * + * @param string $name Label name + * @param string $domain Label domain (plugin) name or '*' for all domains + * @param string $ref_domain Sets domain name if label is found + * + * @return boolean True if text exists (either in the current language or in en_US) + */ + public function text_exists($name, $domain = null, &$ref_domain = null) + { + // load localization files if not done yet + if (empty($this->texts)) + $this->load_language(); + + if (isset($this->texts[$name])) { + $ref_domain = ''; + return true; + } + + // any of loaded domains (plugins) + if ($domain == '*') { + foreach ($this->plugins->loaded_plugins() as $domain) + if (isset($this->texts[$domain.'.'.$name])) { + $ref_domain = $domain; + return true; + } + } + // specified domain + else if ($domain) { + $ref_domain = $domain; + return isset($this->texts[$domain.'.'.$name]); + } + + return false; + } + + /** + * Load a localization package + * + * @param string Language ID + */ + public function load_language($lang = null, $add = array()) + { + $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); + + // load localized texts + if (empty($this->texts) || $lang != $_SESSION['language']) { + $this->texts = array(); + + // handle empty lines after closing PHP tag in localization files + ob_start(); + + // get english labels (these should be complete) + @include(INSTALL_PATH . 'program/localization/en_US/labels.inc'); + @include(INSTALL_PATH . 'program/localization/en_US/messages.inc'); + + if (is_array($labels)) + $this->texts = $labels; + if (is_array($messages)) + $this->texts = array_merge($this->texts, $messages); + + // include user language files + if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { + include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc'); + include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc'); + + if (is_array($labels)) + $this->texts = array_merge($this->texts, $labels); + if (is_array($messages)) + $this->texts = array_merge($this->texts, $messages); + } + + ob_end_clean(); + + $_SESSION['language'] = $lang; + } + + // append additional texts (from plugin) + if (is_array($add) && !empty($add)) + $this->texts += $add; + } + + + /** + * Check the given string and return a valid language code + * + * @param string Language code + * @return string Valid language code + */ + protected function language_prop($lang) + { + static $rcube_languages, $rcube_language_aliases; + + // user HTTP_ACCEPT_LANGUAGE if no language is specified + if (empty($lang) || $lang == 'auto') { + $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); + $lang = str_replace('-', '_', $accept_langs[0]); + } + + if (empty($rcube_languages)) { + @include(INSTALL_PATH . 'program/localization/index.inc'); + } + + // check if we have an alias for that language + if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) { + $lang = $rcube_language_aliases[$lang]; + } + // try the first two chars + else if (!isset($rcube_languages[$lang])) { + $short = substr($lang, 0, 2); + + // check if we have an alias for the short language code + if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) { + $lang = $rcube_language_aliases[$short]; + } + // expand 'nn' to 'nn_NN' + else if (!isset($rcube_languages[$short])) { + $lang = $short.'_'.strtoupper($short); + } + } + + if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) { + $lang = 'en_US'; + } + + return $lang; + } + + + /** + * Read directory program/localization and return a list of available languages + * + * @return array List of available localizations + */ + public function list_languages() + { + static $sa_languages = array(); + + if (!sizeof($sa_languages)) { + @include(INSTALL_PATH . 'program/localization/index.inc'); + + if ($dh = @opendir(INSTALL_PATH . 'program/localization')) { + while (($name = readdir($dh)) !== false) { + if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) + continue; + + if ($label = $rcube_languages[$name]) + $sa_languages[$name] = $label; + } + closedir($dh); + } + } + + return $sa_languages; + } + + + /** + * Encrypt using 3DES + * + * @param string $clear clear text input + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not to base64_encode() the result before returning + * + * @return string encrypted text + */ + public function encrypt($clear, $key = 'des_key', $base64 = true) + { + if (!$clear) + return ''; + + /*- + * Add a single canary byte to the end of the clear text, which + * will help find out how much of padding will need to be removed + * upon decryption; see http://php.net/mcrypt_generic#68082 + */ + $clear = pack("a*H2", $clear, "80"); + + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) { + $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $cipher = $iv . mcrypt_generic($td, $clear); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + } + else { + @include_once 'des.inc'; + + if (function_exists('des')) { + $des_iv_size = 8; + $iv = $this->create_iv($des_iv_size); + $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); + } + else { + self::raise_error(array( + 'code' => 500, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); + } + } + + return $base64 ? base64_encode($cipher) : $cipher; + } + + /** + * Decrypt 3DES-encrypted string + * + * @param string $cipher encrypted text + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not input is base64-encoded + * + * @return string decrypted text + */ + public function decrypt($cipher, $key = 'des_key', $base64 = true) + { + if (!$cipher) + return ''; + + $cipher = $base64 ? base64_decode($cipher) : $cipher; + + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) { + $iv_size = mcrypt_enc_get_iv_size($td); + $iv = substr($cipher, 0, $iv_size); + + // session corruption? (#1485970) + if (strlen($iv) < $iv_size) + return ''; + + $cipher = substr($cipher, $iv_size); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $clear = mdecrypt_generic($td, $cipher); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + } + else { + @include_once 'des.inc'; + + if (function_exists('des')) { + $des_iv_size = 8; + $iv = substr($cipher, 0, $des_iv_size); + $cipher = substr($cipher, $des_iv_size); + $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); + } + else { + self::raise_error(array( + 'code' => 500, 'type' => 'php', + 'file' => __FILE__, 'line' => __LINE__, + 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); + } + } + + /*- + * Trim PHP's padding and the canary byte; see note in + * rcmail::encrypt() and http://php.net/mcrypt_generic#68082 + */ + $clear = substr(rtrim($clear, "\0"), 0, -1); + + return $clear; + } + + /** + * Generates encryption initialization vector (IV) + * + * @param int Vector size + * @return string Vector string + */ + private function create_iv($size) + { + // mcrypt_create_iv() can be slow when system lacks entrophy + // we'll generate IV vector manually + $iv = ''; + for ($i = 0; $i < $size; $i++) + $iv .= chr(mt_rand(0, 255)); + return $iv; + } + + + /** + * Build a valid URL to this instance of Roundcube + * + * @param mixed Either a string with the action or url parameters as key-value pairs + * @return string Valid application URL + */ + public function url($p) + { + // STUB: should be overloaded by the application + return ''; + } + + + /** + * Function to be executed in script shutdown + * Registered with register_shutdown_function() + */ + public function shutdown() + { + foreach ($this->shutdown_functions as $function) + call_user_func($function); + + if (is_object($this->smtp)) + $this->smtp->disconnect(); + + foreach ($this->caches as $cache) { + if (is_object($cache)) + $cache->close(); + } + + if (is_object($this->storage)) { + if ($this->expunge_cache) + $this->storage->expunge_cache(); + $this->storage->close(); + } + } + + + /** + * Registers shutdown function to be executed on shutdown. + * The functions will be executed before destroying any + * objects like smtp, imap, session, etc. + * + * @param callback Function callback + */ + public function add_shutdown_function($function) + { + $this->shutdown_functions[] = $function; + } + + + /** + * Use imagemagick or GD lib to read image properties + * + * @param string Absolute file path + * @return mixed Hash array with image props like type, width, height or False on error + */ + public static function imageprops($filepath) + { + $rcube = self::get_instance(); + if ($cmd = $rcube->config->get('im_identify_path', false)) { + list(, $type, $size) = explode(' ', strtolower(self::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath)))); + if ($size) + list($width, $height) = explode('x', $size); + } + else if (function_exists('getimagesize')) { + $imsize = @getimagesize($filepath); + $width = $imsize[0]; + $height = $imsize[1]; + $type = preg_replace('!image/!', '', $imsize['mime']); + } + + return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false; + } + + + /** + * Convert an image to a given size and type using imagemagick (ensures input is an image) + * + * @param $p['in'] Input filename (mandatory) + * @param $p['out'] Output filename (mandatory) + * @param $p['size'] Width x height of resulting image, e.g. "160x60" + * @param $p['type'] Output file type, e.g. "jpg" + * @param $p['-opts'] Custom command line options to ImageMagick convert + * @return Success of convert as true/false + */ + public static function imageconvert($p) + { + $result = false; + $rcube = self::get_instance(); + $convert = $rcube->config->get('im_convert_path', false); + $identify = $rcube->config->get('im_identify_path', false); + + // imagemagick is required for this + if (!$convert) + return false; + + if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false)))) + list(, $type) = explode(' ', strtolower(self::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps + + $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps")); + $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75); + $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts']; + + if (in_array($type, explode(',', $p['types']))) # Valid type? + $result = self::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === ""; + + return $result; + } + + + /** + * Construct shell command, execute it and return output as string. + * Keywords {keyword} are replaced with arguments + * + * @param $cmd Format string with {keywords} to be replaced + * @param $values (zero, one or more arrays can be passed) + * @return output of command. shell errors not detectable + */ + public static function exec(/* $cmd, $values1 = array(), ... */) + { + $args = func_get_args(); + $cmd = array_shift($args); + $values = $replacements = array(); + + // merge values into one array + foreach ($args as $arg) + $values += (array)$arg; + + preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); + foreach ($matches as $tags) { + list(, $tag, $option, $key) = $tags; + $parts = array(); + + if ($option) { + foreach ((array)$values["-$key"] as $key => $value) { + if ($value === true || $value === false || $value === null) + $parts[] = $value ? $key : ""; + else foreach ((array)$value as $val) + $parts[] = "$key " . escapeshellarg($val); + } + } + else { + foreach ((array)$values[$key] as $value) + $parts[] = escapeshellarg($value); + } + + $replacements[$tag] = join(" ", $parts); + } + + // use strtr behaviour of going through source string once + $cmd = strtr($cmd, $replacements); + + return (string)shell_exec($cmd); + } + + + /** + * Replaces hostname variables. + * + * @param string $name Hostname + * @param string $host Optional IMAP hostname + * + * @return string Hostname + */ + public static function parse_host($name, $host = '') + { + // %n - host + $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); + // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld + $d = preg_replace('/^[^\.]+\./', '', $n); + // %h - IMAP host + $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host; + // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld + $z = preg_replace('/^[^\.]+\./', '', $h); + // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided + if (strpos($name, '%s') !== false) { + $user_email = rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST); + $user_email = rcube_idn_convert($user_email, true); + $matches = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s); + if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) { + return false; + } + } + + $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name); + return $name; + } + + + /** + * Print or write debug messages + * + * @param mixed Debug message or data + */ + public static function console() + { + $args = func_get_args(); + + if (class_exists('rcmail', false)) { + $rcube = self::get_instance(); + if (is_object($rcube->plugins)) { + $plugin = $rcube->plugins->exec_hook('console', array('args' => $args)); + if ($plugin['abort']) { + return; + } + $args = $plugin['args']; + } + } + + $msg = array(); + foreach ($args as $arg) { + $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; + } + + self::write_log('console', join(";\n", $msg)); + } + + + /** + * Append a line to a logfile in the logs directory. + * Date will be added automatically to the line. + * + * @param $name name of log file + * @param line Line to append + */ + public static function write_log($name, $line) + { + if (!is_string($line)) { + $line = var_export($line, true); + } + + $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null; + $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null; + + if (empty($date_format)) { + $date_format = 'd-M-Y H:i:s O'; + } + + $date = date($date_format); + + // trigger logging hook + if (is_object(self::$instance) && is_object(self::$instance->plugins)) { + $log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line)); + $name = $log['name']; + $line = $log['line']; + $date = $log['date']; + if ($log['abort']) + return true; + } + + if ($log_driver == 'syslog') { + $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; + syslog($prio, $line); + return true; + } + + // log_driver == 'file' is assumed here + + $line = sprintf("[%s]: %s\n", $date, $line); + $log_dir = self::$instance ? self::$instance->config->get('log_dir') : null; + + if (empty($log_dir)) { + $log_dir = INSTALL_PATH . 'logs'; + } + + // try to open specific log file for writing + $logfile = $log_dir.'/'.$name; + + if ($fp = @fopen($logfile, 'a')) { + fwrite($fp, $line); + fflush($fp); + fclose($fp); + return true; + } + + trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING); + return false; + } + + + /** + * Throw system error (and show error page). + * + * @param array Named parameters + * - code: Error code (required) + * - type: Error type [php|db|imap|javascript] (required) + * - message: Error message + * - file: File where error occured + * - line: Line where error occured + * @param boolean True to log the error + * @param boolean Terminate script execution + */ + public static function raise_error($arg = array(), $log = false, $terminate = false) + { + // installer + if (class_exists('rcube_install', false)) { + $rci = rcube_install::get_instance(); + $rci->raise_error($arg); + return; + } + + if ($log && $arg['type'] && $arg['message']) { + self::log_bug($arg); + } + + // display error page and terminate script + if ($terminate && is_object(self::$instance->output)) { + self::$instance->output->raise_error($arg['code'], $arg['message']); + } + } + + + /** + * Report error according to configured debug_level + * + * @param array Named parameters + * @see self::raise_error() + */ + public static function log_bug($arg_arr) + { + $program = strtoupper($arg_arr['type']); + $level = self::get_instance()->config->get('debug_level'); + + // disable errors for ajax requests, write to log instead (#1487831) + if (($level & 4) && !empty($_REQUEST['_remote'])) { + $level = ($level ^ 4) | 1; + } + + // write error to local log file + if ($level & 1) { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']); + } + else { + $post_query = ''; + } + + $log_entry = sprintf("%s Error: %s%s (%s %s)", + $program, + $arg_arr['message'], + $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', + $_SERVER['REQUEST_METHOD'], + $_SERVER['REQUEST_URI'] . $post_query); + + if (!self::write_log('errors', $log_entry)) { + // send error to PHPs error handler if write_log didn't succeed + trigger_error($arg_arr['message']); + } + } + + // report the bug to the global bug reporting system + if ($level & 2) { + // TODO: Send error via HTTP + } + + // show error if debug_mode is on + if ($level & 4) { + print "<b>$program Error"; + + if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) { + print " in $arg_arr[file] ($arg_arr[line])"; + } + + print ':</b> '; + print nl2br($arg_arr['message']); + print '<br />'; + flush(); + } + } + + + /** + * Returns remote IP address and forwarded addresses if found + * + * @return string Remote IP address(es) + */ + public static function remote_ip() + { + $address = $_SERVER['REMOTE_ADDR']; + + // append the NGINX X-Real-IP header, if set + if (!empty($_SERVER['HTTP_X_REAL_IP'])) { + $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP']; + } + // append the X-Forwarded-For header, if set + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR']; + } + + if (!empty($remote_ip)) { + $address .= '(' . implode(',', $remote_ip) . ')'; + } + + return $address; + } + + + /** + * Returns current time (with microseconds). + * + * @return float Current time in seconds since the Unix + */ + public static function timer() + { + return microtime(true); + } + + + /** + * Logs time difference according to provided timer + * + * @param float $timer Timer (self::timer() result) + * @param string $label Log line prefix + * @param string $dest Log file name + * + * @see self::timer() + */ + public static function print_timer($timer, $label = 'Timer', $dest = 'console') + { + static $print_count = 0; + + $print_count++; + $now = self::timer(); + $diff = $now - $timer; + + if (empty($label)) { + $label = 'Timer '.$print_count; + } + + self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); + } + + + /** + * Getter for logged user ID. + * + * @return mixed User identifier + */ + public function get_user_id() + { + if (is_object($this->user)) { + return $this->user->ID; + } + + return null; + } + + + /** + * Getter for logged user name. + * + * @return string User name + */ + public function get_user_name() + { + if (is_object($this->user)) { + return $this->user->get_username(); + } + + return null; + } +} + + +/** + * Lightweight plugin API class serving as a dummy if plugins are not enabled + * + * @package Core + */ +class rcube_dummy_plugin_api +{ + /** + * Triggers a plugin hook. + * @see rcube_plugin_api::exec_hook() + */ + public function exec_hook($hook, $args = array()) + { + return $args; + } +} + diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php index b56b58a65..ca2f1e71c 100644 --- a/program/include/rcube_addressbook.php +++ b/program/include/rcube_addressbook.php @@ -211,11 +211,14 @@ abstract class rcube_addressbook */ public function validate(&$save_data, $autofix = false) { + $rcmail = rcmail::get_instance(); + // check validity of email addresses foreach ($this->get_col_values('email', $save_data, true) as $email) { if (strlen($email)) { - if (!check_email(rcube_idn_to_ascii($email))) { - $this->set_error(self::ERROR_VALIDATE, rcube_label(array('name' => 'emailformaterror', 'vars' => array('email' => $email)))); + if (!$rcmail->check_email(rcube_idn_to_ascii($email))) { + $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email))); + $this->set_error(self::ERROR_VALIDATE, $error); return false; } } diff --git a/program/include/rcube_base_replacer.php b/program/include/rcube_base_replacer.php new file mode 100644 index 000000000..f97a2ee29 --- /dev/null +++ b/program/include/rcube_base_replacer.php @@ -0,0 +1,110 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_base_replacer.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2012, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Provide basic functions for base URL replacement | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +/** + * Helper class to turn relative urls into absolute ones + * using a predefined base + * + * @package Core + * @author Thomas Bruederli <roundcube@gmail.com> + */ +class rcube_base_replacer +{ + private $base_url; + + + public function __construct($base) + { + $this->base_url = $base; + } + + + public function callback($matches) + { + return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"'; + } + + + public function replace($body) + { + return preg_replace_callback(array( + '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', + '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', + ), + array($this, 'callback'), $body); + } + + + /** + * Convert paths like ../xxx to an absolute path using a base url + * + * @param string $path Relative path + * @param string $base_url Base URL + * + * @return string Absolute URL + */ + public static function absolute_url($path, $base_url) + { + $host_url = $base_url; + $abs_path = $path; + + // check if path is an absolute URL + if (preg_match('/^[fhtps]+:\/\//', $path)) { + return $path; + } + + // check if path is a content-id scheme + if (strpos($path, 'cid:') === 0) { + return $path; + } + + // cut base_url to the last directory + if (strrpos($base_url, '/') > 7) { + $host_url = substr($base_url, 0, strpos($base_url, '/', 7)); + $base_url = substr($base_url, 0, strrpos($base_url, '/')); + } + + // $path is absolute + if ($path[0] == '/') { + $abs_path = $host_url.$path; + } + else { + // strip './' because its the same as '' + $path = preg_replace('/^\.\//', '', $path); + + if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) { + foreach ($matches as $a_match) { + if (strrpos($base_url, '/')) { + $base_url = substr($base_url, 0, strrpos($base_url, '/')); + } + $path = substr($path, 3); + } + } + + $abs_path = $base_url.'/'.$path; + } + + return $abs_path; + } +} diff --git a/program/include/rcube_cache.php b/program/include/rcube_cache.php index e5011854b..3e6bc0f32 100644 --- a/program/include/rcube_cache.php +++ b/program/include/rcube_cache.php @@ -66,7 +66,7 @@ class rcube_cache */ function __construct($type, $userid, $prefix='', $ttl=0, $packed=true) { - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); $type = strtolower($type); if ($type == 'memcache') { @@ -197,7 +197,7 @@ class rcube_cache { if ($this->type == 'db' && $this->db) { $this->db->query( - "DELETE FROM ".get_table_name('cache'). + "DELETE FROM ".$this->db->table_name('cache'). " WHERE user_id = ?". " AND cache_key LIKE ?". " AND " . $this->db->unixtimestamp('created')." < ?", @@ -274,7 +274,7 @@ class rcube_cache else { $sql_result = $this->db->limitquery( "SELECT cache_id, data, cache_key". - " FROM ".get_table_name('cache'). + " FROM ".$this->db->table_name('cache'). " WHERE user_id = ?". " AND cache_key = ?". // for better performance we allow more records for one key @@ -330,7 +330,7 @@ class rcube_cache // Remove NULL rows (here we don't need to check if the record exist) if ($data == 'N;') { $this->db->query( - "DELETE FROM ".get_table_name('cache'). + "DELETE FROM ".$this->db->table_name('cache'). " WHERE user_id = ?". " AND cache_key = ?", $this->userid, $key); @@ -341,7 +341,7 @@ class rcube_cache // update existing cache record if ($key_exists) { $result = $this->db->query( - "UPDATE ".get_table_name('cache'). + "UPDATE ".$this->db->table_name('cache'). " SET created = ". $this->db->now().", data = ?". " WHERE user_id = ?". " AND cache_key = ?", @@ -352,7 +352,7 @@ class rcube_cache // for better performance we allow more records for one key // so, no need to check if record exist (see rcube_cache::read_record()) $result = $this->db->query( - "INSERT INTO ".get_table_name('cache'). + "INSERT INTO ".$this->db->table_name('cache'). " (created, user_id, cache_key, data)". " VALUES (".$this->db->now().", ?, ?, ?)", $this->userid, $key, $data); @@ -416,7 +416,7 @@ class rcube_cache } $this->db->query( - "DELETE FROM ".get_table_name('cache'). + "DELETE FROM ".$this->db->table_name('cache'). " WHERE user_id = ?" . $where, $this->userid); } diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 46906dde1..e14dcb85a 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -85,11 +85,11 @@ class rcube_config // fix default imap folders encoding foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) - $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP'); + $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP'); if (!empty($this->prop['default_folders'])) foreach ($this->prop['default_folders'] as $n => $folder) - $this->prop['default_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP'); + $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP'); // set PHP error logging according to config if ($this->prop['debug_level'] & 1) { @@ -186,7 +186,7 @@ class rcube_config $result = $def; } - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); if ($name == 'timezone' && isset($this->prop['_timezone_value'])) $result = $this->prop['_timezone_value']; @@ -300,7 +300,7 @@ class rcube_config { // Bomb out if the requested key does not exist if (!array_key_exists($key, $this->prop)) { - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Request for unconfigured crypto key \"$key\"" @@ -311,7 +311,7 @@ class rcube_config // Bomb out if the configured key is not exactly 24 bytes long if (strlen($key) != 24) { - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Configured crypto key '$key' is not exactly 24 bytes long" @@ -335,7 +335,7 @@ class rcube_config if ($delim == "\n" || $delim == "\r\n") return $delim; else - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Invalid mail_header_delimiter setting" @@ -370,7 +370,7 @@ class rcube_config $domain = $this->prop['mail_domain'][$host]; } else if (!empty($this->prop['mail_domain'])) - $domain = rcube_parse_host($this->prop['mail_domain']); + $domain = rcmail::parse_host($this->prop['mail_domain']); if ($encode) $domain = rcube_idn_to_ascii($domain); diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php index c30751faf..8834a7dbc 100644 --- a/program/include/rcube_contacts.php +++ b/program/include/rcube_contacts.php @@ -153,7 +153,7 @@ class rcube_contacts extends rcube_addressbook $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : ''; $sql_result = $this->db->query( - "SELECT * FROM ".get_table_name($this->db_groups). + "SELECT * FROM ".$this->db->table_name($this->db_groups). " WHERE del<>1". " AND user_id=?". $sql_filter. @@ -178,7 +178,7 @@ class rcube_contacts extends rcube_addressbook function get_group($group_id) { $sql_result = $this->db->query( - "SELECT * FROM ".get_table_name($this->db_groups). + "SELECT * FROM ".$this->db->table_name($this->db_groups). " WHERE del<>1". " AND contactgroup_id=?". " AND user_id=?", @@ -214,7 +214,7 @@ class rcube_contacts extends rcube_addressbook $length = $subset != 0 ? abs($subset) : $this->page_size; if ($this->group_id) - $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m". + $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m". " ON (m.contact_id = c.".$this->primary_key.")"; $order_col = (in_array($this->sort_col, $this->table_cols) ? $this->sort_col : 'name'); @@ -228,7 +228,7 @@ class rcube_contacts extends rcube_addressbook $order_cols[] = 'c.email'; $sql_result = $this->db->limitquery( - "SELECT * FROM ".get_table_name($this->db_name)." AS c" . + "SELECT * FROM ".$this->db->table_name($this->db_name)." AS c" . $join . " WHERE c.del<>1" . " AND c.user_id=?" . @@ -488,13 +488,13 @@ class rcube_contacts extends rcube_addressbook private function _count() { if ($this->group_id) - $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m". + $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m". " ON (m.contact_id=c.".$this->primary_key.")"; // count contacts for this user $sql_result = $this->db->query( "SELECT COUNT(c.contact_id) AS rows". - " FROM ".get_table_name($this->db_name)." AS c". + " FROM ".$this->db->table_name($this->db_name)." AS c". $join. " WHERE c.del<>1". " AND c.user_id=?". @@ -536,7 +536,7 @@ class rcube_contacts extends rcube_addressbook return $assoc ? $first : $this->result; $this->db->query( - "SELECT * FROM ".get_table_name($this->db_name). + "SELECT * FROM ".$this->db->table_name($this->db_name). " WHERE contact_id=?". " AND user_id=?". " AND del<>1", @@ -568,8 +568,8 @@ class rcube_contacts extends rcube_addressbook return $results; $sql_result = $this->db->query( - "SELECT cgm.contactgroup_id, cg.name FROM " . get_table_name($this->db_groupmembers) . " AS cgm" . - " LEFT JOIN " . get_table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" . + "SELECT cgm.contactgroup_id, cg.name FROM " . $this->db->table_name($this->db_groupmembers) . " AS cgm" . + " LEFT JOIN " . $this->db->table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" . " WHERE cgm.contact_id=?", $id ); @@ -638,7 +638,7 @@ class rcube_contacts extends rcube_addressbook if (!$existing->count && !empty($a_insert_cols)) { $this->db->query( - "INSERT INTO ".get_table_name($this->db_name). + "INSERT INTO ".$this->db->table_name($this->db_name). " (user_id, changed, del, ".join(', ', $a_insert_cols).")". " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")" ); @@ -676,7 +676,7 @@ class rcube_contacts extends rcube_addressbook if (!empty($write_sql)) { $this->db->query( - "UPDATE ".get_table_name($this->db_name). + "UPDATE ".$this->db->table_name($this->db_name). " SET changed=".$this->db->now().", ".join(', ', $write_sql). " WHERE contact_id=?". " AND user_id=?". @@ -772,7 +772,7 @@ class rcube_contacts extends rcube_addressbook // flag record as deleted (always) $this->db->query( - "UPDATE ".get_table_name($this->db_name). + "UPDATE ".$this->db->table_name($this->db_name). " SET del=1, changed=".$this->db->now(). " WHERE user_id=?". " AND contact_id IN ($ids)", @@ -799,7 +799,7 @@ class rcube_contacts extends rcube_addressbook // clear deleted flag $this->db->query( - "UPDATE ".get_table_name($this->db_name). + "UPDATE ".$this->db->table_name($this->db_name). " SET del=0, changed=".$this->db->now(). " WHERE user_id=?". " AND contact_id IN ($ids)", @@ -819,7 +819,7 @@ class rcube_contacts extends rcube_addressbook { $this->cache = null; - $this->db->query("UPDATE ".get_table_name($this->db_name). + $this->db->query("UPDATE ".$this->db->table_name($this->db_name). " SET del=1, changed=".$this->db->now(). " WHERE user_id = ?", $this->user_id); @@ -841,7 +841,7 @@ class rcube_contacts extends rcube_addressbook $name = $this->unique_groupname($name); $this->db->query( - "INSERT INTO ".get_table_name($this->db_groups). + "INSERT INTO ".$this->db->table_name($this->db_groups). " (user_id, changed, name)". " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")" ); @@ -863,7 +863,7 @@ class rcube_contacts extends rcube_addressbook { // flag group record as deleted $sql_result = $this->db->query( - "UPDATE ".get_table_name($this->db_groups). + "UPDATE ".$this->db->table_name($this->db_groups). " SET del=1, changed=".$this->db->now(). " WHERE contactgroup_id=?". " AND user_id=?", @@ -889,7 +889,7 @@ class rcube_contacts extends rcube_addressbook $name = $this->unique_groupname($newname); $sql_result = $this->db->query( - "UPDATE ".get_table_name($this->db_groups). + "UPDATE ".$this->db->table_name($this->db_groups). " SET name=?, changed=".$this->db->now(). " WHERE contactgroup_id=?". " AND user_id=?", @@ -917,7 +917,7 @@ class rcube_contacts extends rcube_addressbook // get existing assignments ... $sql_result = $this->db->query( - "SELECT contact_id FROM ".get_table_name($this->db_groupmembers). + "SELECT contact_id FROM ".$this->db->table_name($this->db_groupmembers). " WHERE contactgroup_id=?". " AND contact_id IN (".$this->db->array2list($ids, 'integer').")", $group_id @@ -930,7 +930,7 @@ class rcube_contacts extends rcube_addressbook foreach ($ids as $contact_id) { $this->db->query( - "INSERT INTO ".get_table_name($this->db_groupmembers). + "INSERT INTO ".$this->db->table_name($this->db_groupmembers). " (contactgroup_id, contact_id, created)". " VALUES (?, ?, ".$this->db->now().")", $group_id, @@ -960,7 +960,7 @@ class rcube_contacts extends rcube_addressbook $ids = $this->db->array2list($ids, 'integer'); $sql_result = $this->db->query( - "DELETE FROM ".get_table_name($this->db_groupmembers). + "DELETE FROM ".$this->db->table_name($this->db_groupmembers). " WHERE contactgroup_id=?". " AND contact_id IN ($ids)", $group_id @@ -983,7 +983,7 @@ class rcube_contacts extends rcube_addressbook do { $sql_result = $this->db->query( - "SELECT 1 FROM ".get_table_name($this->db_groups). + "SELECT 1 FROM ".$this->db->table_name($this->db_groups). " WHERE del<>1". " AND user_id=?". " AND name=?", diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php deleted file mode 100644 index fffe49094..000000000 --- a/program/include/rcube_html_page.php +++ /dev/null @@ -1,323 +0,0 @@ -<?php - -/* - +-----------------------------------------------------------------------+ - | program/include/rcube_html_page.php | - | | - | This file is part of the Roundcube PHP suite | - | Copyright (C) 2005-2011 The Roundcube Dev Team | - | | - | Licensed under the GNU General Public License version 3 or | - | any later version with exceptions for skins & plugins. | - | See the README file for a full license statement. | - | | - | CONTENTS: | - | Class to build XHTML page output | - | | - +-----------------------------------------------------------------------+ - | Author: Thomas Bruederli <roundcube@gmail.com> | - +-----------------------------------------------------------------------+ - - $Id$ - -*/ - -/** - * Class for HTML page creation - * - * @package HTML - */ -class rcube_html_page -{ - protected $scripts_path = ''; - protected $script_files = array(); - protected $css_files = array(); - protected $scripts = array(); - protected $charset = RCMAIL_CHARSET; - protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>"; - - protected $title = ''; - protected $header = ''; - protected $footer = ''; - protected $body = ''; - protected $base_path = ''; - - - /** Constructor */ - public function __construct() {} - - /** - * Link an external script file - * - * @param string File URL - * @param string Target position [head|foot] - */ - public function include_script($file, $position='head') - { - static $sa_files = array(); - - if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { - $file = $this->scripts_path . $file; - if ($fs = @filemtime($file)) { - $file .= '?s=' . $fs; - } - } - - if (in_array($file, $sa_files)) { - return; - } - - $sa_files[] = $file; - - if (!is_array($this->script_files[$position])) { - $this->script_files[$position] = array(); - } - - $this->script_files[$position][] = $file; - } - - /** - * Add inline javascript code - * - * @param string JS code snippet - * @param string Target position [head|head_top|foot] - */ - public function add_script($script, $position='head') - { - if (!isset($this->scripts[$position])) { - $this->scripts[$position] = "\n" . rtrim($script); - } - else { - $this->scripts[$position] .= "\n" . rtrim($script); - } - } - - /** - * Link an external css file - * - * @param string File URL - */ - public function include_css($file) - { - $this->css_files[] = $file; - } - - /** - * Add HTML code to the page header - * - * @param string $str HTML code - */ - public function add_header($str) - { - $this->header .= "\n" . $str; - } - - /** - * Add HTML code to the page footer - * To be added right befor </body> - * - * @param string $str HTML code - */ - public function add_footer($str) - { - $this->footer .= "\n" . $str; - } - - /** - * Setter for page title - * - * @param string $t Page title - */ - public function set_title($t) - { - $this->title = $t; - } - - /** - * Setter for output charset. - * To be specified in a meta tag and sent as http-header - * - * @param string $charset Charset - */ - public function set_charset($charset) - { - $this->charset = $charset; - } - - /** - * Getter for output charset - * - * @return string Output charset - */ - public function get_charset() - { - return $this->charset; - } - - /** - * Reset all saved properties - */ - public function reset() - { - $this->script_files = array(); - $this->scripts = array(); - $this->title = ''; - $this->header = ''; - $this->footer = ''; - $this->body = ''; - } - - /** - * Process template and write to stdOut - * - * @param string HTML template - * @param string Base for absolute paths - */ - public function write($templ='', $base_path='') - { - $output = empty($templ) ? $this->default_template : trim($templ); - - // set default page title - if (empty($this->title)) { - $this->title = 'Roundcube Mail'; - } - - // replace specialchars in content - $page_title = Q($this->title, 'show', FALSE); - $page_header = ''; - $page_footer = ''; - - // include meta tag with charset - if (!empty($this->charset)) { - if (!headers_sent()) { - header('Content-Type: text/html; charset=' . $this->charset); - } - $page_header = '<meta http-equiv="content-type"'; - $page_header.= ' content="text/html; charset='; - $page_header.= $this->charset . '" />'."\n"; - } - - // definition of the code to be placed in the document header and footer - if (is_array($this->script_files['head'])) { - foreach ($this->script_files['head'] as $file) { - $page_header .= html::script($file); - } - } - - $head_script = $this->scripts['head_top'] . $this->scripts['head']; - if (!empty($head_script)) { - $page_header .= html::script(array(), $head_script); - } - - if (!empty($this->header)) { - $page_header .= $this->header; - } - - // put docready commands into page footer - if (!empty($this->scripts['docready'])) { - $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot'); - } - - if (is_array($this->script_files['foot'])) { - foreach ($this->script_files['foot'] as $file) { - $page_footer .= html::script($file); - } - } - - if (!empty($this->footer)) { - $page_footer .= $this->footer . "\n"; - } - - if (!empty($this->scripts['foot'])) { - $page_footer .= html::script(array(), $this->scripts['foot']); - } - - // find page header - if ($hpos = stripos($output, '</head>')) { - $page_header .= "\n"; - } - else { - if (!is_numeric($hpos)) { - $hpos = stripos($output, '<body'); - } - if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) { - while ($output[$hpos] != '>') { - $hpos++; - } - $hpos++; - } - $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\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, '</body>')) || ($fpos = strripos($output, '</html>'))) { - $output = substr_replace($output, $page_footer."\n", $fpos, 0); - } - else { - $output .= "\n".$page_footer; - } - - // add css files in head, before scripts, for speed up with parallel downloads - if (!empty($this->css_files) && - (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>'))) - ) { - $css = ''; - foreach ($this->css_files as $file) { - $css .= html::tag('link', array('rel' => 'stylesheet', - 'type' => 'text/css', 'href' => $file, 'nl' => true)); - } - $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); - - // trigger hook with final HTML content to be sent - $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output)); - if (!$hook['abort']) { - if ($this->charset != RCMAIL_CHARSET) { - echo rcube_charset_convert($hook['content'], RCMAIL_CHARSET, $this->charset); - } - else { - echo $hook['content']; - } - } - } - - /** - * Callback function for preg_replace_callback in write() - * - * @return string Parsed string - */ - private 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]; - } -} diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index bd8f35176..966fc54b6 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -132,7 +132,7 @@ class rcube_imap extends rcube_storage $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; } else if ($use_ssl) { - raise_error(array('code' => 403, 'type' => 'imap', + rcube::raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "OpenSSL not available"), true, false); $port = 143; @@ -154,7 +154,7 @@ class rcube_imap extends rcube_storage $attempt = 0; do { - $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', + $data = rcube::get_instance()->plugins->exec_hook('imap_connect', array_merge($this->options, array('host' => $host, 'user' => $user, 'attempt' => ++$attempt))); @@ -185,9 +185,9 @@ class rcube_imap extends rcube_storage else if ($this->conn->error) { if ($pass && $user) { $message = sprintf("Login failed for %s from %s. %s", - $user, rcmail_remote_ip(), $this->conn->error); + $user, rcmail::remote_ip(), $this->conn->error); - raise_error(array('code' => 403, 'type' => 'imap', + rcube::raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'line' => __LINE__, 'message' => $message), true, false); } @@ -457,7 +457,7 @@ class rcube_imap extends rcube_storage return; } - $config = rcmail::get_instance()->config; + $config = rcube::get_instance()->config; $imap_personal = $config->get('imap_ns_personal'); $imap_other = $config->get('imap_ns_other'); $imap_shared = $config->get('imap_ns_shared'); @@ -546,7 +546,7 @@ class rcube_imap extends rcube_storage $folder = $this->folder; } - return $this->messagecount($folder, $mode, $force, $status); + return $this->countmessages($folder, $mode, $force, $status); } @@ -562,7 +562,7 @@ class rcube_imap extends rcube_storage * @return int Number of messages * @see rcube_imap::count() */ - protected function messagecount($folder, $mode='ALL', $force=false, $status=true) + protected function countmessages($folder, $mode='ALL', $force=false, $status=true) { $mode = strtoupper($mode); @@ -834,8 +834,8 @@ class rcube_imap extends rcube_storage * protected method for setting threaded messages flags: * depth, has_children and unread_children * - * @param array $headers Reference to headers array indexed by message UID - * @param rcube_imap_result $threads Threads data object + * @param array $headers Reference to headers array indexed by message UID + * @param rcube_result_thread $threads Threads data object * * @return array Message headers array indexed by message UID */ @@ -1048,7 +1048,7 @@ class rcube_imap extends rcube_storage if ($sort) { // use this class for message sorting - $sorter = new rcube_header_sorter(); + $sorter = new rcube_message_header_sorter(); $sorter->set_index($msgs); $sorter->sort_headers($a_msg_headers); } @@ -1075,7 +1075,7 @@ class rcube_imap extends rcube_storage $old = $this->get_folder_stats($folder); // refresh message count -> will update - $this->messagecount($folder, 'ALL', true); + $this->countmessages($folder, 'ALL', true); $result = 0; @@ -1456,7 +1456,7 @@ class rcube_imap extends rcube_storage foreach ($matches[1] as $m) { $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n $string = substr($str, $string_offset - 1, $m[0]); - $string = rcube_charset_convert($string, $charset, $dest_charset); + $string = rcube_charset::convert($string, $charset, $dest_charset); if ($string === false) { continue; } @@ -1498,7 +1498,7 @@ class rcube_imap extends rcube_storage * @param string $folder Folder to read from * @param bool $force True to skip cache * - * @return rcube_mail_header Message headers + * @return rcube_message_header Message headers */ public function get_message_headers($uid, $folder = null, $force = false) { @@ -1529,7 +1529,7 @@ class rcube_imap extends rcube_storage * @param int $uid Message UID to fetch * @param string $folder Folder to read from * - * @return object rcube_mail_header Message data + * @return object rcube_message_header Message data */ public function get_message($uid, $folder = null) { @@ -1948,7 +1948,7 @@ class rcube_imap extends rcube_storage $charset = $this->struct_charset; } else { - $charset = rc_detect_encoding($filename_mime, $this->default_charset); + $charset = rcube_charset::detect($filename_mime, $this->default_charset); } $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset); @@ -1960,7 +1960,7 @@ class rcube_imap extends rcube_storage $filename_encoded = $fmatches[2]; } - $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); + $part->filename = rcube_charset::convert(urldecode($filename_encoded), $filename_charset); } } @@ -2039,7 +2039,7 @@ class rcube_imap extends rcube_storage $o_part->charset = $this->default_charset; } } - $body = rcube_charset_convert($body, $o_part->charset); + $body = rcube_charset::convert($body, $o_part->charset); } } @@ -2227,7 +2227,7 @@ class rcube_imap extends rcube_storage } } - $config = rcmail::get_instance()->config; + $config = rcube::get_instance()->config; $to_trash = $to_mbox == $config->get('trash_mbox'); // flag messages as read before moving them @@ -2510,7 +2510,7 @@ class rcube_imap extends rcube_storage $a_defaults = $a_out = array(); // Give plugins a chance to provide a list of folders - $data = rcmail::get_instance()->plugins->exec_hook('storage_folders', + $data = rcube::get_instance()->plugins->exec_hook('storage_folders', array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB')); if (isset($data['folders'])) { @@ -2521,7 +2521,7 @@ class rcube_imap extends rcube_storage } else { // Server supports LIST-EXTENDED, we can use selection options - $config = rcmail::get_instance()->config; + $config = rcube::get_instance()->config; // #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED if (!$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED')) { // This will also set folder options, LSUB doesn't do that @@ -3530,7 +3530,7 @@ class rcube_imap extends rcube_storage protected function get_cache_engine() { if ($this->caching && !$this->cache) { - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); $ttl = $rcmail->config->get('message_cache_lifetime', '10d') - mktime(); $this->cache = $rcmail->get_cache('IMAP', $this->caching, $ttl); } @@ -3589,8 +3589,9 @@ class rcube_imap extends rcube_storage $this->mcache->expunge($ttl); } - if ($this->cache) + if ($this->cache) { $this->cache->expunge(); + } } @@ -3624,10 +3625,10 @@ class rcube_imap extends rcube_storage protected function get_mcache_engine() { if ($this->messages_caching && !$this->mcache) { - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); if ($dbh = $rcmail->get_dbh()) { $this->mcache = new rcube_imap_cache( - $dbh, $this, $rcmail->user->ID, $this->options['skip_deleted']); + $dbh, $this, $rcmail->get_user_id(), $this->options['skip_deleted']); } } @@ -3691,7 +3692,7 @@ class rcube_imap extends rcube_storage $a_defaults[$p] = $folder; } else { - $folders[$folder] = rcube_charset_convert($folder, 'UTF7-IMAP'); + $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP'); } } @@ -3851,7 +3852,7 @@ class rcube_imap extends rcube_storage */ public function debug_handler(&$imap, $message) { - write_log('imap', $message); + rcmail::write_log('imap', $message); } diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php index ee53dc25c..eaa8a805f 100644 --- a/program/include/rcube_imap_cache.php +++ b/program/include/rcube_imap_cache.php @@ -95,7 +95,7 @@ class rcube_imap_cache { $this->db = $db; $this->imap = $imap; - $this->userid = (int)$userid; + $this->userid = $userid; $this->skip_deleted = $skip_deleted; } @@ -290,7 +290,7 @@ class rcube_imap_cache * @param string $mailbox Folder name * @param array $msgs Message UIDs * - * @return array The list of messages (rcube_mail_header) indexed by UID + * @return array The list of messages (rcube_message_header) indexed by UID */ function get_messages($mailbox, $msgs = array()) { @@ -301,7 +301,7 @@ class rcube_imap_cache // Fetch messages from cache $sql_result = $this->db->query( "SELECT uid, data, flags" - ." FROM ".get_table_name('cache_messages') + ." FROM ".$this->db->table_name('cache_messages') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid IN (".$this->db->array2list($msgs, 'integer').")", @@ -348,7 +348,7 @@ class rcube_imap_cache * from IMAP server * @param bool $no_cache Enables internal cache usage * - * @return rcube_mail_header Message data + * @return rcube_message_header Message data */ function get_message($mailbox, $uid, $update = true, $cache = true) { @@ -362,7 +362,7 @@ class rcube_imap_cache $sql_result = $this->db->query( "SELECT flags, data" - ." FROM ".get_table_name('cache_messages') + ." FROM ".$this->db->table_name('cache_messages') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid = ?", @@ -404,9 +404,9 @@ class rcube_imap_cache /** * Saves the message in cache. * - * @param string $mailbox Folder name - * @param rcube_mail_header $message Message data - * @param bool $force Skips message in-cache existance check + * @param string $mailbox Folder name + * @param rcube_message_header $message Message data + * @param bool $force Skips message in-cache existance check */ function add_message($mailbox, $message, $force = false) { @@ -430,7 +430,7 @@ class rcube_imap_cache // here will work as select, assume row exist if affected_rows=0) if (!$force) { $res = $this->db->query( - "UPDATE ".get_table_name('cache_messages') + "UPDATE ".$this->db->table_name('cache_messages') ." SET flags = ?, data = ?, changed = ".$this->db->now() ." WHERE user_id = ?" ." AND mailbox = ?" @@ -444,7 +444,7 @@ class rcube_imap_cache // insert new record $this->db->query( - "INSERT INTO ".get_table_name('cache_messages') + "INSERT INTO ".$this->db->table_name('cache_messages') ." (user_id, mailbox, uid, flags, changed, data)" ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)", $this->userid, $mailbox, (int) $message->uid, $flags, $msg); @@ -479,7 +479,7 @@ class rcube_imap_cache } $this->db->query( - "UPDATE ".get_table_name('cache_messages') + "UPDATE ".$this->db->table_name('cache_messages') ." SET changed = ".$this->db->now() .", flags = flags ".($enabled ? "+ $idx" : "- $idx") ." WHERE user_id = ?" @@ -500,7 +500,7 @@ class rcube_imap_cache { if (!strlen($mailbox)) { $this->db->query( - "DELETE FROM ".get_table_name('cache_messages') + "DELETE FROM ".$this->db->table_name('cache_messages') ." WHERE user_id = ?", $this->userid); } @@ -513,11 +513,11 @@ class rcube_imap_cache } $this->db->query( - "DELETE FROM ".get_table_name('cache_messages') + "DELETE FROM ".$this->db->table_name('cache_messages') ." WHERE user_id = ?" - ." AND mailbox = ".$this->db->quote($mailbox) + ." AND mailbox = ?" .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""), - $this->userid); + $this->userid, $mailbox); } } @@ -536,17 +536,19 @@ class rcube_imap_cache // otherwise use 'valid' flag to not loose HIGHESTMODSEQ value if ($remove) { $this->db->query( - "DELETE FROM ".get_table_name('cache_index') - ." WHERE user_id = ".intval($this->userid) - .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "") + "DELETE FROM ".$this->db->table_name('cache_index') + ." WHERE user_id = ?" + .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""), + $this->userid ); } else { $this->db->query( - "UPDATE ".get_table_name('cache_index') + "UPDATE ".$this->db->table_name('cache_index') ." SET valid = 0" - ." WHERE user_id = ".intval($this->userid) - .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "") + ." WHERE user_id = ?" + .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""), + $this->userid ); } @@ -569,9 +571,10 @@ class rcube_imap_cache function remove_thread($mailbox = null) { $this->db->query( - "DELETE FROM ".get_table_name('cache_thread') - ." WHERE user_id = ".intval($this->userid) - .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "") + "DELETE FROM ".$this->db->table_name('cache_thread') + ." WHERE user_id = ?" + .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""), + $this->userid ); if (strlen($mailbox)) { @@ -628,7 +631,7 @@ class rcube_imap_cache // Get index from DB $sql_result = $this->db->query( "SELECT data, valid" - ." FROM ".get_table_name('cache_index') + ." FROM ".$this->db->table_name('cache_index') ." WHERE user_id = ?" ." AND mailbox = ?", $this->userid, $mailbox); @@ -665,7 +668,7 @@ class rcube_imap_cache // Get thread from DB $sql_result = $this->db->query( "SELECT data" - ." FROM ".get_table_name('cache_thread') + ." FROM ".$this->db->table_name('cache_thread') ." WHERE user_id = ?" ." AND mailbox = ?", $this->userid, $mailbox); @@ -709,7 +712,7 @@ class rcube_imap_cache if ($exists) { $sql_result = $this->db->query( - "UPDATE ".get_table_name('cache_index') + "UPDATE ".$this->db->table_name('cache_index') ." SET data = ?, valid = 1, changed = ".$this->db->now() ." WHERE user_id = ?" ." AND mailbox = ?", @@ -717,7 +720,7 @@ class rcube_imap_cache } else { $sql_result = $this->db->query( - "INSERT INTO ".get_table_name('cache_index') + "INSERT INTO ".$this->db->table_name('cache_index') ." (user_id, mailbox, data, valid, changed)" ." VALUES (?, ?, ?, 1, ".$this->db->now().")", $this->userid, $mailbox, $data); @@ -740,7 +743,7 @@ class rcube_imap_cache if ($exists) { $sql_result = $this->db->query( - "UPDATE ".get_table_name('cache_thread') + "UPDATE ".$this->db->table_name('cache_thread') ." SET data = ?, changed = ".$this->db->now() ." WHERE user_id = ?" ." AND mailbox = ?", @@ -748,7 +751,7 @@ class rcube_imap_cache } else { $sql_result = $this->db->query( - "INSERT INTO ".get_table_name('cache_thread') + "INSERT INTO ".$this->db->table_name('cache_thread') ." (user_id, mailbox, data, changed)" ." VALUES (?, ?, ?, ".$this->db->now().")", $this->userid, $mailbox, $data); @@ -956,7 +959,7 @@ class rcube_imap_cache $uids = array(); $sql_result = $this->db->query( "SELECT uid" - ." FROM ".get_table_name('cache_messages') + ." FROM ".$this->db->table_name('cache_messages') ." WHERE user_id = ?" ." AND mailbox = ?", $this->userid, $mailbox); @@ -1003,7 +1006,7 @@ class rcube_imap_cache } $this->db->query( - "UPDATE ".get_table_name('cache_messages') + "UPDATE ".$this->db->table_name('cache_messages') ." SET flags = ?, changed = ".$this->db->now() ." WHERE user_id = ?" ." AND mailbox = ?" @@ -1058,7 +1061,7 @@ class rcube_imap_cache * * @param array $sql_arr Message row data * - * @return rcube_mail_header Message object + * @return rcube_message_header Message object */ private function build_message($sql_arr) { diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index a664c5b57..fb2ded1d3 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -29,42 +29,9 @@ */ -/** - * Struct representing an e-mail message header - * - * @package Mail - * @author Aleksander Machniak <alec@alec.pl> - */ -class rcube_mail_header -{ - public $id; - public $uid; - public $subject; - public $from; - public $to; - public $cc; - public $replyto; - public $in_reply_to; - public $date; - public $messageID; - public $size; - public $encoding; - public $charset; - public $ctype; - public $timestamp; - public $bodystructure; - public $internaldate; - public $references; - public $priority; - public $mdn_to; - public $others = array(); - public $flags = array(); -} +// for backward copat. +class rcube_mail_header extends rcube_message_header { } -// For backward compatibility with cached messages (#1486602) -class iilBasicHeader extends rcube_mail_header -{ -} /** * PHP based wrapper class to connect to an IMAP server @@ -1545,8 +1512,6 @@ class rcube_imap_generic */ function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII') { - require_once dirname(__FILE__) . '/rcube_result_index.php'; - $field = strtoupper($field); if ($field == 'INTERNALDATE') { $field = 'ARRIVAL'; @@ -1595,8 +1560,6 @@ class rcube_imap_generic */ function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII') { - require_once dirname(__FILE__) . '/rcube_result_thread.php'; - $old_sel = $this->selected; if (!$this->select($mailbox)) { @@ -1635,8 +1598,6 @@ class rcube_imap_generic */ function search($mailbox, $criteria, $return_uid=false, $items=array()) { - require_once dirname(__FILE__) . '/rcube_result_index.php'; - $old_sel = $this->selected; if (!$this->select($mailbox)) { @@ -1696,8 +1657,6 @@ class rcube_imap_generic function index($mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false, $return_uid=false) { - require_once dirname(__FILE__) . '/rcube_result_index.php'; - $msg_index = $this->fetchHeaderIndex($mailbox, $message_set, $index_field, $skip_deleted, $uidfetch, $return_uid); @@ -2034,7 +1993,7 @@ class rcube_imap_generic * @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query * @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query * - * @return array List of rcube_mail_header elements, False on error + * @return array List of rcube_message_header elements, False on error * @since 0.6 */ function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(), @@ -2074,7 +2033,7 @@ class rcube_imap_generic if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { $id = intval($m[1]); - $result[$id] = new rcube_mail_header; + $result[$id] = new rcube_message_header; $result[$id]->id = $id; $result[$id]->subject = ''; $result[$id]->messageID = 'mid:' . $id; diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index 08b7cd99c..545fef9a5 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -63,12 +63,11 @@ class rcube_ldap extends rcube_addressbook /** * Object constructor * - * @param array LDAP connection properties - * @param boolean Enables debug mode - * @param string Current user mail domain name - * @param integer User-ID + * @param array $p LDAP connection properties + * @param boolean $debug Enables debug mode + * @param string $mail_domain Current user mail domain name */ - function __construct($p, $debug=false, $mail_domain=NULL) + function __construct($p, $debug = false, $mail_domain = null) { $this->prop = $p; @@ -176,10 +175,10 @@ class rcube_ldap extends rcube_addressbook */ private function _connect() { - global $RCMAIL; + $RCMAIL = rcmail::get_instance(); if (!function_exists('ldap_connect')) - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "No ldap support in this installation of PHP"), true, true); @@ -195,7 +194,7 @@ class rcube_ldap extends rcube_addressbook foreach ($this->prop['hosts'] as $host) { - $host = idn_to_ascii(rcube_parse_host($host)); + $host = idn_to_ascii(rcmail::parse_host($host)); $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : ''); $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]"); @@ -225,7 +224,7 @@ class rcube_ldap extends rcube_addressbook } if (!is_resource($this->conn)) { - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not connect to any LDAP server, last tried $hostname"), true); @@ -248,7 +247,7 @@ class rcube_ldap extends rcube_addressbook } // Get the pieces needed for variable replacement. - if ($fu = $RCMAIL->user->get_username()) + if ($fu = $RCMAIL->get_user_name()) list($u, $d) = explode('@', $fu); else $d = $this->mail_domain; @@ -287,7 +286,7 @@ class rcube_ldap extends rcube_addressbook if (!empty($this->prop['search_dn_default'])) $replaces['%dn'] = $this->prop['search_dn_default']; else { - raise_error(array( + rcube::raise_error(array( 'code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "DN not found using LDAP search."), true); @@ -341,7 +340,7 @@ class rcube_ldap extends rcube_addressbook } if (!function_exists('ldap_sasl_bind')) { - raise_error(array('code' => 100, 'type' => 'ldap', + rcube::raise_error(array('code' => 100, 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Unable to bind: ldap_sasl_bind() not exists"), true, true); @@ -367,7 +366,7 @@ class rcube_ldap extends rcube_addressbook $this->_debug("S: ".ldap_error($this->conn)); - raise_error(array( + rcube::raise_error(array( 'code' => ldap_errno($this->conn), 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)), @@ -400,7 +399,7 @@ class rcube_ldap extends rcube_addressbook $this->_debug("S: ".ldap_error($this->conn)); - raise_error(array( + rcube::raise_error(array( 'code' => ldap_errno($this->conn), 'type' => 'ldap', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)), @@ -1562,8 +1561,9 @@ class rcube_ldap extends rcube_addressbook */ private function _debug($str) { - if ($this->debug) - write_log('ldap', $str); + if ($this->debug) { + rcmail::write_log('ldap', $str); + } } diff --git a/program/include/rcube_mdb2.php b/program/include/rcube_mdb2.php index c103f9a61..0139bdc65 100644 --- a/program/include/rcube_mdb2.php +++ b/program/include/rcube_mdb2.php @@ -59,10 +59,11 @@ class rcube_mdb2 * @param string $db_dsnw DSN for read/write operations * @param string $db_dsnr Optional DSN for read only operations */ - function __construct($db_dsnw, $db_dsnr='', $pconn=false) + public function __construct($db_dsnw, $db_dsnr='', $pconn=false) { - if (empty($db_dsnr)) + if (empty($db_dsnr)) { $db_dsnr = $db_dsnw; + } $this->db_dsnw = $db_dsnw; $this->db_dsnr = $db_dsnr; @@ -88,7 +89,8 @@ class rcube_mdb2 'emulate_prepared' => $this->debug_mode, 'debug' => $this->debug_mode, 'debug_handler' => array($this, 'debug_handler'), - 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL); + 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL, + ); if ($this->db_provider == 'pgsql') { $db_options['disable_smart_seqname'] = true; @@ -103,17 +105,19 @@ class rcube_mdb2 $this->db_error = true; $this->db_error_msg = $dbh->getMessage(); - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $dbh->getUserInfo()), true, false); } else if ($this->db_provider == 'sqlite') { $dsn_array = MDB2::parseDSN($dsn); - if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) - $this->_sqlite_create_database($dbh, $this->sqlite_initials); + if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) { + $this->sqlite_create_database($dbh, $this->sqlite_initials); + } } - else if ($this->db_provider!='mssql' && $this->db_provider!='sqlsrv') + else if ($this->db_provider != 'mssql' && $this->db_provider != 'sqlsrv') { $dbh->setCharset('utf8'); + } return $dbh; } @@ -123,9 +127,8 @@ class rcube_mdb2 * Connect to appropiate database depending on the operation * * @param string $mode Connection mode (r|w) - * @access public */ - function db_connect($mode) + public function db_connect($mode) { // previous connection failed, don't attempt to connect again if ($this->conn_failure) { @@ -157,10 +160,12 @@ class rcube_mdb2 $this->db_connected = !PEAR::isError($this->db_handle); } - if ($this->db_connected) + if ($this->db_connected) { $this->db_mode = $mode; - else + } + else { $this->conn_failure = true; + } } @@ -168,9 +173,8 @@ class rcube_mdb2 * Activate/deactivate debug mode * * @param boolean $dbg True if SQL queries should be logged - * @access public */ - function set_debug($dbg = true) + public function set_debug($dbg = true) { $this->debug_mode = $dbg; if ($this->db_connected) { @@ -184,9 +188,8 @@ class rcube_mdb2 * Getter for error state * * @param boolean True on error - * @access public */ - function is_error() + public function is_error() { return $this->db_error ? $this->db_error_msg : false; } @@ -196,9 +199,8 @@ class rcube_mdb2 * Connection state checker * * @param boolean True if in connected state - * @access public */ - function is_connected() + public function is_connected() { return PEAR::isError($this->db_handle) ? false : $this->db_connected; } @@ -208,7 +210,7 @@ class rcube_mdb2 * Is database replication configured? * This returns true if dsnw != dsnr */ - function is_replicated() + public function is_replicated() { return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr; } @@ -219,17 +221,18 @@ class rcube_mdb2 * * @param string SQL query to execute * @param mixed Values to be inserted in query + * * @return number Query handle identifier - * @access public */ - function query() + public function query() { $params = func_get_args(); $query = array_shift($params); // Support one argument of type array, instead of n arguments - if (count($params) == 1 && is_array($params[0])) + if (count($params) == 1 && is_array($params[0])) { $params = $params[0]; + } return $this->_query($query, 0, 0, $params); } @@ -242,10 +245,10 @@ class rcube_mdb2 * @param number Offset for LIMIT statement * @param number Number of rows for LIMIT statement * @param mixed Values to be inserted in query + * * @return number Query handle identifier - * @access public */ - function limitquery() + public function limitquery() { $params = func_get_args(); $query = array_shift($params); @@ -274,17 +277,21 @@ class rcube_mdb2 $this->db_connect($mode); // check connection before proceeding - if (!$this->is_connected()) + if (!$this->is_connected()) { return null; + } - if ($this->db_provider == 'sqlite') - $this->_sqlite_prepare(); + if ($this->db_provider == 'sqlite') { + $this->sqlite_prepare(); + } - if ($numrows || $offset) + if ($numrows || $offset) { $result = $this->db_handle->setLimit($numrows,$offset); + } - if (empty($params)) + if (empty($params)) { $result = $mode == 'r' ? $this->db_handle->query($query) : $this->db_handle->exec($query); + } else { $params = (array)$params; $q = $this->db_handle->prepare($query, null, $mode=='w' ? MDB2_PREPARE_MANIP : null); @@ -292,7 +299,7 @@ class rcube_mdb2 $this->db_error = true; $this->db_error_msg = $q->userinfo; - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $this->db_error_msg), true, false); @@ -315,17 +322,18 @@ class rcube_mdb2 * * @param number $res_id Optional query handle identifier * @return mixed Number of rows or false on failure - * @access public */ - function num_rows($res_id=null) + public function num_rows($res_id=null) { - if (!$this->db_connected) + if (!$this->db_connected) { return false; + } - if ($result = $this->_get_result($res_id)) + if ($result = $this->_get_result($res_id)) { return $result->numRows(); - else - return false; + } + + return false; } @@ -334,12 +342,12 @@ class rcube_mdb2 * * @param number $res_id Optional query handle identifier * @return mixed Number of rows or false on failure - * @access public */ - function affected_rows($res_id = null) + public function affected_rows($res_id = null) { - if (!$this->db_connected) + if (!$this->db_connected) { return false; + } return $this->_get_result($res_id); } @@ -350,21 +358,24 @@ class rcube_mdb2 * For Postgres databases, a sequence name is required * * @param string $table Table name (to find the incremented sequence) + * * @return mixed ID or false on failure - * @access public */ - function insert_id($table = '') + public function insert_id($table = '') { - if (!$this->db_connected || $this->db_mode == 'r') + if (!$this->db_connected || $this->db_mode == 'r') { return false; + } if ($table) { - if ($this->db_provider == 'pgsql') + if ($this->db_provider == 'pgsql') { // find sequence name - $table = get_sequence_name($table); - else + $table = $this->sequence_name($table); + } + else { // resolve table name - $table = get_table_name($table); + $table = $this->table_name($table); + } } $id = $this->db_handle->lastInsertID($table); @@ -378,10 +389,10 @@ class rcube_mdb2 * If no query handle is specified, the last query will be taken as reference * * @param number $res_id Optional query handle identifier + * * @return mixed Array with col values or false on failure - * @access public */ - function fetch_assoc($res_id=null) + public function fetch_assoc($res_id = null) { $result = $this->_get_result($res_id); return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC); @@ -393,10 +404,10 @@ class rcube_mdb2 * If no query handle is specified, the last query will be taken as reference * * @param number $res_id Optional query handle identifier + * * @return mixed Array with col values or false on failure - * @access public */ - function fetch_array($res_id=null) + public function fetch_array($res_id = null) { $result = $this->_get_result($res_id); return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED); @@ -408,13 +419,14 @@ class rcube_mdb2 * * @param MDB2_Result_Common Query $result result handle * @param number $mode Fetch mode identifier - * @return mixed Array with col values or false on failure - * @access private + * + * @return mixed Array with col values or false on failure */ private function _fetch_row($result, $mode) { - if ($result === false || PEAR::isError($result) || !$this->is_connected()) + if ($result === false || PEAR::isError($result) || !$this->is_connected()) { return false; + } return $result->fetchRow($mode); } @@ -424,18 +436,19 @@ class rcube_mdb2 * Wrapper for the SHOW TABLES command * * @return array List of all tables of the current database - * @access public * @since 0.4-beta */ - function list_tables() + public function list_tables() { // get tables if not cached if (!$this->tables) { $this->db_handle->loadModule('Manager'); - if (!PEAR::isError($result = $this->db_handle->listTables())) + if (!PEAR::isError($result = $this->db_handle->listTables())) { $this->tables = $result; - else + } + else { $this->tables = array(); + } } return $this->tables; @@ -446,9 +459,10 @@ class rcube_mdb2 * Wrapper for SHOW COLUMNS command * * @param string Table name + * * @return array List of table cols */ - function list_cols($table) + public function list_cols($table) { $this->db_handle->loadModule('Manager'); if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) { @@ -464,18 +478,20 @@ class rcube_mdb2 * * @param mixed $input Value to quote * @param string $type Type of data + * * @return string Quoted/converted string for use in query - * @access public */ - function quote($input, $type = null) + public function quote($input, $type = null) { // handle int directly for better performance - if ($type == 'integer') + if ($type == 'integer') { return intval($input); + } // create DB handle if not available - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_connected ? $this->db_handle->quote($input, $type) : addslashes($input); } @@ -485,12 +501,12 @@ class rcube_mdb2 * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote + * * @return string Quoted string for use in query * @deprecated Replaced by rcube_MDB2::quote_identifier * @see rcube_mdb2::quote_identifier - * @access public */ - function quoteIdentifier($str) + public function quoteIdentifier($str) { return $this->quote_identifier($str); } @@ -500,13 +516,14 @@ class rcube_mdb2 * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote + * * @return string Quoted string for use in query - * @access public */ - function quote_identifier($str) + public function quote_identifier($str) { - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_connected ? $this->db_handle->quoteIdentifier($str) : $str; } @@ -516,14 +533,15 @@ class rcube_mdb2 * Escapes a string * * @param string $str The string to be escaped + * * @return string The escaped string - * @access public * @since 0.1.1 */ - function escapeSimple($str) + public function escapeSimple($str) { - if (!$this->db_handle) + if (!$this->db_handle) { $this->db_connect('r'); + } return $this->db_handle->escape($str); } @@ -533,9 +551,8 @@ class rcube_mdb2 * Return SQL function for current time and date * * @return string SQL function to use in query - * @access public */ - function now() + public function now() { switch ($this->db_provider) { case 'mssql': @@ -553,16 +570,18 @@ class rcube_mdb2 * * @param array $arr Input array * @param string $type Type of data + * * @return string Comma-separated list of quoted values for use in query - * @access public */ - function array2list($arr, $type = null) + public function array2list($arr, $type = null) { - if (!is_array($arr)) + if (!is_array($arr)) { return $this->quote($arr, $type); + } - foreach ($arr as $idx => $item) + foreach ($arr as $idx => $item) { $arr[$idx] = $this->quote($item, $type); + } return implode(',', $arr); } @@ -575,10 +594,11 @@ class rcube_mdb2 * of timestamp functions in Mysql (year 2038 problem) * * @param string $field Field name + * * @return string SQL statement to use in query * @deprecated */ - function unixtimestamp($field) + public function unixtimestamp($field) { switch($this->db_provider) { case 'pgsql': @@ -598,10 +618,10 @@ class rcube_mdb2 * Return SQL statement to convert from a unix timestamp * * @param string $timestamp Field name + * * @return string SQL statement to use in query - * @access public */ - function fromunixtime($timestamp) + public function fromunixtime($timestamp) { return date("'Y-m-d H:i:s'", $timestamp); } @@ -612,13 +632,13 @@ class rcube_mdb2 * * @param string $column Field name * @param string $value Search value + * * @return string SQL statement to use in query - * @access public */ - function ilike($column, $value) + public function ilike($column, $value) { // TODO: use MDB2's matchPattern() function - switch($this->db_provider) { + switch ($this->db_provider) { case 'pgsql': return $this->quote_identifier($column).' ILIKE '.$this->quote($value); default: @@ -626,20 +646,20 @@ class rcube_mdb2 } } + /** * Abstract SQL statement for value concatenation * * @return string SQL statement to be used in query - * @access public */ - function concat(/* col1, col2, ... */) + public function concat(/* col1, col2, ... */) { $func = ''; $args = func_get_args(); if (is_array($args[0])) $args = $args[0]; - switch($this->db_provider) { + switch ($this->db_provider) { case 'mysql': case 'mysqli': $func = 'CONCAT'; @@ -661,20 +681,22 @@ class rcube_mdb2 * Encodes non-UTF-8 characters in string/array/object (recursive) * * @param mixed $input Data to fix + * * @return mixed Properly UTF-8 encoded data - * @access public */ - function encode($input) + public static function encode($input) { if (is_object($input)) { - foreach (get_object_vars($input) as $idx => $value) - $input->$idx = $this->encode($value); + foreach (get_object_vars($input) as $idx => $value) { + $input->$idx = self::encode($value); + } return $input; } else if (is_array($input)) { - foreach ($input as $idx => $value) - $input[$idx] = $this->encode($value); - return $input; + foreach ($input as $idx => $value) { + $input[$idx] = self::encode($value); + } + return $input; } return utf8_encode($input); @@ -685,20 +707,22 @@ class rcube_mdb2 * Decodes encoded UTF-8 string/object/array (recursive) * * @param mixed $input Input data + * * @return mixed Decoded data - * @access public */ - function decode($input) + public static function decode($input) { if (is_object($input)) { - foreach (get_object_vars($input) as $idx => $value) - $input->$idx = $this->decode($value); + foreach (get_object_vars($input) as $idx => $value) { + $input->$idx = self::decode($value); + } return $input; } else if (is_array($input)) { - foreach ($input as $idx => $value) - $input[$idx] = $this->decode($value); - return $input; + foreach ($input as $idx => $value) { + $input[$idx] = self::decode($value); + } + return $input; } return utf8_decode($input); @@ -709,8 +733,8 @@ class rcube_mdb2 * Adds a query result and returns a handle ID * * @param object $res Query handle + * * @return mixed Handle ID - * @access private */ private function _add_result($res) { @@ -718,7 +742,7 @@ class rcube_mdb2 if (PEAR::isError($res)) { $this->db_error = true; $this->db_error_msg = $res->getMessage(); - raise_error(array('code' => 500, 'type' => 'db', + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), @@ -737,17 +761,20 @@ class rcube_mdb2 * If no ID is specified, the last resource handle will be returned * * @param number $res_id Handle ID + * * @return mixed Resource handle or false on failure - * @access private */ private function _get_result($res_id = null) { - if ($res_id == null) + if ($res_id == null) { $res_id = $this->last_res_id; + } - if (isset($this->a_query_results[$res_id])) - if (!PEAR::isError($this->a_query_results[$res_id])) + if (isset($this->a_query_results[$res_id])) { + if (!PEAR::isError($this->a_query_results[$res_id])) { return $this->a_query_results[$res_id]; + } + } return false; } @@ -758,42 +785,36 @@ class rcube_mdb2 * * @param MDB2 $dbh SQLite database handle * @param string $file_name File path to use for DB creation - * @access private */ - private function _sqlite_create_database($dbh, $file_name) + private function sqlite_create_database($dbh, $file_name) { - if (empty($file_name) || !is_string($file_name)) + if (empty($file_name) || !is_string($file_name)) { return; + } $data = file_get_contents($file_name); - if (strlen($data)) - if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) - raise_error(array('code' => 500, 'type' => 'db', + if (strlen($data)) { + if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) { + rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, - 'message' => $error), true, false); + 'message' => $error), true, false); + } + } } /** * Add some proprietary database functions to the current SQLite handle * in order to make it MySQL compatible - * - * @access private */ - private function _sqlite_prepare() + private function sqlite_prepare() { - include_once(INSTALL_PATH . 'program/include/rcube_sqlite.inc'); - - // we emulate via callback some missing MySQL function - sqlite_create_function($this->db_handle->connection, - 'from_unixtime', 'rcube_sqlite_from_unixtime'); + // we emulate via callback some missing MySQL functions sqlite_create_function($this->db_handle->connection, - 'unix_timestamp', 'rcube_sqlite_unix_timestamp'); + 'unix_timestamp', array('rcube_mdb2', 'sqlite_unix_timestamp')); sqlite_create_function($this->db_handle->connection, - 'now', 'rcube_sqlite_now'); - sqlite_create_function($this->db_handle->connection, - 'md5', 'rcube_sqlite_md5'); + 'now', array('rcube_mdb2', 'sqlite_now')); } @@ -805,8 +826,82 @@ class rcube_mdb2 if ($scope != 'prepare') { $debug_output = sprintf('%s(%d): %s;', $scope, $db->db_index, rtrim($message, ';')); - write_log('sql', $debug_output); + rcmail::write_log('sql', $debug_output); } } -} // end class rcube_db + + /** + * Return correct name for a specific database table + * + * @param string $table Table name + * + * @return string Translated table name + */ + public function table_name($table) + { + $rcmail = rcube::get_instance(); + + // return table name if configured + $config_key = 'db_table_'.$table; + + if ($name = $rcmail->config->get($config_key)) { + return $name; + } + + return $table; + } + + + /** + * Return correct name for a specific database sequence + * (used for Postgres only) + * + * @param string $sequence Secuence name + * + * @return string Translated sequence name + */ + public function sequence_name($sequence) + { + $rcmail = rcube::get_instance(); + + // return sequence name if configured + $config_key = 'db_sequence_'.$sequence; + + if ($name = $rcmail->config->get($config_key)) { + return $name; + } + + return $sequence; + } + + + /** + * Callback for sqlite: unix_timestamp() + */ + public static function sqlite_unix_timestamp($timestamp = '') + { + $timestamp = trim($timestamp); + if (!$timestamp) { + $ret = time(); + } + else if (!preg_match('/^[0-9]+$/s', $timestamp)) { + $ret = strtotime($timestamp); + } + else { + $ret = $timestamp; + } + + return $ret; + } + + + /** + * Callback for sqlite: now() + */ + public static function sqlite_now() + { + return date("Y-m-d H:i:s"); + } + +} diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 76246a2d8..295b06ee6 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -78,7 +78,7 @@ class rcube_message function __construct($uid) { $this->uid = $uid; - $this->app = rcmail::get_instance(); + $this->app = rcube::get_instance(); $this->storage = $this->app->get_storage(); $this->storage->set_options(array('all_headers' => true)); @@ -96,8 +96,10 @@ class rcube_message $this->opt = array( 'safe' => $this->is_safe, 'prefer_html' => $this->app->config->get('prefer_html'), - 'get_url' => rcmail_url('get', array( - '_mbox' => $this->storage->get_folder(), '_uid' => $uid)) + 'get_url' => $this->app->url(array( + 'action' => 'get', + 'mbox' => $this->storage->get_folder(), + 'uid' => $uid)) ); if (!empty($this->headers->structure)) { @@ -380,7 +382,8 @@ class rcube_message $c->type = 'content'; $c->ctype_primary = 'text'; $c->ctype_secondary = 'plain'; - $c->body = rcube_label('htmlmessage'); + $c->mimetype = 'text/plain'; + $c->realtype = 'text/html'; $this->parts[] = $c; } @@ -388,7 +391,6 @@ class rcube_message // add html part as attachment if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { $html_part = &$structure->parts[$html_part]; - $html_part->filename = rcube_label('htmlmessage'); $html_part->mimetype = 'text/html'; $this->attachments[] = $html_part; @@ -400,8 +402,8 @@ class rcube_message $p->type = 'content'; $p->ctype_primary = 'text'; $p->ctype_secondary = 'plain'; - $p->body = rcube_label('encryptedmessage'); - $p->size = strlen($p->body); + $p->mimetype = 'text/plain'; + $p->realtype = 'multipart/encrypted'; $this->parts[] = $p; } @@ -671,7 +673,7 @@ class rcube_message $uupart->size = strlen($uupart->body); $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid; - $ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); + $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); $uupart->mimetype = $ctype; list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype); diff --git a/program/include/rcube_message_header.php b/program/include/rcube_message_header.php new file mode 100644 index 000000000..ee7e1e34b --- /dev/null +++ b/program/include/rcube_message_header.php @@ -0,0 +1,238 @@ +<?php + +/** + +-----------------------------------------------------------------------+ + | program/include/rcube_message_header.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | E-mail message headers representation | + | | + +-----------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +/** + * Struct representing an e-mail message header + * + * @package Mail + * @author Aleksander Machniak <alec@alec.pl> + */ +class rcube_message_header +{ + /** + * Message sequence number + * + * @var int + */ + public $id; + + /** + * Message unique identifier + * + * @var int + */ + public $uid; + + /** + * Message subject + * + * @var string + */ + public $subject; + + /** + * Message sender (From) + * + * @var string + */ + public $from; + + /** + * Message recipient (To) + * + * @var string + */ + public $to; + + /** + * Message additional recipients (Cc) + * + * @var string + */ + public $cc; + + /** + * Message Reply-To header + * + * @var string + */ + public $replyto; + + /** + * Message In-Reply-To header + * + * @var string + */ + public $in_reply_to; + + /** + * Message date (Date) + * + * @var string + */ + public $date; + + /** + * Message identifier (Message-ID) + * + * @var string + */ + public $messageID; + + /** + * Message size + * + * @var int + */ + public $size; + + /** + * Message encoding + * + * @var string + */ + public $encoding; + + /** + * Message charset + * + * @var string + */ + public $charset; + + /** + * Message Content-type + * + * @var string + */ + public $ctype; + + /** + * Message timestamp (based on message date) + * + * @var int + */ + public $timestamp; + + /** + * IMAP bodystructure string + * + * @var string + */ + public $bodystructure; + + /** + * IMAP internal date + * + * @var string + */ + public $internaldate; + + /** + * Message References header + * + * @var string + */ + public $references; + + /** + * Message priority (X-Priority) + * + * @var int + */ + public $priority; + + /** + * Message receipt recipient + * + * @var string + */ + public $mdn_to; + + /** + * Other message headers + * + * @var array + */ + public $others = array(); + + /** + * Message flags + * + * @var array + */ + public $flags = array(); +} + + +/** + * Class for sorting an array of rcube_message_header objects in a predetermined order. + * + * @package Mail + * @author Aleksander Machniak <alec@alec.pl> + */ +class rcube_message_header_sorter +{ + private $uids = array(); + + + /** + * Set the predetermined sort order. + * + * @param array $index Numerically indexed array of IMAP UIDs + */ + function set_index($index) + { + $index = array_flip($index); + + $this->uids = $index; + } + + /** + * Sort the array of header objects + * + * @param array $headers Array of rcube_message_header objects indexed by UID + */ + function sort_headers(&$headers) + { + uksort($headers, array($this, "compare_uids")); + } + + /** + * Sort method called by uksort() + * + * @param int $a Array key (UID) + * @param int $b Array key (UID) + */ + function compare_uids($a, $b) + { + // then find each sequence number in my ordered list + $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1; + $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1; + + // return the relative position as the comparison value + return $posa - $posb; + } +} diff --git a/program/include/rcube_message_part.php b/program/include/rcube_message_part.php new file mode 100644 index 000000000..799dc0f99 --- /dev/null +++ b/program/include/rcube_message_part.php @@ -0,0 +1,103 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_message_part.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Class representing a message part | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + + +/** + * Class representing a message part + * + * @package Mail + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + * @version 2.0 + */ +class rcube_message_part +{ + /** + * Part MIME identifier + * + * @var string + */ + public $mime_id = ''; + + /** + * Content main type + * + * @var string + */ + public $ctype_primary = 'text'; + + /** + * Content subtype + * + * @var string + */ + public $ctype_secondary = 'plain'; + + /** + * Complete content type + * + * @var string + */ + public $mimetype = 'text/plain'; + + public $disposition = ''; + public $filename = ''; + public $encoding = '8bit'; + public $charset = ''; + + /** + * Part size in bytes + * + * @var int + */ + public $size = 0; + + /** + * Part headers + * + * @var array + */ + public $headers = array(); + + public $d_parameters = array(); + public $ctype_parameters = array(); + + + /** + * Clone handler. + */ + function __clone() + { + if (isset($this->parts)) { + foreach ($this->parts as $idx => $part) { + if (is_object($part)) { + $this->parts[$idx] = clone $part; + } + } + } + } + +} diff --git a/program/include/rcube_output.php b/program/include/rcube_output.php new file mode 100644 index 000000000..575f1062d --- /dev/null +++ b/program/include/rcube_output.php @@ -0,0 +1,259 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_output.php | + | | + | This file is part of the Roundcube PHP suite | + | Copyright (C) 2005-2012 The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | CONTENTS: | + | Abstract class for output generation | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +/** + * Class for output generation + * + * @package HTML + */ +abstract class rcube_output +{ + public $browser; + public $type = 'html'; + public $ajax_call = false; + public $framed = false; + + protected $app; + protected $config; + protected $charset = RCMAIL_CHARSET; + protected $env = array(); + protected $pagetitle = ''; + protected $object_handlers = array(); + + + /** + * Object constructor + */ + public function __construct($task = null, $framed = false) + { + $this->app = rcmail::get_instance(); + $this->config = $this->app->config; + $this->browser = new rcube_browser(); + } + + + /** + * Setter for page title + * + * @param string $title Page title + */ + public function set_pagetitle($title) + { + $this->pagetitle = $title; + } + + + /** + * Setter for output charset. + * To be specified in a meta tag and sent as http-header + * + * @param string $charset Charset name + */ + public function set_charset($charset) + { + $this->charset = $charset; + } + + + /** + * Getter for output charset + * + * @return string Output charset name + */ + public function get_charset() + { + return $this->charset; + } + + + /** + * Getter for the current skin path property + */ + public function get_skin_path() + { + return $this->config->get('skin_path'); + } + + + /** + * Set environment variable + * + * @param string $name Property name + * @param mixed $value Property value + */ + public function set_env($name, $value) + { + $this->env[$name] = $value; + } + + + /** + * Environment variable getter. + * + * @param string $name Property name + * + * @return mixed Property value + */ + public function get_env($name) + { + return $this->env[$name]; + } + + + /** + * Delete all stored env variables and commands + */ + public function reset() + { + $this->env = array(); + $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(); + + + /** + * 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 displaying time in seconds + */ + abstract function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0); + + + /** + * 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 + */ + abstract function redirect($p = array(), $delay = 1); + + + /** + * Send output to the client. + */ + abstract function send(); + + + /** + * 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); + } + + + /** + * Send HTTP headers to prevent caching a page + */ + public function nocacheing_headers() + { + if (headers_sent()) { + return; + } + + header("Expires: ".gmdate("D, d M Y H:i:s")." GMT"); + header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + + // Request browser to disable DNS prefetching (CVE-2010-0464) + header("X-DNS-Prefetch-Control: off"); + + // We need to set the following headers to make downloads work using IE in HTTPS mode. + if ($this->browser->ie && rcube_ui::https_check()) { + header('Pragma: private'); + header("Cache-Control: private, must-revalidate"); + } + else { + header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0"); + header("Pragma: no-cache"); + } + } + + + /** + * Show error page and terminate script execution + * + * @param int $code Error code + * @param string $message Error message + */ + public function raise_error($code, $message) + { + // STUB: to be overloaded by specific output classes + fputs(STDERR, "Error $code: $message\n"); + exit(-1); + } + + + /** + * Convert a variable into a javascript object notation + * + * @param mixed Input value + * + * @return string Serialized JSON string + */ + public static function json_serialize($input) + { + $input = rcube_charset::clean($input); + + // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences + // that's why we have @ here + return @json_encode($input); + } + +} diff --git a/program/include/rcube_template.php b/program/include/rcube_output_html.php index b2bdda488..a047aba45 100644 --- a/program/include/rcube_template.php +++ b/program/include/rcube_output_html.php @@ -2,10 +2,10 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_template.php | + | program/include/rcubeoutput_html.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2011, The Roundcube Dev Team | + | Copyright (C) 2006-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -13,7 +13,6 @@ | | | PURPOSE: | | Class to handle HTML page output using a skin template. | - | Extends rcube_html_page class from rcube_shared.inc | | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | @@ -28,33 +27,32 @@ * Class to create HTML page output using a skin template * * @package View - * @todo Documentation - * @uses rcube_html_page */ -class rcube_template extends rcube_html_page +class rcube_output_html extends rcube_output { - private $app; - private $config; - private $pagetitle = ''; - private $message = null; - private $js_env = array(); - private $js_labels = array(); - private $js_commands = array(); - private $object_handlers = array(); - private $plugin_skin_path; - private $template_name; - - public $browser; - public $framed = false; - public $env = array(); public $type = 'html'; - public $ajax_call = false; + + protected $message = null; + protected $js_env = array(); + protected $js_labels = array(); + protected $js_commands = array(); + protected $plugin_skin_path; + protected $template_name; + protected $scripts_path = ''; + protected $script_files = array(); + protected $css_files = array(); + protected $scripts = array(); + protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>"; + protected $header = ''; + protected $footer = ''; + protected $body = ''; + protected $base_path = ''; // deprecated names of templates used before 0.5 - private $deprecated_templates = array( - 'contact' => 'showcontact', - 'contactadd' => 'addcontact', - 'contactedit' => 'editcontact', + protected $deprecated_templates = array( + 'contact' => 'showcontact', + 'contactadd' => 'addcontact', + 'contactedit' => 'editcontact', 'identityedit' => 'editidentity', 'messageprint' => 'printmessage', ); @@ -64,20 +62,16 @@ class rcube_template extends rcube_html_page * * @todo Replace $this->config with the real rcube_config object */ - public function __construct($task, $framed = false) + public function __construct($task = null, $framed = false) { parent::__construct(); - $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('x_frame_options', $this->app->config->get('x_frame_options', 'sameorigin')); + $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin')); // load the correct skin (in case user-defined) - $this->set_skin($this->config['skin']); + $this->set_skin($this->config->get('skin')); // add common javascripts $this->add_script('var '.JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top'); @@ -101,6 +95,7 @@ class rcube_template extends rcube_html_page )); } + /** * Set environment variable * @@ -116,26 +111,22 @@ class rcube_template extends rcube_html_page } } - /** - * Set page title variable - */ - public function set_pagetitle($title) - { - $this->pagetitle = $title; - } /** * Getter for the current page title * * @return string The page title */ - public function get_pagetitle() + protected 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']))); + $title = $this->app->gettext(array( + 'name' => 'welcome', + 'vars' => array('product' => $this->config->get('product_name') + ))); } else { $title = ucfirst($this->env['task']); @@ -144,6 +135,7 @@ class rcube_template extends rcube_html_page return $title; } + /** * Set skin */ @@ -156,23 +148,18 @@ class rcube_template extends rcube_html_page $valid = true; } else { - $skin_path = $this->config['skin_path'] ? $this->config['skin_path'] : 'skins/default'; + $skin_path = $this->config->get('skin_path'); + if (!$skin_path) { + $skin_path = 'skins/default'; + } $valid = !$skin; } - $this->app->config->set('skin_path', $skin_path); - $this->config['skin_path'] = $skin_path; + $this->config->set('skin_path', $skin_path); return $valid; } - /** - * Getter for the current skin path property - */ - public function get_skin_path() - { - return $this->config['skin_path']; - } /** * Check if a specific template exists @@ -182,32 +169,10 @@ class rcube_template extends rcube_html_page */ public function template_exists($name) { - $filename = $this->config['skin_path'] . '/templates/' . $name . '.html'; + $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])); } - /** - * 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); - } /** * Register a GUI object to the client script @@ -221,6 +186,7 @@ class rcube_template extends rcube_html_page $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');"); } + /** * Call a client method * @@ -236,6 +202,7 @@ class rcube_template extends rcube_html_page $this->js_commands[] = $cmd; } + /** * Add a localized label to the client environment */ @@ -246,10 +213,11 @@ class rcube_template extends rcube_html_page $args = $args[0]; foreach ($args as $name) { - $this->js_labels[$name] = rcube_label($name); + $this->js_labels[$name] = $this->app->gettext($name); } } + /** * Invoke display_message command * @@ -263,10 +231,10 @@ class rcube_template extends rcube_html_page public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0) { if ($override || !$this->message) { - if (rcube_label_exists($message)) { + if ($this->app->text_exists($message)) { if (!empty($vars)) $vars = array_map('Q', $vars); - $msgtext = rcube_label(array('name' => $message, 'vars' => $vars)); + $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars)); } else $msgtext = $message; @@ -276,39 +244,38 @@ class rcube_template extends rcube_html_page } } + /** * Delete all stored env variables and commands - * - * @return void - * @uses rcube_html::reset() - * @uses self::$env - * @uses self::$js_env - * @uses self::$js_commands - * @uses self::$object_handlers */ public function reset() { - $this->env = array(); + parent::reset(); $this->js_env = array(); $this->js_labels = array(); $this->js_commands = array(); - $this->object_handlers = array(); - parent::reset(); + $this->script_files = array(); + $this->scripts = array(); + $this->header = ''; + $this->footer = ''; + $this->body = ''; } + /** * Redirect to a certain url * - * @param mixed Either a string with the action or url parameters as key-value pairs - * @see rcmail::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()) + public function redirect($p = array(), $delay = 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 @@ -321,7 +288,7 @@ class rcube_template extends rcube_html_page if ($templ != 'iframe') { // prevent from endless loops if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) { - raise_error(array('code' => 505, 'type' => 'php', + rcube::raise_error(array('code' => 505, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => 'Recursion alert: ignoring output->send()'), true, false); return; @@ -342,12 +309,11 @@ class rcube_template extends rcube_html_page } } + /** * Process template and write to stdOut * - * @param string HTML template - * @see rcube_html_page::write() - * @override + * @param string $template HTML template content */ public function write($template = '') { @@ -374,9 +340,10 @@ class rcube_template extends rcube_html_page header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe)); // call super method - parent::write($template, $this->config['skin_path']); + $this->_write($template, $this->config->get('skin_path')); } + /** * Parse a specific skin template and deliver to stdout (or return) * @@ -388,7 +355,7 @@ class rcube_template extends rcube_html_page */ function parse($name = 'main', $exit = true, $write = true) { - $skin_path = $this->config['skin_path']; + $skin_path = $this->config->get('skin_path'); $plugin = false; $realname = $name; $temp = explode('.', $name, 2); @@ -399,7 +366,7 @@ class rcube_template extends rcube_html_page if (count($temp) > 1) { $plugin = $temp[0]; $name = $temp[1]; - $skin_dir = $plugin . '/skins/' . $this->config['skin']; + $skin_dir = $plugin . '/skins/' . $this->config->get('skin'); $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir; // fallback to default skin @@ -414,16 +381,16 @@ class rcube_template extends rcube_html_page if (!is_readable($path) && $this->deprecated_templates[$realname]) { $path = "$skin_path/templates/".$this->deprecated_templates[$realname].".html"; if (is_readable($path)) - raise_error(array('code' => 502, 'type' => 'php', + rcube::raise_error(array('code' => 502, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Using deprecated template '".$this->deprecated_templates[$realname] - ."' in ".$this->config['skin_path']."/templates. Please rename to '".$realname."'"), + ."' in $skin_path/templates. Please rename to '".$realname."'"), true, false); } // read template file if (($templ = @file_get_contents($path)) === false) { - raise_error(array( + rcube::raise_error(array( 'code' => 501, 'type' => 'php', 'line' => __LINE__, @@ -458,7 +425,7 @@ class rcube_template extends rcube_html_page if ($write) { // add debug console - if ($realname != 'error' && ($this->config['debug_level'] & 8)) { + if ($realname != 'error' && ($this->config->get('debug_level') & 8)) { $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;display:none"> <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" style="display:none;width:400px;border:none;font-size:10px" spellcheck="false"></textarea></div>' @@ -480,16 +447,17 @@ class rcube_template extends rcube_html_page } } + /** * Return executable javascript code for all registered commands * * @return string $out */ - private function get_js_commands() + protected function get_js_commands() { $out = ''; if (!$this->framed && !empty($this->js_env)) { - $out .= JS_OBJECT_NAME . '.set_env('.json_serialize($this->js_env).");\n"; + $out .= JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n"; } if (!empty($this->js_labels)) { $this->command('add_label', $this->js_labels); @@ -497,7 +465,7 @@ class rcube_template extends rcube_html_page foreach ($this->js_commands as $i => $args) { $method = array_shift($args); foreach ($args as $i => $arg) { - $args[$i] = json_serialize($arg); + $args[$i] = self::json_serialize($arg); } $parent = $this->framed || preg_match('/^parent\./', $method); $out .= sprintf( @@ -511,6 +479,7 @@ class rcube_template extends rcube_html_page return $out; } + /** * Make URLs starting with a slash point to skin directory * @@ -520,41 +489,62 @@ class rcube_template extends rcube_html_page public function abs_url($str) { if ($str[0] == '/') - return $this->config['skin_path'] . $str; + return $this->config->get('skin_path') . $str; else return $str; } + /** + * Show error page and terminate script execution + * + * @param int $code Error code + * @param string $message Error message + */ + public function raise_error($code, $message) + { + global $__page_content, $ERROR_CODE, $ERROR_MESSAGE; + + $ERROR_CODE = $code; + $ERROR_MESSAGE = $message; + + include INSTALL_PATH . 'program/steps/utils/error.inc'; + exit; + } + + /***** Template parsing methods *****/ /** * Replace all strings ($varname) * with the content of the according global variable. */ - private function parse_with_globals($input) + protected function parse_with_globals($input) { - $GLOBALS['__version'] = Q(RCMAIL_VERSION); - $GLOBALS['__comm_path'] = Q($this->app->comm_path); - $GLOBALS['__skin_path'] = Q($this->config['skin_path']); + $GLOBALS['__version'] = html::quote(RCMAIL_VERSION); + $GLOBALS['__comm_path'] = html::quote($this->app->comm_path); + $GLOBALS['__skin_path'] = Q($this->config->get('skin_path')); + return preg_replace_callback('/\$(__[a-z0-9_\-]+)/', - array($this, 'globals_callback'), $input); + array($this, 'globals_callback'), $input); } + /** * Callback funtion for preg_replace_callback() in parse_with_globals() */ - private function globals_callback($matches) + protected function globals_callback($matches) { return $GLOBALS[$matches[1]]; } + /** * Public wrapper to dipp into template parsing. * * @param string $input * @return string - * @uses rcube_template::parse_xml() + * @uses rcube_output_html::parse_xml() * @since 0.1-rc1 */ public function just_parse($input) @@ -562,20 +552,21 @@ class rcube_template extends rcube_html_page return $this->parse_xml($input); } + /** * Parse for conditional tags * * @param string $input * @return string */ - private function parse_conditions($input) + protected function parse_conditions($input) { $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>\n?/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE); if ($matches && count($matches) == 4) { if (preg_match('/^(else|endif)$/i', $matches[1])) { return $matches[0] . $this->parse_conditions($matches[3]); } - $attrib = parse_attrib_string($matches[2]); + $attrib = html::parse_attrib_string($matches[2]); if (isset($attrib['condition'])) { $condmet = $this->check_condition($attrib['condition']); $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE); @@ -588,7 +579,7 @@ class rcube_template extends rcube_html_page } return $matches[0] . $this->parse_conditions($result); } - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, @@ -608,7 +599,7 @@ class rcube_template extends rcube_html_page * @param string Condition statement * @return boolean True if condition is met, False if not */ - private function check_condition($condition) + protected function check_condition($condition) { return eval("return (".$this->parse_expression($condition).");"); } @@ -617,10 +608,10 @@ class rcube_template extends rcube_html_page /** * Inserts hidden field with CSRF-prevention-token into POST forms */ - private function alter_form_tag($matches) + protected function alter_form_tag($matches) { - $out = $matches[0]; - $attrib = parse_attrib_string($matches[1]); + $out = $matches[0]; + $attrib = html::parse_attrib_string($matches[1]); if (strtolower($attrib['method']) == 'post') { $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token())); @@ -637,7 +628,7 @@ class rcube_template extends rcube_html_page * @param string Expression statement * @return string Expression value */ - private function parse_expression($expression) + protected function parse_expression($expression) { return preg_replace( array( @@ -653,7 +644,7 @@ class rcube_template extends rcube_html_page "\$_SESSION['\\1']", "\$this->app->config->get('\\1',get_boolean('\\3'))", "\$this->env['\\1']", - "get_input_value('\\1', RCUBE_INPUT_GPC)", + "rcube_ui::get_input_value('\\1', rcube_ui::INPUT_GPC)", "\$_COOKIE['\\1']", "\$this->browser->{'\\1'}", $this->template_name, @@ -671,7 +662,7 @@ class rcube_template extends rcube_html_page * @todo Use DOM-parser to traverse template HTML * @todo Maybe a cache. */ - private function parse_xml($input) + protected function parse_xml($input) { return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input); } @@ -684,10 +675,10 @@ class rcube_template extends rcube_html_page * @param array Matches array of preg_replace_callback * @return string Tag/Object content */ - private function xml_command($matches) + protected function xml_command($matches) { $command = strtolower($matches[1]); - $attrib = parse_attrib_string($matches[2]); + $attrib = html::parse_attrib_string($matches[2]); // empty output if required condition is not met if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) { @@ -706,20 +697,20 @@ class rcube_template extends rcube_html_page // show a label case 'label': if ($attrib['name'] || $attrib['command']) { - $vars = $attrib + array('product' => $this->config['product_name']); + $vars = $attrib + array('product' => $this->config->get('product_name')); unset($vars['name'], $vars['command']); - $label = rcube_label($attrib + array('vars' => $vars)); - return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : Q($label)) : ''; + $label = $this->app->gettext($attrib + array('vars' => $vars)); + return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : html::quote($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['skin_path']).$attrib['file']); - + $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config->get('skin_path')).$attrib['file']); + if (is_readable($path)) { - if ($this->config['skin_include_php']) { + if ($this->config->get('skin_include_php')) { $incl = $this->include_php($path); } else { @@ -765,13 +756,13 @@ class rcube_template extends rcube_html_page } else if ($object == 'logo') { $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"'))); - if ($this->config['skin_logo']) - $attrib['src'] = $this->config['skin_logo']; + if ($logo = $this->config->get('skin_logo')) + $attrib['src'] = $logo; $content = html::img($attrib); } else if ($object == 'productname') { - $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail'; - $content = Q($name); + $name = $this->config->get('product_name', 'Roundcube Webmail'); + $content = html::quote($name); } else if ($object == 'version') { $ver = (string)RCMAIL_VERSION; @@ -779,20 +770,20 @@ class rcube_template extends rcube_html_page if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs)) $ver .= ' [SVN r'.$regs[1].']'; } - $content = Q($ver); + $content = html::quote($ver); } else if ($object == 'steptitle') { - $content = Q($this->get_pagetitle()); + $content = html::quote($this->get_pagetitle()); } else if ($object == 'pagetitle') { - if (!empty($this->config['devel_mode']) && !empty($_SESSION['username'])) - $title = $_SESSION['username'].' :: '; - else if (!empty($this->config['product_name'])) - $title = $this->config['product_name'].' :: '; + if ($this->config->get('devel_mode') && !empty($_SESSION['username'])) + $title = $_SESSION['username'].' :: '; + else if ($prod_name = $this->config->get('product_name')) + $title = $prod_name . ' :: '; else - $title = ''; + $title = ''; $title .= $this->get_pagetitle(); - $content = Q($title); + $content = html::quote($title); } // exec plugin hooks for this template object @@ -802,7 +793,7 @@ class rcube_template extends rcube_html_page // return code for a specified eval expression case 'exp': $value = $this->parse_expression($attrib['expression']); - return eval("return Q($value);"); + return eval("return html::quote($value);"); // return variable case 'var': @@ -815,13 +806,13 @@ class rcube_template extends rcube_html_page $value = $this->env[$name]; break; case 'config': - $value = $this->config[$name]; + $value = $this->config->get($name); if (is_array($value) && $value[$_SESSION['storage_host']]) { $value = $value[$_SESSION['storage_host']]; } break; case 'request': - $value = get_input_value($name, RCUBE_INPUT_GPC); + $value = rcube_ui::get_input_value($name, rcube_ui::INPUT_GPC); break; case 'session': $value = $_SESSION[$name]; @@ -838,19 +829,20 @@ class rcube_template extends rcube_html_page $value = implode(', ', $value); } - return Q($value); + return html::quote($value); break; } return ''; } + /** * Include a specific file and return it's contents * * @param string File path * @return string Contents of the processed file */ - private function include_php($file) + protected function include_php($file) { ob_start(); include $file; @@ -860,6 +852,7 @@ class rcube_template extends rcube_html_page return $out; } + /** * Create and register a button * @@ -901,13 +894,13 @@ class rcube_template extends rcube_html_page } // get localized text for labels and titles if ($attrib['title']) { - $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain'])); + $attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain'])); } if ($attrib['label']) { - $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain'])); + $attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain'])); } if ($attrib['alt']) { - $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain'])); + $attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain'])); } // set title to alt attribute for IE browsers @@ -935,14 +928,14 @@ class rcube_template extends rcube_html_page // make valid href to specific buttons if (in_array($attrib['command'], rcmail::$main_tasks)) { - $attrib['href'] = rcmail_url(null, null, $attrib['command']); + $attrib['href'] = $this->app->url(array('task' => $attrib['command'])); $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", JS_OBJECT_NAME, $attrib['command']); } else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) { - $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']); + $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task'])); } else if (in_array($attrib['command'], $a_static_commands)) { - $attrib['href'] = rcmail_url($attrib['command']); + $attrib['href'] = $this->app->url(array('action' => $attrib['command'])); } else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) { $attrib['href'] = $this->env['permaurl']; @@ -969,7 +962,7 @@ class rcube_template extends rcube_html_page $out = ''; // generate image tag - if ($attrib['type']=='image') { + if ($attrib['type'] == 'image') { $attrib_str = html::attrib_string( $attrib, array( @@ -983,13 +976,13 @@ class rcube_template extends rcube_html_page } $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target'); } - else if ($attrib['type']=='link') { + else if ($attrib['type'] == 'link') { $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']); $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target'); if ($attrib['innerclass']) $btn_content = html::span($attrib['innerclass'], $btn_content); } - else if ($attrib['type']=='input') { + else if ($attrib['type'] == 'input') { $attrib['type'] = 'button'; if ($attrib['label']) { @@ -1012,6 +1005,244 @@ class rcube_template extends rcube_html_page } + /** + * Link an external script file + * + * @param string File URL + * @param string Target position [head|foot] + */ + public function include_script($file, $position='head') + { + static $sa_files = array(); + + if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { + $file = $this->scripts_path . $file; + if ($fs = @filemtime($file)) { + $file .= '?s=' . $fs; + } + } + + if (in_array($file, $sa_files)) { + return; + } + + $sa_files[] = $file; + + if (!is_array($this->script_files[$position])) { + $this->script_files[$position] = array(); + } + + $this->script_files[$position][] = $file; + } + + + /** + * Add inline javascript code + * + * @param string JS code snippet + * @param string Target position [head|head_top|foot] + */ + public function add_script($script, $position='head') + { + if (!isset($this->scripts[$position])) { + $this->scripts[$position] = "\n" . rtrim($script); + } + else { + $this->scripts[$position] .= "\n" . rtrim($script); + } + } + + + /** + * Link an external css file + * + * @param string File URL + */ + public function include_css($file) + { + $this->css_files[] = $file; + } + + + /** + * Add HTML code to the page header + * + * @param string $str HTML code + */ + public function add_header($str) + { + $this->header .= "\n" . $str; + } + + + /** + * Add HTML code to the page footer + * To be added right befor </body> + * + * @param string $str HTML code + */ + public function add_footer($str) + { + $this->footer .= "\n" . $str; + } + + + /** + * Process template and write to stdOut + * + * @param string HTML template + * @param string Base for absolute paths + */ + public function _write($templ = '', $base_path = '') + { + $output = empty($templ) ? $this->default_template : trim($templ); + + // set default page title + if (empty($this->pagetitle)) { + $this->pagetitle = 'Roundcube Mail'; + } + + // replace specialchars in content + $page_title = html::quote($this->pagetitle); + $page_header = ''; + $page_footer = ''; + + // include meta tag with charset + if (!empty($this->charset)) { + if (!headers_sent()) { + header('Content-Type: text/html; charset=' . $this->charset); + } + $page_header = '<meta http-equiv="content-type"'; + $page_header.= ' content="text/html; charset='; + $page_header.= $this->charset . '" />'."\n"; + } + + // definition of the code to be placed in the document header and footer + if (is_array($this->script_files['head'])) { + foreach ($this->script_files['head'] as $file) { + $page_header .= html::script($file); + } + } + + $head_script = $this->scripts['head_top'] . $this->scripts['head']; + if (!empty($head_script)) { + $page_header .= html::script(array(), $head_script); + } + + if (!empty($this->header)) { + $page_header .= $this->header; + } + + // put docready commands into page footer + if (!empty($this->scripts['docready'])) { + $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot'); + } + + if (is_array($this->script_files['foot'])) { + foreach ($this->script_files['foot'] as $file) { + $page_footer .= html::script($file); + } + } + + if (!empty($this->footer)) { + $page_footer .= $this->footer . "\n"; + } + + if (!empty($this->scripts['foot'])) { + $page_footer .= html::script(array(), $this->scripts['foot']); + } + + // find page header + if ($hpos = stripos($output, '</head>')) { + $page_header .= "\n"; + } + else { + if (!is_numeric($hpos)) { + $hpos = stripos($output, '<body'); + } + if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) { + while ($output[$hpos] != '>') { + $hpos++; + } + $hpos++; + } + $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\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, '</body>')) || ($fpos = strripos($output, '</html>'))) { + $output = substr_replace($output, $page_footer."\n", $fpos, 0); + } + else { + $output .= "\n".$page_footer; + } + + // add css files in head, before scripts, for speed up with parallel downloads + if (!empty($this->css_files) && + (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>'))) + ) { + $css = ''; + foreach ($this->css_files as $file) { + $css .= html::tag('link', array('rel' => 'stylesheet', + 'type' => 'text/css', 'href' => $file, 'nl' => true)); + } + $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); + + // trigger hook with final HTML content to be sent + $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output)); + if (!$hook['abort']) { + if ($this->charset != RCMAIL_CHARSET) { + echo rcube_charset::convert($hook['content'], RCMAIL_CHARSET, $this->charset); + } + else { + echo $hook['content']; + } + } + } + + + /** + * 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]; + } + + /* ************* common functions delivering gui objects ************** */ @@ -1108,15 +1339,15 @@ class rcube_template extends rcube_html_page * @param array Named parameters * @return string HTML code for the gui object */ - private function login_form($attrib) + protected function login_form($attrib) { - $default_host = $this->config['default_host']; - $autocomplete = (int) $this->config['login_autocomplete']; + $default_host = $this->config->get('default_host'); + $autocomplete = (int) $this->config->get('login_autocomplete'); $_SESSION['temp'] = true; // save original url - $url = get_input_value('_url', RCUBE_INPUT_POST); + $url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST); if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING'])) $url = $_SERVER['QUERY_STRING']; @@ -1165,16 +1396,16 @@ class rcube_template extends rcube_html_page // create HTML table with two cols $table = new html_table(array('cols' => 2)); - $table->add('title', html::label('rcmloginuser', Q(rcube_label('username')))); - $table->add('input', $input_user->show(get_input_value('_user', RCUBE_INPUT_GPC))); + $table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username')))); + $table->add('input', $input_user->show(rcube_ui::get_input_value('_user', rcube_ui::INPUT_GPC))); - $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password')))); + $table->add('title', html::label('rcmloginpwd', html::quote($this->app->gettext('password')))); $table->add('input', $input_pass->show()); // add host selection row if (is_object($input_host) && !$hide_host) { - $table->add('title', html::label('rcmloginhost', Q(rcube_label('server')))); - $table->add('input', $input_host->show(get_input_value('_host', RCUBE_INPUT_GPC))); + $table->add('title', html::label('rcmloginhost', html::quote($this->app->gettext('server')))); + $table->add('input', $input_host->show(rcube_ui::get_input_value('_host', rcube_ui::INPUT_GPC))); } $out = $input_task->show(); @@ -1204,7 +1435,7 @@ class rcube_template extends rcube_html_page * @param array Named parameters * @return void */ - private function preloader($attrib) + protected function preloader($attrib) { $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY); $images = array_map(array($this, 'abs_url'), $images); @@ -1212,7 +1443,7 @@ class rcube_template extends rcube_html_page if (empty($images) || $this->app->task == 'logout') return; - $this->add_script('var images = ' . json_serialize($images) .'; + $this->add_script('var images = ' . self::json_serialize($images) .'; for (var i=0; i<images.length; i++) { img = new Image(); img.src = images[i]; @@ -1227,7 +1458,7 @@ class rcube_template extends rcube_html_page * @param array Named parameters * @return string HTML code for the gui object */ - private function search_form($attrib) + protected function search_form($attrib) { // add some labels to client $this->add_label('searching'); @@ -1265,14 +1496,15 @@ class rcube_template extends rcube_html_page * @param array Named tag parameters * @return string HTML code for the gui object */ - private function message_container($attrib) + protected function message_container($attrib) { if (isset($attrib['id']) === false) { $attrib['id'] = 'rcmMessageContainer'; } $this->add_gui_object('message', $attrib['id']); - return html::div($attrib, ""); + + return html::div($attrib, ''); } @@ -1282,7 +1514,7 @@ class rcube_template extends rcube_html_page * @param array Named parameters for the select tag * @return string HTML code for the gui object */ - function charset_selector($attrib) + public function charset_selector($attrib) { // pass the following attributes to the form class $field_attrib = array('name' => '_charset'); @@ -1293,39 +1525,39 @@ class rcube_template extends rcube_html_page } $charsets = array( - '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-8859-2 ('.rcube_label('easterneuropean').')', - 'ISO-8859-4' => 'ISO-8859-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').')', + 'UTF-8' => 'UTF-8 ('.$this->app->gettext('unicode').')', + 'US-ASCII' => 'ASCII ('.$this->app->gettext('english').')', + 'ISO-8859-1' => 'ISO-8859-1 ('.$this->app->gettext('westerneuropean').')', + 'ISO-8859-2' => 'ISO-8859-2 ('.$this->app->gettext('easterneuropean').')', + 'ISO-8859-4' => 'ISO-8859-4 ('.$this->app->gettext('baltic').')', + 'ISO-8859-5' => 'ISO-8859-5 ('.$this->app->gettext('cyrillic').')', + 'ISO-8859-6' => 'ISO-8859-6 ('.$this->app->gettext('arabic').')', + 'ISO-8859-7' => 'ISO-8859-7 ('.$this->app->gettext('greek').')', + 'ISO-8859-8' => 'ISO-8859-8 ('.$this->app->gettext('hebrew').')', + 'ISO-8859-9' => 'ISO-8859-9 ('.$this->app->gettext('turkish').')', + 'ISO-8859-10' => 'ISO-8859-10 ('.$this->app->gettext('nordic').')', + 'ISO-8859-11' => 'ISO-8859-11 ('.$this->app->gettext('thai').')', + 'ISO-8859-13' => 'ISO-8859-13 ('.$this->app->gettext('baltic').')', + 'ISO-8859-14' => 'ISO-8859-14 ('.$this->app->gettext('celtic').')', + 'ISO-8859-15' => 'ISO-8859-15 ('.$this->app->gettext('westerneuropean').')', + 'ISO-8859-16' => 'ISO-8859-16 ('.$this->app->gettext('southeasterneuropean').')', + 'WINDOWS-1250' => 'Windows-1250 ('.$this->app->gettext('easterneuropean').')', + 'WINDOWS-1251' => 'Windows-1251 ('.$this->app->gettext('cyrillic').')', + 'WINDOWS-1252' => 'Windows-1252 ('.$this->app->gettext('westerneuropean').')', + 'WINDOWS-1253' => 'Windows-1253 ('.$this->app->gettext('greek').')', + 'WINDOWS-1254' => 'Windows-1254 ('.$this->app->gettext('turkish').')', + 'WINDOWS-1255' => 'Windows-1255 ('.$this->app->gettext('hebrew').')', + 'WINDOWS-1256' => 'Windows-1256 ('.$this->app->gettext('arabic').')', + 'WINDOWS-1257' => 'Windows-1257 ('.$this->app->gettext('baltic').')', + 'WINDOWS-1258' => 'Windows-1258 ('.$this->app->gettext('vietnamese').')', + 'ISO-2022-JP' => 'ISO-2022-JP ('.$this->app->gettext('japanese').')', + 'ISO-2022-KR' => 'ISO-2022-KR ('.$this->app->gettext('korean').')', + 'ISO-2022-CN' => 'ISO-2022-CN ('.$this->app->gettext('chinese').')', + 'EUC-JP' => 'EUC-JP ('.$this->app->gettext('japanese').')', + 'EUC-KR' => 'EUC-KR ('.$this->app->gettext('korean').')', + 'EUC-CN' => 'EUC-CN ('.$this->app->gettext('chinese').')', + 'BIG5' => 'BIG5 ('.$this->app->gettext('chinese').')', + 'GB2312' => 'GB2312 ('.$this->app->gettext('chinese').')', ); if (!empty($_POST['_charset'])) @@ -1348,7 +1580,7 @@ class rcube_template extends rcube_html_page /** * Include content from config/about.<LANG>.html if available */ - private function about_content($attrib) + protected function about_content($attrib) { $content = ''; $filenames = array( @@ -1369,6 +1601,4 @@ class rcube_template extends rcube_html_page return $content; } -} // end class rcube_template - - +} diff --git a/program/include/rcube_json_output.php b/program/include/rcube_output_json.php index f062d4b71..73cf76795 100644 --- a/program/include/rcube_json_output.php +++ b/program/include/rcube_output_json.php @@ -2,21 +2,20 @@ /* +-----------------------------------------------------------------------+ - | program/include/rcube_json_output.php | + | program/include/rcube_output_json.php | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2010, The Roundcube Dev Team | + | Copyright (C) 2008-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | | See the README file for a full license statement. | | | | PURPOSE: | - | Class to handle HTML page output using a skin template. | - | Extends rcube_html_page class from rcube_shared.inc | - | | + | Class to handle JSON (AJAX) output | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | +-----------------------------------------------------------------------+ $Id$ @@ -29,37 +28,18 @@ * * @package View */ -class rcube_json_output +class rcube_output_json extends rcube_output { - /** - * Stores configuration object. - * - * @var rcube_config - */ - private $config; - private $charset = RCMAIL_CHARSET; - private $texts = array(); - private $commands = array(); - private $callbacks = array(); - private $message = null; - - public $browser; - public $env = array(); + protected $texts = array(); + protected $commands = array(); + protected $callbacks = array(); + protected $message = null; + public $type = 'js'; public $ajax_call = true; /** - * Constructor - */ - public function __construct($task=null) - { - $this->config = rcmail::get_instance()->config; - $this->browser = new rcube_browser(); - } - - - /** * Set environment variable * * @param string $name Property name @@ -88,31 +68,10 @@ class rcube_json_output /** - * @ignore - */ - function set_charset($charset) - { - // ignore: $this->charset = $charset; - } - - - /** - * Get charset for output - * - * @return string Output charset - */ - function get_charset() - { - return $this->charset; - } - - - /** * Register a template object handler * * @param string $obj Object name * @param string $func Function name to call - * @return void */ public function add_handler($obj, $func) { @@ -124,7 +83,6 @@ class rcube_json_output * Register a list of template object handlers * * @param array $arr Hash array with object=>handler pairs - * @return void */ public function add_handlers($arr) { @@ -159,7 +117,7 @@ class rcube_json_output $args = $args[0]; foreach ($args as $name) { - $this->texts[$name] = rcube_label($name); + $this->texts[$name] = $this->app->gettext($name); } } @@ -177,10 +135,11 @@ class rcube_json_output public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0) { if ($override || !$this->message) { - if (rcube_label_exists($message)) { - if (!empty($vars)) - $vars = array_map('Q', $vars); - $msgtext = rcube_label(array('name' => $message, 'vars' => $vars)); + if ($this->app->text_exists($message)) { + if (!empty($vars)) { + $vars = array_map(array('rcube_ui', 'Q'), $vars); + } + $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars)); } else $msgtext = $message; @@ -196,7 +155,7 @@ class rcube_json_output */ public function reset() { - $this->env = array(); + parent::reset(); $this->texts = array(); $this->commands = array(); } @@ -228,6 +187,20 @@ class rcube_json_output /** + * Show error page and terminate script execution + * + * @param int $code Error code + * @param string $message Error message + */ + public function raise_error($code, $message) + { + $this->show_message("Application Error ($code): $message", 'error'); + $this->remote_response(); + exit; + } + + + /** * Send an AJAX response with executable JS code * * @param string $add Additional JS code @@ -235,13 +208,13 @@ class rcube_json_output * @return void * @deprecated */ - public function remote_response($add='') + protected function remote_response($add='') { static $s_header_sent = false; if (!$s_header_sent) { $s_header_sent = true; - send_nocacheing_headers(); + $this->nocacheing_headers(); header('Content-Type: text/plain; charset=' . $this->get_charset()); } @@ -251,7 +224,7 @@ class rcube_json_output $rcmail = rcmail::get_instance(); $response['action'] = $rcmail->action; - if ($unlock = get_input_value('_unlock', RCUBE_INPUT_GPC)) { + if ($unlock = rcube_ui::get_input_value('_unlock', rcube_ui::INPUT_GPC)) { $response['unlock'] = $unlock; } @@ -267,7 +240,7 @@ class rcube_json_output if (!empty($this->callbacks)) $response['callbacks'] = $this->callbacks; - echo json_serialize($response); + echo self::json_serialize($response); } @@ -276,14 +249,14 @@ class rcube_json_output * * @return string $out */ - private function get_js_commands() + protected function get_js_commands() { $out = ''; foreach ($this->commands as $i => $args) { $method = array_shift($args); foreach ($args as $i => $arg) { - $args[$i] = json_serialize($arg); + $args[$i] = self::json_serialize($arg); } $out .= sprintf( diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php index aeb05afa1..e20f7ce6a 100644 --- a/program/include/rcube_plugin.php +++ b/program/include/rcube_plugin.php @@ -110,9 +110,10 @@ abstract class rcube_plugin public function load_config($fname = 'config.inc.php') { $fpath = $this->home.'/'.$fname; - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); if (is_file($fpath) && !$rcmail->config->load_from_file($fpath)) { - raise_error(array('code' => 527, 'type' => 'php', + rcube::raise_error(array( + 'code' => 527, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to load config from $fpath"), true, false); return false; @@ -176,7 +177,7 @@ abstract class rcube_plugin foreach ($texts as $key => $value) $add[$domain.'.'.$key] = $value; - $rcmail = rcmail::get_instance(); + $rcmail = rcube::get_instance(); $rcmail->load_language($lang, $add); // add labels to client @@ -196,7 +197,7 @@ abstract class rcube_plugin */ public function gettext($p) { - return rcmail::get_instance()->gettext($p, $this->ID); + return rcube::get_instance()->gettext($p, $this->ID); } /** @@ -309,9 +310,10 @@ abstract class rcube_plugin */ public function local_skin_path() { - $skin_path = 'skins/'.$this->api->config->get('skin'); - if (!is_dir(realpath(slashify($this->home) . $skin_path))) - $skin_path = 'skins/default'; + $rcmail = rcube::get_instance(); + $skin_path = 'skins/' . $rcmail->config->get('skin'); + if (!is_dir(realpath(slashify($this->home) . $skin_path))) + $skin_path = 'skins/default'; return $skin_path; } diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php index be12f11b5..95fd3b66a 100644 --- a/program/include/rcube_plugin_api.php +++ b/program/include/rcube_plugin_api.php @@ -22,6 +22,11 @@ */ +// location where plugins are loade from +if (!defined('RCMAIL_PLUGINS_DIR')) + define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins/'); + + /** * The plugin loader and global API * @@ -33,8 +38,8 @@ class rcube_plugin_api public $dir; public $url = 'plugins/'; + public $task = ''; public $output; - public $config; public $handlers = array(); private $plugins = array(); @@ -43,7 +48,6 @@ class rcube_plugin_api private $actionmap = array(); private $objectsmap = array(); private $template_contents = array(); - private $required_plugins = array('filesystem_attachments', 'jqueryui'); private $active_hook = false; // Deprecated names of hooks, will be removed after 0.5-stable release @@ -99,29 +103,48 @@ class rcube_plugin_api */ private function __construct() { - $this->dir = INSTALL_PATH . $this->url; + $this->dir = slashify(RCMAIL_PLUGINS_DIR); } /** - * Load and init all enabled plugins + * Initialize plugin engine * * This has to be done after rcmail::load_gui() or rcmail::json_init() * was called because plugins need to have access to rcmail->output + * + * @param object rcube Instance of the rcube base class + * @param string Current application task (used for conditional plugin loading) */ - public function init() + public function init($app, $task = '') { - $rcmail = rcmail::get_instance(); - $this->output = $rcmail->output; - $this->config = $rcmail->config; + $this->task = $task; + $this->output = $app->output; + + // register an internal hook + $this->register_hook('template_container', array($this, 'template_container_hook')); - $plugins_enabled = (array)$rcmail->config->get('plugins', array()); + // maybe also register a shudown function which triggers shutdown functions of all plugin objects + } + + + /** + * Load and init all enabled plugins + * + * This has to be done after rcmail::load_gui() or rcmail::json_init() + * was called because plugins need to have access to rcmail->output + * + * @param array List of configured plugins to load + * @param array List of plugins required by the application + */ + public function load_plugins($plugins_enabled, $required_plugins = array()) + { foreach ($plugins_enabled as $plugin_name) { $this->load_plugin($plugin_name); } // check existance of all required core plugins - foreach ($this->required_plugins as $plugin_name) { + foreach ($required_plugins as $plugin_name) { $loaded = false; foreach ($this->plugins as $plugin) { if ($plugin instanceof $plugin_name) { @@ -136,19 +159,13 @@ class rcube_plugin_api // trigger fatal error if still not loaded if (!$loaded) { - raise_error(array('code' => 520, 'type' => 'php', + rcube::raise_error(array('code' => 520, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Requried plugin $plugin_name was not loaded"), true, true); } } - - // register an internal hook - $this->register_hook('template_container', array($this, 'template_container_hook')); - - // maybe also register a shudown function which triggers shutdown functions of all plugin objects } - /** * Load the specified plugin * @@ -159,8 +176,6 @@ class rcube_plugin_api { static $plugins_dir; - $rcmail = rcmail::get_instance(); - if (!$plugins_dir) { $dir = dir($this->dir); $plugins_dir = unslashify($dir->path); @@ -181,8 +196,8 @@ class rcube_plugin_api // check inheritance... if (is_subclass_of($plugin, 'rcube_plugin')) { // ... task, request type and framed mode - if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) - && (!$plugin->noajax || (is_object($rcmail->output) && is_a($rcmail->output, 'rcube_template'))) + if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task)) + && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html')) && (!$plugin->noframe || empty($_REQUEST['_framed'])) ) { $plugin->init(); @@ -192,13 +207,13 @@ class rcube_plugin_api } } else { - raise_error(array('code' => 520, 'type' => 'php', + rcube::raise_error(array('code' => 520, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "No plugin class $plugin_name found in $fn"), true, false); } } else { - raise_error(array('code' => 520, 'type' => 'php', + rcube::raise_error(array('code' => 520, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to load plugin file $fn"), true, false); } @@ -217,7 +232,7 @@ class rcube_plugin_api { if (is_callable($callback)) { if (isset($this->deprecated_hooks[$hook])) { - raise_error(array('code' => 522, 'type' => 'php', + rcube::raise_error(array('code' => 522, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false); $hook = $this->deprecated_hooks[$hook]; @@ -225,7 +240,7 @@ class rcube_plugin_api $this->handlers[$hook][] = $callback; } else - raise_error(array('code' => 521, 'type' => 'php', + rcube::raise_error(array('code' => 521, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Invalid callback function for $hook"), true, false); } @@ -297,7 +312,7 @@ class rcube_plugin_api $this->actionmap[$action] = $owner; } else { - raise_error(array('code' => 523, 'type' => 'php', + rcube::raise_error(array('code' => 523, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Cannot register action $action; already taken by another plugin"), true, false); } @@ -316,7 +331,7 @@ class rcube_plugin_api call_user_func($this->actions[$action]); } else { - raise_error(array('code' => 524, 'type' => 'php', + rcube::raise_error(array('code' => 524, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "No handler found for action $action"), true, true); } @@ -337,14 +352,14 @@ class rcube_plugin_api $name = 'plugin.'.$name; // can register handler only if it's not taken or registered by myself - if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) { + if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) { $this->output->add_handler($name, $callback); $this->objectsmap[$name] = $owner; } else { - raise_error(array('code' => 525, 'type' => 'php', + rcube::raise_error(array('code' => 525, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false); + 'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false); } } @@ -358,12 +373,12 @@ class rcube_plugin_api public function register_task($task, $owner) { if ($task != asciiwords($task)) { - raise_error(array('code' => 526, 'type' => 'php', + rcube::raise_error(array('code' => 526, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false); } else if (in_array($task, rcmail::$main_tasks)) { - raise_error(array('code' => 526, 'type' => 'php', + rcube::raise_error(array('code' => 526, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false); } @@ -408,7 +423,7 @@ class rcube_plugin_api */ public function include_script($fn) { - if ($this->output->type == 'html') { + if (is_object($this->output) && $this->output->type == 'html') { $src = $this->resource_url($fn); $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); } @@ -422,7 +437,7 @@ class rcube_plugin_api */ public function include_stylesheet($fn) { - if ($this->output->type == 'html') { + if (is_object($this->output) && $this->output->type == 'html') { $src = $this->resource_url($fn); $this->output->include_css($src); } diff --git a/program/include/rcube_session.php b/program/include/rcube_session.php index e6e636e18..da221c30e 100644 --- a/program/include/rcube_session.php +++ b/program/include/rcube_session.php @@ -78,7 +78,7 @@ class rcube_session array($this, 'gc')); } else { - raise_error(array('code' => 604, 'type' => 'db', + rcube::raise_error(array('code' => 604, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Failed to connect to memcached. Please check configuration"), true, true); @@ -129,7 +129,7 @@ class rcube_session public function db_read($key) { $sql_result = $this->db->query( - "SELECT vars, ip, changed FROM ".get_table_name('session') + "SELECT vars, ip, changed FROM ".$this->db->table_name('session') ." WHERE sess_id = ?", $key); if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { @@ -177,18 +177,18 @@ class rcube_session if ($newvars !== $oldvars) { $this->db->query( sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?", - get_table_name('session'), $now), + $this->db->table_name('session'), $now), base64_encode($newvars), $key); } else if ($ts - $this->changed > $this->lifetime / 2) { - $this->db->query("UPDATE ".get_table_name('session')." SET changed=$now WHERE sess_id=?", $key); + $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key); } } else { $this->db->query( sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ". "VALUES (?, ?, ?, %s, %s)", - get_table_name('session'), $now, $now), + $this->db->table_name('session'), $now, $now), $key, base64_encode($vars), (string)$this->ip); } @@ -228,7 +228,7 @@ class rcube_session public function db_destroy($key) { $this->db->query( - sprintf("DELETE FROM %s WHERE sess_id = ?", get_table_name('session')), + sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key); return true; @@ -246,7 +246,7 @@ class rcube_session // just delete all expired sessions $this->db->query( sprintf("DELETE FROM %s WHERE changed < %s", - get_table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); + $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); $this->gc(); @@ -322,8 +322,9 @@ class rcube_session */ public function gc() { - foreach ($this->gc_handlers as $fct) + foreach ($this->gc_handlers as $fct) { call_user_func($fct); + } } @@ -624,14 +625,14 @@ class rcube_session $auth_string = "$this->key,$this->secret,$timeslot"; return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string)); } - + /** * */ function log($line) { if ($this->logging) - write_log('session', $line); + rcmail::write_log('session', $line); } } diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 9340091bb..2e9444717 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -2,17 +2,17 @@ /* +-----------------------------------------------------------------------+ - | rcube_shared.inc | + | program/include/rcube_shared.inc | | | | This file is part of the Roundcube PHP suite | - | Copyright (C) 2005-2007, The Roundcube Dev Team | + | Copyright (C) 2005-2012, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | | See the README file for a full license statement. | | | | CONTENTS: | - | Shared functions and classes used in PHP projects | + | Shared functions used by Roundcube Framework | | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | @@ -31,213 +31,95 @@ /** - * Send HTTP headers to prevent caching this page - */ -function send_nocacheing_headers() -{ - global $OUTPUT; - - if (headers_sent()) - return; - - header("Expires: ".gmdate("D, d M Y H:i:s")." GMT"); - header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); - // Request browser to disable DNS prefetching (CVE-2010-0464) - header("X-DNS-Prefetch-Control: off"); - - // We need to set the following headers to make downloads work using IE in HTTPS mode. - if ($OUTPUT->browser->ie && rcube_https_check()) { - header('Pragma: private'); - header("Cache-Control: private, must-revalidate"); - } else { - header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0"); - header("Pragma: no-cache"); - } -} - - -/** - * Send header with expire date 30 days in future - * - * @param int Expiration time in seconds - */ -function send_future_expire_header($offset=2600000) -{ - if (headers_sent()) - return; - - header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+$offset)." GMT"); - header("Cache-Control: max-age=$offset"); - header("Pragma: "); -} - - -/** * Similar function as in_array() but case-insensitive * - * @param mixed Needle value - * @param array Array to search in + * @param string $needle Needle value + * @param array $heystack Array to search in + * * @return boolean True if found, False if not */ function in_array_nocase($needle, $haystack) { - $needle = mb_strtolower($needle); - foreach ($haystack as $value) - if ($needle===mb_strtolower($value)) - return true; + $needle = mb_strtolower($needle); + foreach ($haystack as $value) { + if ($needle === mb_strtolower($value)) { + return true; + } + } - return false; + return false; } /** - * Find out if the string content means TRUE or FALSE + * Find out if the string content means true or false * - * @param string Input value - * @return boolean Imagine what! + * @param string $str Input value + * + * @return boolean Boolean value */ function get_boolean($str) { - $str = strtolower($str); - if (in_array($str, array('false', '0', 'no', 'off', 'nein', ''), TRUE)) - return FALSE; - else - return TRUE; + $str = strtolower($str); + + return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true); } /** - * Parse a human readable string for a number of bytes + * Parse a human readable string for a number of bytes. + * + * @param string $str Input string * - * @param string Input string * @return float Number of bytes */ function parse_bytes($str) { - if (is_numeric($str)) - return floatval($str); - - if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) - { - $bytes = floatval($regs[1]); - switch (strtolower($regs[2])) - { - case 'g': - case 'gb': - $bytes *= 1073741824; - break; - case 'm': - case 'mb': - $bytes *= 1048576; - break; - case 'k': - case 'kb': - $bytes *= 1024; - break; + if (is_numeric($str)) { + return floatval($str); } - } - return floatval($bytes); -} - -/** - * Create a human readable string for a number of bytes - * - * @param int Number of bytes - * @return string Byte string - */ -function show_bytes($bytes) -{ - if ($bytes >= 1073741824) - { - $gb = $bytes/1073741824; - $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . rcube_label('GB'); - } - else if ($bytes >= 1048576) - { - $mb = $bytes/1048576; - $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . rcube_label('MB'); - } - else if ($bytes >= 1024) - $str = sprintf("%d ", round($bytes/1024)) . rcube_label('KB'); - else - $str = sprintf('%d ', $bytes) . rcube_label('B'); - - return $str; -} - -/** - * Wrapper function for wordwrap - */ -function rc_wordwrap($string, $width=75, $break="\n", $cut=false) -{ - $para = explode($break, $string); - $string = ''; - while (count($para)) { - $line = array_shift($para); - if ($line[0] == '>') { - $string .= $line.$break; - continue; - } - $list = explode(' ', $line); - $len = 0; - while (count($list)) { - $line = array_shift($list); - $l = mb_strlen($line); - $newlen = $len + $l + ($len ? 1 : 0); - - if ($newlen <= $width) { - $string .= ($len ? ' ' : '').$line; - $len += (1 + $l); - } else { - if ($l > $width) { - if ($cut) { - $start = 0; - while ($l) { - $str = mb_substr($line, $start, $width); - $strlen = mb_strlen($str); - $string .= ($len ? $break : '').$str; - $start += $strlen; - $l -= $strlen; - $len = $strlen; - } - } else { - $string .= ($len ? $break : '').$line; - if (count($list)) $string .= $break; - $len = 0; - } - } else { - $string .= $break.$line; - $len = $l; + if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) { + $bytes = floatval($regs[1]); + switch (strtolower($regs[2])) { + case 'g': + case 'gb': + $bytes *= 1073741824; + break; + case 'm': + case 'mb': + $bytes *= 1048576; + break; + case 'k': + case 'kb': + $bytes *= 1024; + break; } - } } - if (count($para)) $string .= $break; - } - return $string; + + return floatval($bytes); } + /** - * Read a specific HTTP request header + * Read a specific HTTP request header. * - * @access static * @param string $name Header name + * * @return mixed Header value or null if not available */ -function rc_request_header($name) +function rcube_request_header($name) { - if (function_exists('getallheaders')) - { - $hdrs = array_change_key_case(getallheaders(), CASE_UPPER); - $key = strtoupper($name); - } - else - { - $key = 'HTTP_' . strtoupper(strtr($name, '-', '_')); - $hdrs = array_change_key_case($_SERVER, CASE_UPPER); - } - - return $hdrs[$key]; + if (function_exists('getallheaders')) { + $hdrs = array_change_key_case(getallheaders(), CASE_UPPER); + $key = strtoupper($name); + } + else { + $key = 'HTTP_' . strtoupper(strtr($name, '-', '_')); + $hdrs = array_change_key_case($_SERVER, CASE_UPPER); + } + + return $hdrs[$key]; } @@ -263,219 +145,251 @@ function unslashify($str) * Delete all files within a folder * * @param string Path to directory + * * @return boolean True on success, False if directory was not found */ function clear_directory($dir_path) { - $dir = @opendir($dir_path); - if(!$dir) return FALSE; + $dir = @opendir($dir_path); + if (!$dir) { + return false; + } - while ($file = readdir($dir)) - if (strlen($file)>2) - unlink("$dir_path/$file"); + while ($file = readdir($dir)) { + if (strlen($file) > 2) { + unlink("$dir_path/$file"); + } + } - closedir($dir); - return TRUE; + closedir($dir); + + return true; } /** - * Create a unix timestamp with a specified offset from now + * Create a unix timestamp with a specified offset from now. + * + * @param string $offset_str String representation of the offset (e.g. 20min, 5h, 2days) + * @param int $factor Factor to multiply with the offset * - * @param string String representation of the offset (e.g. 20min, 5h, 2days) - * @param int Factor to multiply with the offset * @return int Unix timestamp */ function get_offset_time($offset_str, $factor=1) { - if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs)) - { - $amount = (int)$regs[1]; - $unit = strtolower($regs[2]); - } - else - { - $amount = (int)$offset_str; - $unit = 's'; - } - - $ts = mktime(); - switch ($unit) - { + if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs)) { + $amount = (int)$regs[1]; + $unit = strtolower($regs[2]); + } + else { + $amount = (int)$offset_str; + $unit = 's'; + } + + $ts = mktime(); + switch ($unit) { case 'w': - $amount *= 7; + $amount *= 7; case 'd': - $amount *= 24; + $amount *= 24; case 'h': - $amount *= 60; + $amount *= 60; case 'm': - $amount *= 60; + $amount *= 60; case 's': - $ts += $amount * $factor; - } + $ts += $amount * $factor; + } - return $ts; + return $ts; } /** - * Truncate string if it is longer than the allowed length - * Replace the middle or the ending part of a string with a placeholder + * Truncate string if it is longer than the allowed length. + * Replace the middle or the ending part of a string with a placeholder. + * + * @param string $str Input string + * @param int $maxlength Max. length + * @param string $placeholder Replace removed chars with this + * @param bool $ending Set to True if string should be truncated from the end * - * @param string Input string - * @param int Max. length - * @param string Replace removed chars with this - * @param bool Set to True if string should be truncated from the end * @return string Abbreviated string */ -function abbreviate_string($str, $maxlength, $place_holder='...', $ending=false) +function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false) { - $length = mb_strlen($str); + $length = mb_strlen($str); - if ($length > $maxlength) - { - if ($ending) - return mb_substr($str, 0, $maxlength) . $place_holder; + if ($length > $maxlength) { + if ($ending) { + return mb_substr($str, 0, $maxlength) . $placeholder; + } + + $placeholder_length = mb_strlen($placeholder); + $first_part_length = floor(($maxlength - $placeholder_length)/2); + $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length; - $place_holder_length = mb_strlen($place_holder); - $first_part_length = floor(($maxlength - $place_holder_length)/2); - $second_starting_location = $length - $maxlength + $first_part_length + $place_holder_length; - $str = mb_substr($str, 0, $first_part_length) . $place_holder . mb_substr($str, $second_starting_location); - } + $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location); + } - return $str; + return $str; } /** - * A method to guess the mime_type of an attachment. - * - * @param string $path Path to the file. - * @param string $name File name (with suffix) - * @param string $failover Mime type supplied for failover. - * @param string $is_stream Set to True if $path contains file body + * Explode quoted string * - * @return string - * @author Till Klampaeckel <till@php.net> - * @see http://de2.php.net/manual/en/ref.fileinfo.php - * @see http://de2.php.net/mime_content_type + * @param string Delimiter expression string for preg_match() + * @param string Input string */ -function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false) +function rcube_explode_quoted_string($delimiter, $string) { - $mime_type = null; - $mime_magic = rcmail::get_instance()->config->get('mime_magic'); - $mime_ext = @include(RCMAIL_CONFIG_DIR . '/mimetypes.php'); - - // use file name suffix with hard-coded mime-type map - if (is_array($mime_ext) && $name) { - if ($suffix = substr($name, strrpos($name, '.')+1)) { - $mime_type = $mime_ext[strtolower($suffix)]; + $result = array(); + $strlen = strlen($string); + + for ($q=$p=$i=0; $i < $strlen; $i++) { + if ($string[$i] == "\"" && $string[$i-1] != "\\") { + $q = $q ? false : true; + } + else if (!$q && preg_match("/$delimiter/", $string[$i])) { + $result[] = substr($string, $p, $i - $p); + $p = $i + 1; } } - // try fileinfo extension if available - if (!$mime_type && function_exists('finfo_open')) { - if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) { - if ($is_stream) - $mime_type = finfo_buffer($finfo, $path); - else - $mime_type = finfo_file($finfo, $path); - finfo_close($finfo); + $result[] = substr($string, $p); + + return $result; +} + + +/** + * Get all keys from array (recursive). + * + * @param array $array Input array + * + * @return array List of array keys + */ +function array_keys_recursive($array) +{ + $keys = array(); + + if (!empty($array)) { + foreach ($array as $key => $child) { + $keys[] = $key; + foreach (array_keys_recursive($child) as $val) { + $keys[] = $val; + } } } - // try PHP's mime_content_type - if (!$mime_type && !$is_stream && function_exists('mime_content_type')) { - $mime_type = @mime_content_type($path); - } + return $keys; +} - // fall back to user-submitted string - if (!$mime_type) { - $mime_type = $failover; - } - else { - // Sometimes (PHP-5.3?) content-type contains charset definition, - // Remove it (#1487122) also "charset=binary" is useless - $mime_type = array_shift(preg_split('/[; ]/', $mime_type)); - } - return $mime_type; +/** + * Remove all non-ascii and non-word chars except ., -, _ + */ +function asciiwords($str, $css_id = false, $replace_with = '') +{ + $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : ''); + return preg_replace("/[^$allowed]/i", $replace_with, $str); } /** - * Detect image type of the given binary data by checking magic numbers + * Remove single and double quotes from given string * - * @param string Binary file content - * @return string Detected mime-type or jpeg as fallback + * @param string Input value + * + * @return string Dequoted string */ -function rc_image_content_type($data) +function strip_quotes($str) { - $type = 'jpeg'; - if (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png'; - else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif'; - else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico'; -// else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg'; + return str_replace(array("'", '"'), '', $str); +} - return 'image/' . $type; + +/** + * Remove new lines characters from given string + * + * @param string $str Input value + * + * @return string Stripped string + */ +function strip_newlines($str) +{ + return preg_replace('/[\r\n]/', '', $str); } /** - * Explode quoted string - * - * @param string Delimiter expression string for preg_match() - * @param string Input string + * Improved equivalent to strtotime() + * + * @param string $date Date string + * + * @return int Unix timestamp */ -function rcube_explode_quoted_string($delimiter, $string) +function rcube_strtotime($date) { - $result = array(); - $strlen = strlen($string); - - for ($q=$p=$i=0; $i < $strlen; $i++) { - if ($string[$i] == "\"" && $string[$i-1] != "\\") { - $q = $q ? false : true; - } - else if (!$q && preg_match("/$delimiter/", $string[$i])) { - $result[] = substr($string, $p, $i - $p); - $p = $i + 1; + // check for MS Outlook vCard date format YYYYMMDD + if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) { + return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1])); + } + else if (is_numeric($date)) { + return $date; + } + + // support non-standard "GMTXXXX" literal + $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); + + // if date parsing fails, we have a date in non-rfc format. + // remove token from the end and try again + while ((($ts = @strtotime($date)) === false) || ($ts < 0)) { + $d = explode(' ', $date); + array_pop($d); + if (!$d) { + break; + } + $date = implode(' ', $d); } - } - $result[] = substr($string, $p); - return $result; + return $ts; } /** - * Get all keys from array (recursive) - * - * @param array Input array - * @return array + * Compose a valid representation of name and e-mail address + * + * @param string $email E-mail address + * @param string $name Person name + * + * @return string Formatted string */ -function array_keys_recursive($array) +function format_email_recipient($email, $name = '') { - $keys = array(); + $email = trim($email); + + if ($name && $name != $email) { + // Special chars as defined by RFC 822 need to in quoted string (or escaped). + if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) { + $name = '"'.addcslashes($name, '"').'"'; + } - if (!empty($array)) - foreach ($array as $key => $child) { - $keys[] = $key; - foreach (array_keys_recursive($child) as $val) - $keys[] = $val; + return "$name <$email>"; } - return $keys; + + return $email; } /** * mbstring replacement functions */ - if (!extension_loaded('mbstring')) { function mb_strlen($str) { - return strlen($str); + return strlen($str); } function mb_strtolower($str) @@ -552,3 +466,89 @@ if (!function_exists('idn_to_ascii')) } } + +/* + * Idn_to_ascii wrapper. + * Intl/Idn modules version of this function doesn't work with e-mail address + */ +function rcube_idn_to_ascii($str) +{ + return rcube_idn_convert($str, true); +} + +/* + * Idn_to_ascii wrapper. + * Intl/Idn modules version of this function doesn't work with e-mail address + */ +function rcube_idn_to_utf8($str) +{ + return rcube_idn_convert($str, false); +} + +function rcube_idn_convert($input, $is_utf=false) +{ + if ($at = strpos($input, '@')) { + $user = substr($input, 0, $at); + $domain = substr($input, $at+1); + } + else { + $domain = $input; + } + + $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain); + + if ($domain === false) { + return ''; + } + + return $at ? $user . '@' . $domain : $domain; +} + + +/** + * Use PHP5 autoload for dynamic class loading + * + * @todo Make Zend, PEAR etc play with this + * @todo Make our classes conform to a more straight forward CS. + */ +function rcube_autoload($classname) +{ + $filename = preg_replace( + array( + '/MDB2_(.+)/', + '/Mail_(.+)/', + '/Net_(.+)/', + '/Auth_(.+)/', + '/^html_.+/', + '/^utf8$/', + ), + array( + 'MDB2/\\1', + 'Mail/\\1', + 'Net/\\1', + 'Auth/\\1', + 'html', + 'utf8.class', + ), + $classname + ); + + if ($fp = @fopen("$filename.php", 'r', true)) { + fclose($fp); + include_once("$filename.php"); + return true; + } + + return false; +} + +/** + * Local callback function for PEAR errors + */ +function rcube_pear_error($err) +{ + error_log(sprintf("%s (%s): %s", + $err->getMessage(), + $err->getCode(), + $err->getUserinfo()), 0); +} diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php index 854505d79..0923e3be0 100644 --- a/program/include/rcube_smtp.php +++ b/program/include/rcube_smtp.php @@ -52,7 +52,7 @@ class rcube_smtp */ public function connect($host=null, $port=null, $user=null, $pass=null) { - $RCMAIL = rcmail::get_instance(); + $RCMAIL = rcube::get_instance(); // disconnect/destroy $this->conn $this->disconnect(); @@ -74,7 +74,7 @@ class rcube_smtp 'smtp_auth_callbacks' => array(), )); - $smtp_host = rcube_parse_host($CONFIG['smtp_server']); + $smtp_host = rcmail::parse_host($CONFIG['smtp_server']); // when called from Installer it's possible to have empty $smtp_host here if (!$smtp_host) $smtp_host = 'localhost'; $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25; @@ -338,7 +338,7 @@ class rcube_smtp */ public function debug_handler(&$smtp, $message) { - write_log('smtp', preg_replace('/\r\n$/', '', $message)); + rcmail::write_log('smtp', preg_replace('/\r\n$/', '', $message)); } diff --git a/program/include/rcube_spellchecker.php b/program/include/rcube_spellchecker.php index a6f391346..8dfc3ead1 100644 --- a/program/include/rcube_spellchecker.php +++ b/program/include/rcube_spellchecker.php @@ -61,7 +61,7 @@ class rcube_spellchecker $this->lang = $lang ? $lang : 'en'; if ($this->engine == 'pspell' && !extension_loaded('pspell')) { - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Pspell extension not available"), true, true); @@ -535,7 +535,7 @@ class rcube_spellchecker private function update_dict() { if (strcasecmp($this->options['dictionary'], 'shared') != 0) { - $userid = (int) $this->rc->user->ID; + $userid = $this->rc->get_user_id(); } $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array( @@ -548,24 +548,24 @@ class rcube_spellchecker if ($this->have_dict) { if (!empty($this->dict)) { $this->rc->db->query( - "UPDATE ".get_table_name('dictionary') + "UPDATE ".$this->rc->db->table_name('dictionary') ." SET data = ?" - ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") + ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", implode(' ', $plugin['dictionary']), $plugin['language']); } // don't store empty dict else { $this->rc->db->query( - "DELETE FROM " . get_table_name('dictionary') - ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") + "DELETE FROM " . $this->rc->db->table_name('dictionary') + ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", $plugin['language']); } } else if (!empty($this->dict)) { $this->rc->db->query( - "INSERT INTO " .get_table_name('dictionary') + "INSERT INTO " .$this->rc->db->table_name('dictionary') ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)", $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary'])); } @@ -582,7 +582,7 @@ class rcube_spellchecker } if (strcasecmp($this->options['dictionary'], 'shared') != 0) { - $userid = (int) $this->rc->user->ID; + $userid = $this->rc->get_user_id(); } $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array( @@ -591,8 +591,8 @@ class rcube_spellchecker if (empty($plugin['abort'])) { $dict = array(); $this->rc->db->query( - "SELECT data FROM ".get_table_name('dictionary') - ." WHERE user_id ". ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL") + "SELECT data FROM ".$this->rc->db->table_name('dictionary') + ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL") ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?", $plugin['language']); diff --git a/program/include/rcube_sqlite.inc b/program/include/rcube_sqlite.inc deleted file mode 100644 index 3b74b2665..000000000 --- a/program/include/rcube_sqlite.inc +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -/* - +-----------------------------------------------------------------------+ - | program/include/rcube_sqlite.inc | - | | - | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2010, The Roundcube Dev Team | - | | - | Licensed under the GNU General Public License version 3 or | - | any later version with exceptions for skins & plugins. | - | See the README file for a full license statement. | - | | - | PURPOSE: | - | Provide callback functions for sqlite that will emulate | - | sone MySQL functions | - | | - +-----------------------------------------------------------------------+ - | Author: Thomas Bruederli <roundcube@gmail.com> | - +-----------------------------------------------------------------------+ - - $Id$ - -*/ - -/** - * Callback functions for sqlite database interface - * - * @package Database - */ - - -function rcube_sqlite_from_unixtime($timestamp) -{ - $timestamp = trim($timestamp); - if (!preg_match('/^[0-9]+$/is', $timestamp)) - $ret = strtotime($timestamp); - else - $ret = $timestamp; - - $ret = date('Y-m-d H:i:s', $ret); - rcube_sqlite_debug("FROM_UNIXTIME ($timestamp) = $ret"); - return $ret; -} - - -function rcube_sqlite_unix_timestamp($timestamp='') -{ - $timestamp = trim($timestamp); - if (!$timestamp) - $ret = time(); - else if (!preg_match('/^[0-9]+$/is', $timestamp)) - $ret = strtotime($timestamp); - else - $ret = $timestamp; - - rcube_sqlite_debug("UNIX_TIMESTAMP ($timestamp) = $ret"); - return $ret; -} - - -function rcube_sqlite_now() -{ - rcube_sqlite_debug("NOW() = ".date("Y-m-d H:i:s")); - return date("Y-m-d H:i:s"); -} - - -function rcube_sqlite_md5($str) -{ - return md5($str); -} - - -function rcube_sqlite_debug($str) -{ - //console($str); -} - diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php index 8123e9cee..e80ee6abc 100644 --- a/program/include/rcube_storage.php +++ b/program/include/rcube_storage.php @@ -434,7 +434,7 @@ abstract class rcube_storage * @param int $uid Message UID to fetch * @param string $folder Folder to read from * - * @return object rcube_mail_header Message data + * @return object rcube_message_header Message data */ abstract function get_message($uid, $folder = null); @@ -446,7 +446,7 @@ abstract class rcube_storage * @param string $folder Folder to read from * @param bool $force True to skip cache * - * @return rcube_mail_header Message headers + * @return rcube_message_header Message headers */ abstract function get_message_headers($uid, $folder = null, $force = false); @@ -477,7 +477,7 @@ abstract class rcube_storage public function get_body($uid, $part = 1) { $headers = $this->get_message_headers($uid); - return rcube_charset_convert($this->get_message_part($uid, $part, null), + return rcube_charset::convert($this->get_message_part($uid, $part, null), $headers->charset ? $headers->charset : $this->default_charset); } @@ -970,6 +970,7 @@ abstract class rcube_storage */ abstract function clear_cache($key = null, $prefix_mode = false); + /** * Returns cached value * @@ -979,93 +980,10 @@ abstract class rcube_storage */ abstract function get_cache($key); + /** * Delete outdated cache entries */ abstract function expunge_cache(); } // end class rcube_storage - - -/** - * Class representing a message part - * - * @package Mail - */ -class rcube_message_part -{ - var $mime_id = ''; - var $ctype_primary = 'text'; - var $ctype_secondary = 'plain'; - var $mimetype = 'text/plain'; - var $disposition = ''; - var $filename = ''; - var $encoding = '8bit'; - var $charset = ''; - var $size = 0; - var $headers = array(); - var $d_parameters = array(); - var $ctype_parameters = array(); - - function __clone() - { - if (isset($this->parts)) { - foreach ($this->parts as $idx => $part) { - if (is_object($part)) { - $this->parts[$idx] = clone $part; - } - } - } - } -} - - -/** - * Class for sorting an array of rcube_mail_header objects in a predetermined order. - * - * @package Mail - * @author Eric Stadtherr - */ -class rcube_header_sorter -{ - private $uids = array(); - - - /** - * Set the predetermined sort order. - * - * @param array $index Numerically indexed array of IMAP UIDs - */ - function set_index($index) - { - $index = array_flip($index); - - $this->uids = $index; - } - - /** - * Sort the array of header objects - * - * @param array $headers Array of rcube_mail_header objects indexed by UID - */ - function sort_headers(&$headers) - { - uksort($headers, array($this, "compare_uids")); - } - - /** - * Sort method called by uksort() - * - * @param int $a Array key (UID) - * @param int $b Array key (UID) - */ - function compare_uids($a, $b) - { - // then find each sequence number in my ordered list - $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1; - $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1; - - // return the relative position as the comparison value - return $posa - $posb; - } -} diff --git a/program/include/rcube_string_replacer.php b/program/include/rcube_string_replacer.php index b3d29eb97..320f89af7 100644 --- a/program/include/rcube_string_replacer.php +++ b/program/include/rcube_string_replacer.php @@ -98,7 +98,7 @@ class rcube_string_replacer $i = $this->add($prefix . html::a(array( 'href' => $url_prefix . $url, 'target' => '_blank' - ), Q($url)) . $suffix); + ), rcube_ui::Q($url)) . $suffix); } // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes. @@ -118,8 +118,8 @@ class rcube_string_replacer $i = $this->add(html::a(array( 'href' => 'mailto:' . $href, - 'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".JQ($href)."',this)", - ), Q($href)) . $suffix); + 'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".rcube_ui::JQ($href)."',this)", + ), rcube_ui::Q($href)) . $suffix); return $i >= 0 ? $this->get_replacement($i) : ''; } diff --git a/program/include/rcube_ui.php b/program/include/rcube_ui.php new file mode 100644 index 000000000..262571047 --- /dev/null +++ b/program/include/rcube_ui.php @@ -0,0 +1,1468 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_ui.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2011-2012, Kolab Systems AG | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Provide basic functions for the webmail user interface | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +/** + * Roundcube Webmail functions for user interface + * + * @package Core + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + */ +class rcube_ui +{ + // define constants for input reading + const INPUT_GET = 0x0101; + const INPUT_POST = 0x0102; + const INPUT_GPC = 0x0103; + + + /** + * Get localized text in the desired language + * It's a global wrapper for rcube::gettext() + * + * @param mixed $p Named parameters array or label name + * @param string $domain Domain to search in (e.g. plugin name) + * + * @return string Localized text + * @see rcube::gettext() + */ + public static function label($p, $domain = null) + { + return rcube::get_instance()->gettext($p, $domain); + } + + + /** + * Global wrapper of rcube::text_exists() + * to check whether a text label is defined + * + * @see rcube::text_exists() + */ + public static function label_exists($name, $domain = null, &$ref_domain = null) + { + return rcube::get_instance()->text_exists($name, $domain, $ref_domain); + } + + + /** + * Compose an URL for a specific action + * + * @param string Request action + * @param array More URL parameters + * @param string Request task (omit if the same) + * + * @return The application URL + */ + public static function url($action, $p = array(), $task = null) + { + return rcube::get_instance()->url((array)$p + array('_action' => $action, 'task' => $task)); + } + + + /** + * Replacing specials characters to a specific encoding type + * + * @param string Input string + * @param string Encoding type: text|html|xml|js|url + * @param string Replace mode for tags: show|replace|remove + * @param boolean Convert newlines + * + * @return string The quoted string + */ + public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true) + { + static $html_encode_arr = false; + static $js_rep_table = false; + static $xml_rep_table = false; + + // encode for HTML output + if ($enctype == 'html') { + if (!$html_encode_arr) { + $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS); + unset($html_encode_arr['?']); + } + + $encode_arr = $html_encode_arr; + + // don't replace quotes and html tags + if ($mode == 'show' || $mode == '') { + $ltpos = strpos($str, '<'); + if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) { + unset($encode_arr['"']); + unset($encode_arr['<']); + unset($encode_arr['>']); + unset($encode_arr['&']); + } + } + else if ($mode == 'remove') { + $str = strip_tags($str); + } + + $out = strtr($str, $encode_arr); + + // avoid douple quotation of & + $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out); + + return $newlines ? nl2br($out) : $out; + } + + // if the replace tables for XML and JS are not yet defined + if ($js_rep_table === false) { + $js_rep_table = $xml_rep_table = array(); + $xml_rep_table['&'] = '&'; + + // can be increased to support more charsets + for ($c=160; $c<256; $c++) { + $xml_rep_table[chr($c)] = "&#$c;"; + } + + $xml_rep_table['"'] = '"'; + $js_rep_table['"'] = '\\"'; + $js_rep_table["'"] = "\\'"; + $js_rep_table["\\"] = "\\\\"; + // Unicode line and paragraph separators (#1486310) + $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '
'; + $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '
'; + } + + // encode for javascript use + if ($enctype == 'js') { + return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table)); + } + + // encode for plaintext + if ($enctype == 'text') { + return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str); + } + + if ($enctype == 'url') { + return rawurlencode($str); + } + + // encode for XML + if ($enctype == 'xml') { + return strtr($str, $xml_rep_table); + } + + // no encoding given -> return original string + return $str; + } + + + /** + * Quote a given string. + * Shortcut function for self::rep_specialchars_output() + * + * @return string HTML-quoted string + * @see self::rep_specialchars_output() + */ + public static function Q($str, $mode = 'strict', $newlines = true) + { + return self::rep_specialchars_output($str, 'html', $mode, $newlines); + } + + + /** + * Quote a given string for javascript output. + * Shortcut function for self::rep_specialchars_output() + * + * @return string JS-quoted string + * @see self::rep_specialchars_output() + */ + public static function JQ($str) + { + return self::rep_specialchars_output($str, 'js'); + } + + + /** + * Read input value and convert it for internal use + * Performs stripslashes() and charset conversion if necessary + * + * @param string Field name to read + * @param int Source to get value from (GPC) + * @param boolean Allow HTML tags in field value + * @param string Charset to convert into + * + * @return string Field value or NULL if not available + */ + public static function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) + { + $value = NULL; + + if ($source == self::INPUT_GET) { + if (isset($_GET[$fname])) { + $value = $_GET[$fname]; + } + } + else if ($source == self::INPUT_POST) { + if (isset($_POST[$fname])) { + $value = $_POST[$fname]; + } + } + else if ($source == self::INPUT_GPC) { + if (isset($_POST[$fname])) { + $value = $_POST[$fname]; + } + else if (isset($_GET[$fname])) { + $value = $_GET[$fname]; + } + else if (isset($_COOKIE[$fname])) { + $value = $_COOKIE[$fname]; + } + } + + return self::parse_input_value($value, $allow_html, $charset); + } + + /** + * Parse/validate input value. See self::get_input_value() + * Performs stripslashes() and charset conversion if necessary + * + * @param string Input value + * @param boolean Allow HTML tags in field value + * @param string Charset to convert into + * + * @return string Parsed value + */ + public static function parse_input_value($value, $allow_html=FALSE, $charset=NULL) + { + global $OUTPUT; + + if (empty($value)) { + return $value; + } + + if (is_array($value)) { + foreach ($value as $idx => $val) { + $value[$idx] = self::parse_input_value($val, $allow_html, $charset); + } + return $value; + } + + // strip single quotes if magic_quotes_sybase is enabled + if (ini_get('magic_quotes_sybase')) { + $value = str_replace("''", "'", $value); + } + // strip slashes if magic_quotes enabled + else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) { + $value = stripslashes($value); + } + + // remove HTML tags if not allowed + if (!$allow_html) { + $value = strip_tags($value); + } + + $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null; + + // remove invalid characters (#1488124) + if ($output_charset == 'UTF-8') { + $value = rcube_charset::clean($value); + } + + // convert to internal charset + if ($charset && $output_charset) { + $value = rcube_charset::convert($value, $output_charset, $charset); + } + + return $value; + } + + + /** + * Convert array of request parameters (prefixed with _) + * to a regular array with non-prefixed keys. + * + * @param int $mode Source to get value from (GPC) + * @param string $ignore PCRE expression to skip parameters by name + * + * @return array Hash array with all request parameters + */ + public static function request2param($mode = null, $ignore = 'task|action') + { + $out = array(); + $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST); + + foreach ($src as $key => $value) { + $fname = $key[0] == '_' ? substr($key, 1) : $key; + if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) { + $out[$fname] = self::get_input_value($key, $mode); + } + } + + return $out; + } + + + /** + * Convert the given string into a valid HTML identifier + * Same functionality as done in app.js with rcube_webmail.html_identifier() + */ + public static function html_identifier($str, $encode=false) + { + if ($encode) { + return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); + } + else { + return asciiwords($str, true, '_'); + } + } + + + /** + * Create a HTML table based on the given data + * + * @param array Named table attributes + * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set + * @param array List of cols to show + * @param string Name of the identifier col + * + * @return string HTML table code + */ + public static function table_output($attrib, $table_data, $a_show_cols, $id_col) + { + global $RCMAIL; + + $table = new html_table(/*array('cols' => count($a_show_cols))*/); + + // add table header + if (!$attrib['noheader']) { + foreach ($a_show_cols as $col) { + $table->add_header($col, self::Q(self::label($col))); + } + } + + if (!is_array($table_data)) { + $db = $RCMAIL->get_dbh(); + while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) { + $table->add_row(array('id' => 'rcmrow' . self::html_identifier($sql_arr[$id_col]))); + + // format each col + foreach ($a_show_cols as $col) { + $table->add($col, self::Q($sql_arr[$col])); + } + } + } + else { + foreach ($table_data as $row_data) { + $class = !empty($row_data['class']) ? $row_data['class'] : ''; + $rowid = 'rcmrow' . self::html_identifier($row_data[$id_col]); + + $table->add_row(array('id' => $rowid, 'class' => $class)); + + // format each col + foreach ($a_show_cols as $col) { + $table->add($col, self::Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); + } + } + } + + return $table->show($attrib); + } + + + /** + * Create an edit field for inclusion on a form + * + * @param string col field name + * @param string value field value + * @param array attrib HTML element attributes for field + * @param string type HTML element type (default 'text') + * + * @return string HTML field definition + */ + public static function get_edit_field($col, $value, $attrib, $type = 'text') + { + static $colcounts = array(); + + $fname = '_'.$col; + $attrib['name'] = $fname . ($attrib['array'] ? '[]' : ''); + $attrib['class'] = trim($attrib['class'] . ' ff_' . $col); + + if ($type == 'checkbox') { + $attrib['value'] = '1'; + $input = new html_checkbox($attrib); + } + else if ($type == 'textarea') { + $attrib['cols'] = $attrib['size']; + $input = new html_textarea($attrib); + } + else if ($type == 'select') { + $input = new html_select($attrib); + $input->add('---', ''); + $input->add(array_values($attrib['options']), array_keys($attrib['options'])); + } + else if ($attrib['type'] == 'password') { + $input = new html_passwordfield($attrib); + } + else { + if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') { + $attrib['type'] = 'text'; + } + $input = new html_inputfield($attrib); + } + + // use value from post + if (isset($_POST[$fname])) { + $postvalue = self::get_input_value($fname, self::INPUT_POST, true); + $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue; + } + + $out = $input->show($value); + + return $out; + } + + + /** + * Replace all css definitions with #container [def] + * and remove css-inlined scripting + * + * @param string CSS source code + * @param string Container ID to use as prefix + * + * @return string Modified CSS source + * @todo I'm not sure this should belong to rcube_ui class + */ + public static function mod_css_styles($source, $container_id, $allow_remote=false) + { + $last_pos = 0; + $replacements = new rcube_string_replacer; + + // ignore the whole block if evil styles are detected + $source = self::xss_entity_decode($source); + $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); + $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); + if (preg_match("/$evilexpr/i", $stripped)) { + return '/* evil! */'; + } + + // cut out all contents between { and } + while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { + $styles = substr($source, $pos+1, $pos2-($pos+1)); + + // check every line of a style block... + if ($allow_remote) { + $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); + foreach ($a_styles as $line) { + $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); + // ... and only allow strict url() values + $regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims'; + if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) { + $a_styles = array('/* evil! */'); + break; + } + } + $styles = join(";\n", $a_styles); + } + + $key = $replacements->add($styles); + $source = substr($source, 0, $pos+1) + . $replacements->get_replacement($key) + . substr($source, $pos2, strlen($source)-$pos2); + $last_pos = $pos+2; + } + + // remove html comments and add #container to each tag selector. + // also replace body definition because we also stripped off the <body> tag + $styles = preg_replace( + array( + '/(^\s*<!--)|(-->\s*$)/', + '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im', + '/'.preg_quote($container_id, '/').'\s+body/i', + ), + array( + '', + "\\1#$container_id \\2", + $container_id, + ), + $source); + + // put block contents back in + $styles = $replacements->resolve($styles); + + return $styles; + } + + + /** + * Convert the given date to a human readable form + * This uses the date formatting properties from config + * + * @param mixed Date representation (string, timestamp or DateTime object) + * @param string Date format to use + * @param bool Enables date convertion according to user timezone + * + * @return string Formatted date string + */ + public static function format_date($date, $format = null, $convert = true) + { + global $RCMAIL, $CONFIG; + + if (is_object($date) && is_a($date, 'DateTime')) { + $timestamp = $date->format('U'); + } + else { + if (!empty($date)) { + $timestamp = rcube_strtotime($date); + } + + if (empty($timestamp)) { + return ''; + } + + try { + $date = new DateTime("@".$timestamp); + } + catch (Exception $e) { + return ''; + } + } + + if ($convert) { + try { + // convert to the right timezone + $stz = date_default_timezone_get(); + $tz = new DateTimeZone($RCMAIL->config->get('timezone')); + $date->setTimezone($tz); + date_default_timezone_set($tz->getName()); + + $timestamp = $date->format('U'); + } + catch (Exception $e) { + } + } + + // define date format depending on current time + if (!$format) { + $now = time(); + $now_date = getdate($now); + $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']); + $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']); + + if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) { + $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i')); + $today = true; + } + else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now) { + $format = $RCMAIL->config->get('date_short', 'D H:i'); + } + else { + $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i'); + } + } + + // strftime() format + if (preg_match('/%[a-z]+/i', $format)) { + $format = strftime($format, $timestamp); + if ($stz) { + date_default_timezone_set($stz); + } + return $today ? (self::label('today') . ' ' . $format) : $format; + } + + // parse format string manually in order to provide localized weekday and month names + // an alternative would be to convert the date() format string to fit with strftime() + $out = ''; + for ($i=0; $i<strlen($format); $i++) { + if ($format[$i] == "\\") { // skip escape chars + continue; + } + + // write char "as-is" + if ($format[$i] == ' ' || $format[$i-1] == "\\") { + $out .= $format[$i]; + } + // weekday (short) + else if ($format[$i] == 'D') { + $out .= self::label(strtolower(date('D', $timestamp))); + } + // weekday long + else if ($format[$i] == 'l') { + $out .= self::label(strtolower(date('l', $timestamp))); + } + // month name (short) + else if ($format[$i] == 'M') { + $out .= self::label(strtolower(date('M', $timestamp))); + } + // month name (long) + else if ($format[$i] == 'F') { + $out .= self::label('long'.strtolower(date('M', $timestamp))); + } + else if ($format[$i] == 'x') { + $out .= strftime('%x %X', $timestamp); + } + else { + $out .= date($format[$i], $timestamp); + } + } + + if ($today) { + $label = self::label('today'); + // replcae $ character with "Today" label (#1486120) + if (strpos($out, '$') !== false) { + $out = preg_replace('/\$/', $label, $out, 1); + } + else { + $out = $label . ' ' . $out; + } + } + + if ($stz) { + date_default_timezone_set($stz); + } + + return $out; + } + + + /** + * Return folders list in HTML + * + * @param array $attrib Named parameters + * + * @return string HTML code for the gui object + */ + public static function folder_list($attrib) + { + global $RCMAIL; + static $a_mailboxes; + + $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)'); + + // add some labels to client + $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm'); + + $type = $attrib['type'] ? $attrib['type'] : 'ul'; + unset($attrib['type']); + + if ($type == 'ul' && !$attrib['id']) { + $attrib['id'] = 'rcmboxlist'; + } + + if (empty($attrib['folder_name'])) { + $attrib['folder_name'] = '*'; + } + + // get current folder + $mbox_name = $RCMAIL->storage->get_folder(); + + // build the folders tree + if (empty($a_mailboxes)) { + // get mailbox list + $a_folders = $RCMAIL->storage->list_folders_subscribed( + '', $attrib['folder_name'], $attrib['folder_filter']); + $delimiter = $RCMAIL->storage->get_hierarchy_delimiter(); + $a_mailboxes = array(); + + foreach ($a_folders as $folder) { + self::build_folder_tree($a_mailboxes, $folder, $delimiter); + } + } + + // allow plugins to alter the folder tree or to localize folder names + $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array( + 'list' => $a_mailboxes, + 'delimiter' => $delimiter, + 'type' => $type, + 'attribs' => $attrib, + )); + + $a_mailboxes = $hook['list']; + $attrib = $hook['attribs']; + + if ($type == 'select') { + $select = new html_select($attrib); + + // add no-selection option + if ($attrib['noselection']) { + $select->add(self::label($attrib['noselection']), ''); + } + + self::render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']); + $out = $select->show($attrib['default']); + } + else { + $js_mailboxlist = array(); + $out = html::tag('ul', $attrib, self::render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); + + $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']); + $RCMAIL->output->set_env('mailboxes', $js_mailboxlist); + $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']); + $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders')); + } + + return $out; + } + + + /** + * Return folders list as html_select object + * + * @param array $p Named parameters + * + * @return html_select HTML drop-down object + */ + public static function folder_selector($p = array()) + { + global $RCMAIL; + + $p += array('maxlength' => 100, 'realnames' => false); + $a_mailboxes = array(); + $storage = $RCMAIL->get_storage(); + + if (empty($p['folder_name'])) { + $p['folder_name'] = '*'; + } + + if ($p['unsubscribed']) { + $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); + } + else { + $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); + } + + $delimiter = $storage->get_hierarchy_delimiter(); + + foreach ($list as $folder) { + if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) { + self::build_folder_tree($a_mailboxes, $folder, $delimiter); + } + } + + $select = new html_select($p); + + if ($p['noselection']) { + $select->add($p['noselection'], ''); + } + + self::render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p); + + return $select; + } + + + /** + * Create a hierarchical array of the mailbox list + */ + private static function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '') + { + global $RCMAIL; + + // Handle namespace prefix + $prefix = ''; + if (!$path) { + $n_folder = $folder; + $folder = $RCMAIL->storage->mod_folder($folder); + + if ($n_folder != $folder) { + $prefix = substr($n_folder, 0, -strlen($folder)); + } + } + + $pos = strpos($folder, $delm); + + if ($pos !== false) { + $subFolders = substr($folder, $pos+1); + $currentFolder = substr($folder, 0, $pos); + + // sometimes folder has a delimiter as the last character + if (!strlen($subFolders)) { + $virtual = false; + } + else if (!isset($arrFolders[$currentFolder])) { + $virtual = true; + } + else { + $virtual = $arrFolders[$currentFolder]['virtual']; + } + } + else { + $subFolders = false; + $currentFolder = $folder; + $virtual = false; + } + + $path .= $prefix . $currentFolder; + + if (!isset($arrFolders[$currentFolder])) { + $arrFolders[$currentFolder] = array( + 'id' => $path, + 'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'), + 'virtual' => $virtual, + 'folders' => array()); + } + else { + $arrFolders[$currentFolder]['virtual'] = $virtual; + } + + if (strlen($subFolders)) { + self::build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm); + } + } + + + /** + * Return html for a structured list <ul> for the mailbox tree + */ + private static function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0) + { + global $RCMAIL; + + $maxlength = intval($attrib['maxlength']); + $realnames = (bool)$attrib['realnames']; + $msgcounts = $RCMAIL->storage->get_cache('messagecount'); + $collapsed = $RCMAIL->config->get('collapsed_folders'); + + $out = ''; + foreach ($arrFolders as $key => $folder) { + $title = null; + $folder_class = self::folder_classname($folder['id']); + $collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false; + $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0; + + if ($folder_class && !$realnames) { + $foldername = $RCMAIL->gettext($folder_class); + } + else { + $foldername = $folder['name']; + + // shorten the folder name to a given length + if ($maxlength && $maxlength > 1) { + $fname = abbreviate_string($foldername, $maxlength); + if ($fname != $foldername) { + $title = $foldername; + } + $foldername = $fname; + } + } + + // make folder name safe for ids and class names + $folder_id = self::html_identifier($folder['id'], true); + $classes = array('mailbox'); + + // set special class for Sent, Drafts, Trash and Junk + if ($folder_class) { + $classes[] = $folder_class; + } + + if ($folder['id'] == $mbox_name) { + $classes[] = 'selected'; + } + + if ($folder['virtual']) { + $classes[] = 'virtual'; + } + else if ($unread) { + $classes[] = 'unread'; + } + + $js_name = self::JQ($folder['id']); + $html_name = self::Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : ''); + $link_attrib = $folder['virtual'] ? array() : array( + 'href' => self::url('', array('_mbox' => $folder['id'])), + 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name), + 'rel' => $folder['id'], + 'title' => $title, + ); + + $out .= html::tag('li', array( + 'id' => "rcmli".$folder_id, + 'class' => join(' ', $classes), + 'noclose' => true), + html::a($link_attrib, $html_name) . + (!empty($folder['folders']) ? html::div(array( + 'class' => ($collapsed ? 'collapsed' : 'expanded'), + 'style' => "position:absolute", + 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name) + ), ' ') : '')); + + $jslist[$folder_id] = array( + 'id' => $folder['id'], + 'name' => $foldername, + 'virtual' => $folder['virtual'] + ); + + if (!empty($folder['folders'])) { + $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)), + self::render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1)); + } + + $out .= "</li>\n"; + } + + return $out; + } + + + /** + * Return html for a flat list <select> for the mailbox tree + */ + private static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array()) + { + global $RCMAIL; + + $out = ''; + + foreach ($arrFolders as $key => $folder) { + // skip exceptions (and its subfolders) + if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) { + continue; + } + + // skip folders in which it isn't possible to create subfolders + if (!empty($opts['skip_noinferiors'])) { + $attrs = $RCMAIL->storage->folder_attributes($folder['id']); + if ($attrs && in_array('\\Noinferiors', $attrs)) { + continue; + } + } + + if (!$realnames && ($folder_class = self::folder_classname($folder['id']))) { + $foldername = self::label($folder_class); + } + else { + $foldername = $folder['name']; + + // shorten the folder name to a given length + if ($maxlength && $maxlength > 1) { + $foldername = abbreviate_string($foldername, $maxlength); + } + + $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']); + + if (!empty($folder['folders'])) { + $out .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, + $select, $realnames, $nestLevel+1, $opts); + } + } + } + + return $out; + } + + + /** + * Return internal name for the given folder if it matches the configured special folders + */ + private static function folder_classname($folder_id) + { + global $CONFIG; + + if ($folder_id == 'INBOX') { + return 'inbox'; + } + + // for these mailboxes we have localized labels and css classes + foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx) + { + if ($folder_id == $CONFIG[$smbx.'_mbox']) { + return $smbx; + } + } + } + + + /** + * Try to localize the given IMAP folder name. + * UTF-7 decode it in case no localized text was found + * + * @param string $name Folder name + * + * @return string Localized folder name in UTF-8 encoding + */ + public static function localize_foldername($name) + { + if ($folder_class = self::folder_classname($name)) { + return self::label($folder_class); + } + else { + return rcube_charset::convert($name, 'UTF7-IMAP'); + } + } + + + public static function localize_folderpath($path) + { + global $RCMAIL; + + $protect_folders = $RCMAIL->config->get('protect_default_folders'); + $default_folders = (array) $RCMAIL->config->get('default_folders'); + $delimiter = $RCMAIL->storage->get_hierarchy_delimiter(); + $path = explode($delimiter, $path); + $result = array(); + + foreach ($path as $idx => $dir) { + $directory = implode($delimiter, array_slice($path, 0, $idx+1)); + if ($protect_folders && in_array($directory, $default_folders)) { + unset($result); + $result[] = self::localize_foldername($directory); + } + else { + $result[] = rcube_charset::convert($dir, 'UTF7-IMAP'); + } + } + + return implode($delimiter, $result); + } + + + public static function quota_display($attrib) + { + global $OUTPUT; + + if (!$attrib['id']) { + $attrib['id'] = 'rcmquotadisplay'; + } + + $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text'; + + $OUTPUT->add_gui_object('quotadisplay', $attrib['id']); + + $quota = self::quota_content($attrib); + + $OUTPUT->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready'); + + return html::span($attrib, ''); + } + + + public static function quota_content($attrib = null) + { + global $RCMAIL; + + $quota = $RCMAIL->storage->get_quota(); + $quota = $RCMAIL->plugins->exec_hook('quota', $quota); + + $quota_result = (array) $quota; + $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; + + if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) { + $quota_result['title'] = self::label('unlimited'); + $quota_result['percent'] = 0; + } + else if ($quota['total']) { + if (!isset($quota['percent'])) { + $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100)); + } + + $title = sprintf('%s / %s (%.0f%%)', + self::show_bytes($quota['used'] * 1024), self::show_bytes($quota['total'] * 1024), + $quota_result['percent']); + + $quota_result['title'] = $title; + + if ($attrib['width']) { + $quota_result['width'] = $attrib['width']; + } + if ($attrib['height']) { + $quota_result['height'] = $attrib['height']; + } + } + else { + $quota_result['title'] = self::label('unknown'); + $quota_result['percent'] = 0; + } + + return $quota_result; + } + + + /** + * Outputs error message according to server error/response codes + * + * @param string $fallback Fallback message label + * @param array $fallback_args Fallback message label arguments + */ + public static function display_server_error($fallback = null, $fallback_args = null) + { + global $RCMAIL; + + $err_code = $RCMAIL->storage->get_error_code(); + $res_code = $RCMAIL->storage->get_response_code(); + + if ($err_code < 0) { + $RCMAIL->output->show_message('storageerror', 'error'); + } + else if ($res_code == rcube_storage::NOPERM) { + $RCMAIL->output->show_message('errornoperm', 'error'); + } + else if ($res_code == rcube_storage::READONLY) { + $RCMAIL->output->show_message('errorreadonly', 'error'); + } + else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) { + // try to detect access rights problem and display appropriate message + if (stripos($err_str, 'Permission denied') !== false) { + $RCMAIL->output->show_message('errornoperm', 'error'); + } + else { + $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); + } + } + else if ($fallback) { + $RCMAIL->output->show_message($fallback, 'error', $fallback_args); + } + } + + + /** + * Generate CSS classes from mimetype and filename extension + * + * @param string $mimetype Mimetype + * @param string $filename Filename + * + * @return string CSS classes separated by space + */ + public static function file2class($mimetype, $filename) + { + list($primary, $secondary) = explode('/', $mimetype); + + $classes = array($primary ? $primary : 'unknown'); + if ($secondary) { + $classes[] = $secondary; + } + if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) { + $classes[] = $m[1]; + } + + return strtolower(join(" ", $classes)); + } + + + /** + * Output HTML editor scripts + * + * @param string $mode Editor mode + */ + public static function html_editor($mode = '') + { + global $RCMAIL; + + $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode)); + + if ($hook['abort']) { + return; + } + + $lang = strtolower($_SESSION['language']); + + // TinyMCE uses two-letter lang codes, with exception of Chinese + if (strpos($lang, 'zh_') === 0) { + $lang = str_replace('_', '-', $lang); + } + else { + $lang = substr($lang, 0, 2); + } + + if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) { + $lang = 'en'; + } + + $script = json_encode(array( + 'mode' => $mode, + 'lang' => $lang, + 'skin_path' => $RCMAIL->output->get_skin_path(), + 'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')), + 'spelldict' => intval($RCMAIL->config->get('spellcheck_dictionary')) + )); + + $RCMAIL->output->include_script('tiny_mce/tiny_mce.js'); + $RCMAIL->output->include_script('editor.js'); + $RCMAIL->output->add_script("rcmail_editor_init($script)", 'docready'); + } + + + /** + * Replaces TinyMCE's emoticon images with plain-text representation + * + * @param string $html HTML content + * + * @return string HTML content + */ + public static function replace_emoticons($html) + { + $emoticons = array( + '8-)' => 'smiley-cool', + ':-#' => 'smiley-foot-in-mouth', + ':-*' => 'smiley-kiss', + ':-X' => 'smiley-sealed', + ':-P' => 'smiley-tongue-out', + ':-@' => 'smiley-yell', + ":'(" => 'smiley-cry', + ':-(' => 'smiley-frown', + ':-D' => 'smiley-laughing', + ':-)' => 'smiley-smile', + ':-S' => 'smiley-undecided', + ':-$' => 'smiley-embarassed', + 'O:-)' => 'smiley-innocent', + ':-|' => 'smiley-money-mouth', + ':-O' => 'smiley-surprised', + ';-)' => 'smiley-wink', + ); + + foreach ($emoticons as $idx => $file) { + // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" /> + $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i'; + $replace[] = $idx; + } + + return preg_replace($search, $replace, $html); + } + + + /** + * File upload progress handler. + */ + public static function upload_progress() + { + global $RCMAIL; + + $prefix = ini_get('apc.rfc1867_prefix'); + $params = array( + 'action' => $RCMAIL->action, + 'name' => self::get_input_value('_progress', self::INPUT_GET), + ); + + if (function_exists('apc_fetch')) { + $status = apc_fetch($prefix . $params['name']); + + if (!empty($status)) { + $status['percent'] = round($status['current']/$status['total']*100); + $params = array_merge($status, $params); + } + } + + if (isset($params['percent'])) + $params['text'] = self::label(array('name' => 'uploadprogress', 'vars' => array( + 'percent' => $params['percent'] . '%', + 'current' => self::show_bytes($params['current']), + 'total' => self::show_bytes($params['total']) + ))); + + $RCMAIL->output->command('upload_progress_update', $params); + $RCMAIL->output->send(); + } + + + /** + * Initializes file uploading interface. + */ + public static function upload_init() + { + global $RCMAIL; + + // Enable upload progress bar + if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) { + if ($field_name = ini_get('apc.rfc1867_name')) { + $RCMAIL->output->set_env('upload_progress_name', $field_name); + $RCMAIL->output->set_env('upload_progress_time', (int) $seconds); + } + } + + // find max filesize value + $max_filesize = parse_bytes(ini_get('upload_max_filesize')); + $max_postsize = parse_bytes(ini_get('post_max_size')); + if ($max_postsize && $max_postsize < $max_filesize) { + $max_filesize = $max_postsize; + } + + $RCMAIL->output->set_env('max_filesize', $max_filesize); + $max_filesize = self::show_bytes($max_filesize); + $RCMAIL->output->set_env('filesizeerror', self::label(array( + 'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize)))); + + return $max_filesize; + } + + + /** + * Initializes client-side autocompletion. + */ + public static function autocomplete_init() + { + global $RCMAIL; + static $init; + + if ($init) { + return; + } + + $init = 1; + + if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) { + $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql'); + if (count($book_types) > 1) { + $RCMAIL->output->set_env('autocomplete_threads', $threads); + $RCMAIL->output->set_env('autocomplete_sources', $book_types); + } + } + + $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15)); + $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length')); + $RCMAIL->output->add_label('autocompletechars', 'autocompletemore'); + } + + + /** + * Returns supported font-family specifications + * + * @param string $font Font name + * + * @param string|array Font-family specification array or string (if $font is used) + */ + public static function font_defs($font = null) + { + $fonts = array( + 'Andale Mono' => '"Andale Mono",Times,monospace', + 'Arial' => 'Arial,Helvetica,sans-serif', + 'Arial Black' => '"Arial Black","Avant Garde",sans-serif', + 'Book Antiqua' => '"Book Antiqua",Palatino,serif', + 'Courier New' => '"Courier New",Courier,monospace', + 'Georgia' => 'Georgia,Palatino,serif', + 'Helvetica' => 'Helvetica,Arial,sans-serif', + 'Impact' => 'Impact,Chicago,sans-serif', + 'Tahoma' => 'Tahoma,Arial,Helvetica,sans-serif', + 'Terminal' => 'Terminal,Monaco,monospace', + 'Times New Roman' => '"Times New Roman",Times,serif', + 'Trebuchet MS' => '"Trebuchet MS",Geneva,sans-serif', + 'Verdana' => 'Verdana,Geneva,sans-serif', + ); + + if ($font) { + return $fonts[$font]; + } + + return $fonts; + } + + + /** + * Create a human readable string for a number of bytes + * + * @param int Number of bytes + * + * @return string Byte string + */ + public static function show_bytes($bytes) + { + if ($bytes >= 1073741824) { + $gb = $bytes/1073741824; + $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . self::label('GB'); + } + else if ($bytes >= 1048576) { + $mb = $bytes/1048576; + $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . self::label('MB'); + } + else if ($bytes >= 1024) { + $str = sprintf("%d ", round($bytes/1024)) . self::label('KB'); + } + else { + $str = sprintf('%d ', $bytes) . self::label('B'); + } + + return $str; + } + + + /** + * Decode escaped entities used by known XSS exploits. + * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples + * + * @param string CSS content to decode + * + * @return string Decoded string + * @todo I'm not sure this should belong to rcube_ui class + */ + public static function xss_entity_decode($content) + { + $out = html_entity_decode(html_entity_decode($content)); + $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', + array(self, 'xss_entity_decode_callback'), $out); + $out = preg_replace('#/\*.*\*/#Ums', '', $out); + + return $out; + } + + + /** + * preg_replace_callback callback for xss_entity_decode + * + * @param array $matches Result from preg_replace_callback + * + * @return string Decoded entity + */ + public static function xss_entity_decode_callback($matches) + { + return chr(hexdec($matches[1])); + } + + + /** + * Check if we can process not exceeding memory_limit + * + * @param integer Required amount of memory + * + * @return boolean True if memory won't be exceeded, False otherwise + */ + public static function mem_check($need) + { + $mem_limit = parse_bytes(ini_get('memory_limit')); + $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB + + return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true; + } + + + /** + * Check if working in SSL mode + * + * @param integer $port HTTPS port number + * @param boolean $use_https Enables 'use_https' option checking + * + * @return boolean + */ + public static function https_check($port=null, $use_https=true) + { + global $RCMAIL; + + if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') { + return true; + } + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') { + return true; + } + if ($port && $_SERVER['SERVER_PORT'] == $port) { + return true; + } + if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) { + return true; + } + + return false; + } + +} diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php index d1df64391..804267459 100644 --- a/program/include/rcube_user.php +++ b/program/include/rcube_user.php @@ -66,7 +66,7 @@ class rcube_user if ($id && !$sql_arr) { $sql_result = $this->db->query( - "SELECT * FROM ".get_table_name('users')." WHERE user_id = ?", $id); + "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id); $sql_arr = $this->db->fetch_assoc($sql_result); } @@ -127,9 +127,9 @@ class rcube_user if (!empty($_SESSION['preferences'])) { // Check last write attempt time, try to write again (every 5 minutes) if ($_SESSION['preferences_time'] < time() - 5 * 60) { - $saved_prefs = unserialize($_SESSION['preferences']); + $saved_prefs = unserialize($_SESSION['preferences']); $this->rc->session->remove('preferences'); - $this->rc->session->remove('preferences_time'); + $this->rc->session->remove('preferences_time'); $this->save_prefs($saved_prefs); } else { @@ -173,7 +173,7 @@ class rcube_user $save_prefs = serialize($save_prefs); $this->db->query( - "UPDATE ".get_table_name('users'). + "UPDATE ".$this->db->table_name('users'). " SET preferences = ?". ", language = ?". " WHERE user_id = ?", @@ -232,7 +232,7 @@ class rcube_user $result = array(); $sql_result = $this->db->query( - "SELECT * FROM ".get_table_name('identities'). + "SELECT * FROM ".$this->db->table_name('identities'). " WHERE del <> 1 AND user_id = ?". ($sql_add ? " ".$sql_add : ""). " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC", @@ -267,7 +267,7 @@ class rcube_user $query_params[] = $iid; $query_params[] = $this->ID; - $sql = "UPDATE ".get_table_name('identities'). + $sql = "UPDATE ".$this->db->table_name('identities'). " SET changed = ".$this->db->now().", ".join(', ', $query_cols). " WHERE identity_id = ?". " AND user_id = ?". @@ -301,7 +301,7 @@ class rcube_user $insert_cols[] = 'user_id'; $insert_values[] = $this->ID; - $sql = "INSERT INTO ".get_table_name('identities'). + $sql = "INSERT INTO ".$this->db->table_name('identities'). " (changed, ".join(', ', $insert_cols).")". " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")"; @@ -324,7 +324,7 @@ class rcube_user return false; $sql_result = $this->db->query( - "SELECT count(*) AS ident_count FROM ".get_table_name('identities'). + "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities'). " WHERE user_id = ? AND del <> 1", $this->ID); @@ -335,7 +335,7 @@ class rcube_user return -1; $this->db->query( - "UPDATE ".get_table_name('identities'). + "UPDATE ".$this->db->table_name('identities'). " SET del = 1, changed = ".$this->db->now(). " WHERE user_id = ?". " AND identity_id = ?", @@ -355,7 +355,7 @@ class rcube_user { if ($this->ID && $iid) { $this->db->query( - "UPDATE ".get_table_name('identities'). + "UPDATE ".$this->db->table_name('identities'). " SET ".$this->db->quoteIdentifier('standard')." = '0'". " WHERE user_id = ?". " AND identity_id <> ?". @@ -373,7 +373,7 @@ class rcube_user { if ($this->ID) { $this->db->query( - "UPDATE ".get_table_name('users'). + "UPDATE ".$this->db->table_name('users'). " SET last_login = ".$this->db->now(). " WHERE user_id = ?", $this->ID); @@ -403,7 +403,7 @@ class rcube_user $dbh = rcmail::get_instance()->get_dbh(); // query for matching user name - $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = ?"; + $query = "SELECT * FROM ".$dbh->table_name('users')." WHERE mail_host = ? AND %s = ?"; $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user); // query for matching alias @@ -451,7 +451,7 @@ class rcube_user $dbh = $rcmail->get_dbh(); $dbh->query( - "INSERT INTO ".get_table_name('users'). + "INSERT INTO ".$dbh->table_name('users'). " (created, last_login, username, mail_host, alias, language)". " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)", strip_newlines($user), @@ -507,7 +507,7 @@ class rcube_user } } else { - raise_error(array( + rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, @@ -573,7 +573,7 @@ class rcube_user $sql_result = $this->db->query( "SELECT search_id AS id, ".$this->db->quoteIdentifier('name') - ." FROM ".get_table_name('searches') + ." FROM ".$this->db->table_name('searches') ." WHERE user_id = ?" ." AND ".$this->db->quoteIdentifier('type')." = ?" ." ORDER BY ".$this->db->quoteIdentifier('name'), @@ -607,7 +607,7 @@ class rcube_user "SELECT ".$this->db->quoteIdentifier('name') .", ".$this->db->quoteIdentifier('data') .", ".$this->db->quoteIdentifier('type') - ." FROM ".get_table_name('searches') + ." FROM ".$this->db->table_name('searches') ." WHERE user_id = ?" ." AND search_id = ?", (int) $this->ID, (int) $id); @@ -638,7 +638,7 @@ class rcube_user return false; $this->db->query( - "DELETE FROM ".get_table_name('searches') + "DELETE FROM ".$this->db->table_name('searches') ." WHERE user_id = ?" ." AND search_id = ?", (int) $this->ID, $sid); @@ -668,7 +668,7 @@ class rcube_user $insert_cols[] = $this->db->quoteIdentifier('data'); $insert_values[] = serialize($data['data']); - $sql = "INSERT INTO ".get_table_name('searches') + $sql = "INSERT INTO ".$this->db->table_name('searches') ." (".join(', ', $insert_cols).")" ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")"; diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php index ad8e35e43..7163ef75c 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -389,7 +389,7 @@ class rcube_vcard if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) { foreach ($subnode as $j => $value) { if (is_numeric($j) && is_string($value)) - $card[$key][$i][$j] = rcube_charset_convert($value, $charset); + $card[$key][$i][$j] = rcube_charset::convert($value, $charset); } unset($card[$key][$i]['charset']); } @@ -425,7 +425,7 @@ class rcube_vcard $charset = null; // detect charset and convert to utf-8 else if (($charset = self::detect_encoding($data)) && $charset != RCMAIL_CHARSET) { - $data = rcube_charset_convert($data, $charset); + $data = rcube_charset::convert($data, $charset); $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM $charset = RCMAIL_CHARSET; } @@ -780,7 +780,7 @@ class rcube_vcard )*\z/xs', substr($string, 0, 2048))) return 'UTF-8'; - return rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1 + return rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1 } } diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc index a31370b16..e52da3936 100644 --- a/program/steps/addressbook/func.inc +++ b/program/steps/addressbook/func.inc @@ -203,7 +203,7 @@ function rcmail_directory_list($attrib) 'rel' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); - $sources = (array) $OUTPUT->env['address_sources']; + $sources = (array) $OUTPUT->get_env('address_sources'); reset($sources); // currently selected source diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index ebf79be4e..c0a5bf7bc 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -1162,10 +1162,22 @@ function rcmail_save_attachment(&$message, $pid) $data = $message->get_part_content($pid); } + $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; + $filename = $part->filename; + if (!strlen($filename)) { + if ($mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); + } + else { + $filename = 'Part_'.$pid; + } + $filename .= '.' . $part->ctype_secondary; + } + $attachment = array( 'group' => $COMPOSE['id'], - 'name' => $part->filename ? $part->filename : 'Part_'.$pid.'.'.$part->ctype_secondary, - 'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary, + 'name' => $filename, + 'mimetype' => $mimetype, 'content_id' => $part->content_id, 'data' => $data, 'path' => $path, diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 8dcd37b20..319166c2d 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -230,7 +230,7 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null // Make sure there are no duplicated columns (#1486999) $a_show_cols = array_unique($a_show_cols); - // Plugins may set header's list_cols/list_flags and other rcube_mail_header variables + // Plugins may set header's list_cols/list_flags and other rcube_message_header variables // and list columns $plugin = $RCMAIL->plugins->exec_hook('messages_list', array('messages' => $a_headers, 'cols' => $a_show_cols)); @@ -1024,10 +1024,20 @@ function rcmail_message_body($attrib) foreach ($MESSAGE->parts as $i => $part) { if ($part->type == 'headers') $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers); - else if ($part->type == 'content' && $part->size) { + else if ($part->type == 'content') { + // unsapported + if ($part->realtype) { + if ($part->realtype == 'multipart/encrypted') { + $out .= html::span('part-notice', rcube_label('encryptedmessage')); + } + continue; + } + else if (!$part->size) { + continue; + } // Check if we have enough memory to handle the message in it // #1487424: we need up to 10x more memory than the body - if (!rcmail_mem_check($part->size * 10)) { + else if (!rcmail_mem_check($part->size * 10)) { $out .= html::span('part-notice', rcube_label('messagetoobig'). ' ' . html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id .'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download'))); @@ -1438,9 +1448,14 @@ function rcmail_message_part_controls($attrib) $part = $MESSAGE->mime_parts[$part]; $table = new html_table(array('cols' => 3)); - if (!empty($part->filename)) { + $filename = $part->filename; + if (empty($filename) && $attach_prop->mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); + } + + if (!empty($filename)) { $table->add('title', Q(rcube_label('filename'))); - $table->add('header', Q($part->filename)); + $table->add('header', Q($filename)); $table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download')))); } diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 924433df3..19be4bd07 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -69,8 +69,15 @@ if (!empty($_GET['_uid'])) { // show part page if (!empty($_GET['_frame'])) { - if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id]) && $part->filename) - $OUTPUT->set_pagetitle($part->filename); + if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id])) { + $filename = $part->filename; + if (empty($filename) && $part->mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); + } + if (!empty($filename)) { + $OUTPUT->set_pagetitle($filename); + } + } $OUTPUT->send('messagepart'); exit; @@ -130,15 +137,25 @@ else if (strlen($pid = get_input_value('_part', RCUBE_INPUT_GET))) { $out = rcmail_print_body($part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false)); } - $OUTPUT = new rcube_html_page(); + $OUTPUT = new rcube_output_html(); $OUTPUT->write($out); } else { // don't kill the connection if download takes more than 30 sec. @set_time_limit(0); + if ($part->filename) { + $filename = $part->filename; + } + else if ($part->mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); + } + else { + $filename = ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube'); + } + $ext = '.' . ($mimetype == 'text/plain' ? 'txt' : $ctype_secondary); - $filename = $part->filename ? $part->filename : ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube') . $ext; + $filename .= $ext; $filename = preg_replace('[\r\n]', '', $filename); if ($browser->ie && $browser->ver < 7) diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index c6c6d9636..076098a56 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -126,20 +126,24 @@ function rcmail_message_attachments($attrib) if (sizeof($MESSAGE->attachments)) { foreach ($MESSAGE->attachments as $attach_prop) { - if ($PRINT_MODE) { - $ol .= html::tag('li', null, sprintf("%s (%s)", Q($attach_prop->filename), Q(show_bytes($attach_prop->size)))); + $filename = $attach_prop->filename; + if (empty($filename) && $attach_prop->mimetype == 'text/html') { + $filename = rcube_label('htmlmessage'); } - else { - if (mb_strlen($attach_prop->filename) > 50) { - $filename = abbreviate_string($attach_prop->filename, 50); - $title = $attach_prop->filename; + + if ($PRINT_MODE) { + $ol .= html::tag('li', null, sprintf("%s (%s)", Q($filename), Q(show_bytes($attach_prop->size)))); } else { - $filename = $attach_prop->filename; - $title = ''; - } - - $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $attach_prop->filename), + if (mb_strlen($filename) > 50) { + $filename = abbreviate_string($filename, 50); + $title = $filename; + } + else { + $title = ''; + } + + $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $filename), html::a(array( 'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false), 'onclick' => sprintf( diff --git a/program/steps/utils/error.inc b/program/steps/utils/error.inc index 050c1f7cd..000674417 100644 --- a/program/steps/utils/error.inc +++ b/program/steps/utils/error.inc @@ -22,6 +22,7 @@ */ +$rcmail = rcmail::get_instance(); // browser is not compatible with this application if ($ERROR_CODE==409) { @@ -88,7 +89,7 @@ else { $__error_title = "SERVICE CURRENTLY NOT AVAILABLE!"; $__error_text = "Please contact your server-administrator."; - if (($CONFIG['debug_level'] & 4) && $ERROR_MESSAGE) + if (($rcmail->config->get('debug_level') & 4) && $ERROR_MESSAGE) $__error_text = $ERROR_MESSAGE; else $__error_text = sprintf('Error No. [%s]', $ERROR_CODE); @@ -97,7 +98,7 @@ else { $HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500; // Ajax request -if ($OUTPUT && ($OUTPUT instanceof rcube_json_output)) { +if ($rcmail->output && $rcmail->output->type == 'js') { header("HTTP/1.0 $HTTP_ERR_CODE $__error_title"); die; } @@ -110,13 +111,13 @@ $__page_content = <<<EOF </div> EOF; -if ($OUTPUT && $OUTPUT->template_exists('error')) { - $OUTPUT->reset(); - $OUTPUT->send('error'); +if ($rcmail->output && $rcmail->output->template_exists('error')) { + $rcmail->output->reset(); + $rcmail->output->send('error'); } -$__skin = $CONFIG->skin ? $CONFIG->skin : 'default'; -$__productname = $CONFIG['product_name'] ? $CONFIG['product_name'] : 'Roundcube Webmail'; +$__skin = $rcmail->config->get('skin', 'default'); +$__productname = $rcmail->config->get('product_name', 'Roundcube Webmail'); // print system error page print <<<EOF |