Page MenuHomePhorge

No OneTemporary

Size
269 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
This document is not UTF8. It was detected as ISO-8859-1 (Latin 1) and converted to UTF8 for display.
diff --git a/program/include/bc.php b/program/include/bc.php
index b589135ff..40524be50 100644
--- a/program/include/bc.php
+++ b/program/include/bc.php
@@ -1,400 +1,400 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/bc.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 deprecated functions aliases for backward compatibility |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Roundcube Webmail deprecated functions
*
* @package Core
* @author Thomas Bruederli <roundcube@gmail.com>
*/
// constants for input reading
define('RCUBE_INPUT_GET', rcube_utils::INPUT_GET);
define('RCUBE_INPUT_POST', rcube_utils::INPUT_POST);
define('RCUBE_INPUT_GPC', rcube_utils::INPUT_GPC);
-define('JS_OBJECT_NAME', rcmail::JS_OBJECT_NAME);
+define('JS_OBJECT_NAME', rcmail_output::JS_OBJECT_NAME);
define('RCMAIL_CHARSET', RCUBE_CHARSET);
function get_table_name($table)
{
return rcmail::get_instance()->db->table_name($table);
}
function rcube_label($p, $domain=null)
{
return rcmail::get_instance()->gettext($p, $domain);
}
function rcube_label_exists($name, $domain=null, &$ref_domain = null)
{
return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
}
function rcmail_overwrite_action($action)
{
rcmail::get_instance()->overwrite_action($action);
}
function rcmail_url($action, $p=array(), $task=null)
{
return rcmail::get_instance()->url((array)$p + array('_action' => $action, 'task' => $task));
}
function rcmail_temp_gc()
{
$rcmail = rcmail::get_instance()->temp_gc();
}
function rcube_charset_convert($str, $from, $to=NULL)
{
return rcube_charset::convert($str, $from, $to);
}
function rc_detect_encoding($string, $failover='')
{
return rcube_charset::detect($string, $failover);
}
function rc_utf8_clean($input)
{
return rcube_charset::clean($input);
}
function json_serialize($input)
{
return rcube_output::json_serialize($input);
}
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=true)
{
return rcube_utils::rep_specialchars_output($str, $enctype, $mode, $newlines);
}
function Q($str, $mode='strict', $newlines=true)
{
return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
}
function JQ($str)
{
return rcube_utils::rep_specialchars_output($str, 'js');
}
function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
{
return rcube_utils::get_input_value($fname, $source, $allow_html, $charset);
}
function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
{
return rcube_utils::parse_input_value($value, $allow_html, $charset);
}
function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
{
return rcube_utils::request2param($mode, $ignore);
}
function html_identifier($str, $encode=false)
{
return rcube_utils::html_identifier($str, $encode);
}
function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
{
return rcmail::get_instance()->table_output($attrib, $table_data, $a_show_cols, $id_col);
}
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
- return rcube_utils::get_edit_field($col, $value, $attrib, $type);
+ return rcube_output::get_edit_field($col, $value, $attrib, $type);
}
function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
{
return rcube_utils::mod_css_styles($source, $container_id, $allow_remote);
}
function rcmail_xss_entity_decode($content)
{
return rcube_utils::xss_entity_decode($content);
}
function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
{
return html::attrib_string($attrib, $allowed_attribs);
}
function parse_attrib_string($str)
{
return html::parse_attrib_string($str);
}
function format_date($date, $format=NULL, $convert=true)
{
return rcmail::get_instance()->format_date($date, $format, $convert);
}
function rcmail_mailbox_list($attrib)
{
return rcmail::get_instance()->folder_list($attrib);
}
function rcmail_mailbox_select($attrib = array())
{
return rcmail::get_instance()->folder_selector($attrib);
}
function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
{
return rcmail::get_instance()->render_folder_tree_html($arrFolders, $mbox_name, $jslist, $attrib, $nestLevel);
}
function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
{
return rcmail::get_instance()->render_folder_tree_select($arrFolders, $mbox_name, $maxlength, $select, $realnames, $nestLevel, $opts);
}
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
{
return rcmail::get_instance()->build_folder_tree($arrFolders, $folder, $delm, $path);
}
function rcmail_folder_classname($folder_id)
{
return rcmail::get_instance()->folder_classname($folder_id);
}
function rcmail_localize_foldername($name)
{
return rcmail::get_instance()->localize_foldername($name);
}
function rcmail_localize_folderpath($path)
{
return rcmail::get_instance()->localize_folderpath($path);
}
function rcmail_quota_display($attrib)
{
return rcmail::get_instance()->quota_display($attrib);
}
function rcmail_quota_content($attrib = null)
{
return rcmail::get_instance()->quota_content($attrib);
}
function rcmail_display_server_error($fallback=null, $fallback_args=null)
{
rcmail::get_instance()->display_server_error($fallback, $fallback_args);
}
function rcmail_filetype2classname($mimetype, $filename)
{
return rcube_utils::file2class($mimetype, $filename);
}
function rcube_html_editor($mode='')
{
rcmail::get_instance()->html_editor($mode);
}
function rcmail_replace_emoticons($html)
{
return rcmail::get_instance()->replace_emoticons($html);
}
function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
{
return rcmail::get_instance()->deliver_message($message, $from, $mailto, $smtp_error, $body_file, $smtp_opts);
}
function rcmail_gen_message_id()
{
return rcmail::get_instance()->gen_message_id();
}
function rcmail_user_date()
{
return rcmail::get_instance()->user_date();
}
function rcmail_mem_check($need)
{
return rcube_utils::mem_check($need);
}
function rcube_https_check($port=null, $use_https=true)
{
return rcube_utils::https_check($port, $use_https);
}
function rcube_sess_unset($var_name=null)
{
rcmail::get_instance()->session->remove($var_name);
}
function rcube_parse_host($name, $host='')
{
return rcube_utils::parse_host($name, $host);
}
function check_email($email, $dns_check=true)
{
return rcube_utils::check_email($email, $dns_check);
}
function console()
{
call_user_func_array(array('rcmail', 'console'), func_get_args());
}
function write_log($name, $line)
{
return rcmail::write_log($name, $line);
}
function rcmail_log_login()
{
return rcmail::get_instance()->log_login();
}
function rcmail_remote_ip()
{
return rcube_utils::remote_ip();
}
function rcube_check_referer()
{
return rcmail::check_referer();
}
function rcube_timer()
{
return rcmail::timer();
}
function rcube_print_time($timer, $label='Timer', $dest='console')
{
rcmail::print_timer($timer, $label, $dest);
}
function raise_error($arg=array(), $log=false, $terminate=false)
{
rcmail::raise_error($arg, $log, $terminate);
}
function rcube_log_bug($arg_arr)
{
rcmail::log_bug($arg_arr);
}
function rcube_upload_progress()
{
rcmail::get_instance()->upload_progress();
}
function rcube_upload_init()
{
return rcmail::get_instance()->upload_init();
}
function rcube_autocomplete_init()
{
rcmail::get_instance()->autocomplete_init();
}
function rcube_fontdefs($font = null)
{
return rcmail::font_defs($font);
}
function send_nocacheing_headers()
{
return rcmail::get_instance()->output->nocacheing_headers();
}
function show_bytes($bytes)
{
return rcmail::get_instance()->show_bytes($bytes);
}
function rc_wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null)
{
return rcube_mime::wordwrap($string, $width, $break, $cut, $charset);
}
function rc_request_header($name)
{
return rcube_utils::request_header($name);
}
function rcube_explode_quoted_string($delimiter, $string)
{
return rcube_utils::explode_quoted_string($delimiter, $string);
}
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 rc_image_content_type($data)
{
return rcube_mime::image_content_type($data);
}
function rcube_strtotime($date)
{
return rcube_utils::strtotime($date);
}
function rcube_idn_to_ascii($str)
{
return rcube_utils::idn_to_ascii($str);
}
function rcube_idn_to_utf8($str)
{
return rcube_utils::idn_to_utf8($str);
}
function send_future_expire_header($offset = 2600000)
{
return rcmail::get_instance()->output->future_expire_header($offset);
}
function get_opt($aliases = array())
{
return rcube_utils::get_opt($aliases);
}
function prompt_silent($prompt = 'Password:')
{
return rcube_utils::prompt_silent($prompt);
}
function get_boolean($str)
{
return rcube_utils::get_boolean($str);
}
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 25ae0189b..be71fc084 100644
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -1,79 +1,97 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/iniset.php |
| |
| This file is part of the Roundcube Webmail client |
| 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: |
| Setup the application environment required to process |
| any request. |
+-----------------------------------------------------------------------+
| Author: Till Klampaeckel <till@php.net> |
| Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// application constants
define('RCMAIL_VERSION', '0.9-git');
define('RCMAIL_START', microtime(true));
$config = array(
// Some users are not using Installer, so we'll check some
// critical PHP settings here. Only these, which doesn't provide
// an error/warning in the logs later. See (#1486307).
'suhosin.session.encrypt' => 0,
'session.auto_start' => 0,
'file_uploads' => 1,
);
foreach ($config as $optname => $optval) {
if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) {
die("ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
."Check your PHP configuration (including php_admin_flag).");
}
}
if (!defined('INSTALL_PATH')) {
define('INSTALL_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
}
if (!defined('RCMAIL_CONFIG_DIR')) {
define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
}
if (!defined('RCUBE_LOCALIZATION_DIR')) {
define('RCUBE_LOCALIZATION_DIR', INSTALL_PATH . 'program/localization/');
}
define('RCUBE_INSTALL_PATH', INSTALL_PATH);
define('RCUBE_CONFIG_DIR', RCMAIL_CONFIG_DIR.'/');
// 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
$include_path = INSTALL_PATH . 'program/lib' . PATH_SEPARATOR;
$include_path.= ini_get('include_path');
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}
// increase maximum execution time for php scripts
// (does not work in safe mode)
@set_time_limit(120);
// include Roundcube Framework
require_once 'Roundcube/bootstrap.php';
-// backward compatybility (to be removed)
-require_once INSTALL_PATH . 'program/include/rcmail.php';
+// register autoloader for rcmail app classes
+spl_autoload_register('rcmail_autoload');
// backward compatybility (to be removed)
require_once INSTALL_PATH . 'program/include/bc.php';
+
+
+/**
+ * PHP5 autoloader routine for dynamic class loading
+ */
+function rcmail_autoload($classname)
+{
+ if (strpos($classname, 'rcmail') === 0) {
+ $filepath = INSTALL_PATH . "program/include/$classname.php";
+ if (is_readable($filepath)) {
+ include_once $filepath;
+ return true;
+ }
+ }
+
+ return false;
+}
+
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 3a8c62cec..8e01a2155 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1,2103 +1,2101 @@
<?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: |
| Application class providing core functions and holding |
| instances of all 'global' objects like db- and imap-connections |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Application class of Roundcube Webmail
* implemented as singleton
*
* @package Core
*/
class rcmail extends rcube
{
/**
* Main tasks.
*
* @var array
*/
static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
/**
* Current task.
*
* @var string
*/
public $task;
/**
* Current action.
*
* @var string
*/
public $action = '';
public $comm_path = './';
private $address_books = array();
private $action_map = array();
- const JS_OBJECT_NAME = 'rcmail';
-
const ERROR_STORAGE = -2;
const ERROR_INVALID_REQUEST = 1;
const ERROR_INVALID_HOST = 2;
const ERROR_COOKIES_DISABLED = 3;
/**
* This implements the 'singleton' design pattern
*
* @return rcmail The one and only instance
*/
static function get_instance()
{
if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
self::$instance = new rcmail();
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Initial startup function
* to register session, create database and imap connections
*/
protected function startup()
{
$this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
// start session
$this->session_init();
// create user object
$this->set_user(new rcube_user($_SESSION['user_id']));
// set task and action properties
$this->set_task(rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC));
$this->action = asciiwords(rcube_utils::get_input_value('_action', rcube_utils::INPUT_GPC));
// reset some session parameters when changing task
if ($this->task != 'utils') {
if ($this->session && $_SESSION['task'] != $this->task)
$this->session->remove('page');
// set current task to session
$_SESSION['task'] = $this->task;
}
// init output class
if (!empty($_REQUEST['_remote']))
$GLOBALS['OUTPUT'] = $this->json_init();
else
$GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
// load plugins
$this->plugins->init($this, $this->task);
$this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
}
/**
* Setter for application task
*
* @param string Task to set
*/
public function set_task($task)
{
$task = asciiwords($task);
if ($this->user && $this->user->ID)
$task = !$task ? 'mail' : $task;
else
$task = 'login';
$this->task = $task;
$this->comm_path = $this->url(array('task' => $this->task));
if ($this->output)
$this->output->set_env('task', $this->task);
}
/**
* Setter for system user object
*
* @param rcube_user Current user instance
*/
public function set_user($user)
{
if (is_object($user)) {
$this->user = $user;
// overwrite config with user preferences
$this->config->set_user_prefs((array)$this->user->get_prefs());
}
$lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
$_SESSION['language'] = $this->user->language = $lang;
// set localization
setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
// workaround for http://bugs.php.net/bug.php?id=18556
if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
}
}
/**
* Return instance of the internal address book class
*
* @param string Address book identifier (-1 for default addressbook)
* @param boolean True if the address book needs to be writeable
*
* @return rcube_contacts Address book object
*/
public function get_address_book($id, $writeable = false)
{
$contacts = null;
$ldap_config = (array)$this->config->get('ldap_public');
// 'sql' is the alias for '0' used by autocomplete
if ($id == 'sql')
$id = '0';
else if ($id == -1) {
$id = $this->config->get('default_addressbook');
$default = true;
}
// use existing instance
if (isset($this->address_books[$id]) && ($this->address_books[$id] instanceof rcube_addressbook)) {
$contacts = $this->address_books[$id];
}
else if ($id && $ldap_config[$id]) {
$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->get_user_id());
}
else {
$plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
// plugin returned instance of a rcube_addressbook
if ($plugin['instance'] instanceof rcube_addressbook) {
$contacts = $plugin['instance'];
}
}
// when user requested default writeable addressbook
// we need to check if default is writeable, if not we
// will return first writeable book (if any exist)
if ($contacts && $default && $contacts->readonly && $writeable) {
$contacts = null;
}
// Get first addressbook from the list if configured default doesn't exist
// This can happen when user deleted the addressbook (e.g. Kolab folder)
if (!$contacts && (!$id || $default)) {
$source = reset($this->get_address_sources($writeable, !$default));
if (!empty($source)) {
$contacts = $this->get_address_book($source['id']);
if ($contacts) {
$id = $source['id'];
}
}
}
if (!$contacts) {
self::raise_error(array(
'code' => 700, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Addressbook source ($id) not found!"),
true, true);
}
// add to the 'books' array for shutdown function
$this->address_books[$id] = $contacts;
if ($writeable && $contacts->readonly) {
return null;
}
// set configured sort order
if ($sort_col = $this->config->get('addressbook_sort_col')) {
$contacts->set_sort_order($sort_col);
}
return $contacts;
}
/**
* Return address books list
*
* @param boolean True if the address book needs to be writeable
* @param boolean True if the address book needs to be not hidden
*
* @return array Address books array
*/
public function get_address_sources($writeable = false, $skip_hidden = false)
{
$abook_type = strtolower($this->config->get('address_book_type'));
$ldap_config = $this->config->get('ldap_public');
$autocomplete = (array) $this->config->get('autocomplete_addressbooks');
$list = array();
// We are using the DB address book or a plugin address book
if ($abook_type != 'ldap' && $abook_type != '') {
if (!isset($this->address_books['0']))
$this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
$list['0'] = array(
'id' => '0',
'name' => $this->gettext('personaladrbook'),
'groups' => $this->address_books['0']->groups,
'readonly' => $this->address_books['0']->readonly,
'autocomplete' => in_array('sql', $autocomplete),
'undelete' => $this->address_books['0']->undelete && $this->config->get('undo_timeout'),
);
}
if ($ldap_config) {
$ldap_config = (array) $ldap_config;
foreach ($ldap_config as $id => $prop) {
// handle misconfiguration
if (empty($prop) || !is_array($prop)) {
continue;
}
$list[$id] = array(
'id' => $id,
'name' => html::quote($prop['name']),
'groups' => is_array($prop['groups']),
'readonly' => !$prop['writable'],
'hidden' => $prop['hidden'],
'autocomplete' => in_array($id, $autocomplete)
);
}
}
$plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
$list = $plugin['sources'];
foreach ($list as $idx => $item) {
// register source for shutdown function
if (!is_object($this->address_books[$item['id']])) {
$this->address_books[$item['id']] = $item;
}
// remove from list if not writeable as requested
if ($writeable && $item['readonly']) {
unset($list[$idx]);
}
// remove from list if hidden as requested
else if ($skip_hidden && $item['hidden']) {
unset($list[$idx]);
}
}
return $list;
}
/**
* Init output object for GUI and add common scripts.
- * This will instantiate a rcube_output_html object and set
+ * This will instantiate a rcmail_output_html object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
- * @return rcube_output_html Reference to HTML output object
+ * @return rcube_output Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
- if (!($this->output instanceof rcube_output_html))
- $this->output = new rcube_output_html($this->task, $framed);
+ if (!($this->output instanceof rcmail_output_html))
+ $this->output = new rcmail_output_html($this->task, $framed);
// set refresh interval
$this->output->set_env('refresh_interval', $this->config->get('refresh_interval', 0));
$this->output->set_env('session_lifetime', $this->config->get('session_lifetime', 0) * 60);
if ($framed) {
$this->comm_path .= '&_framed=1';
$this->output->set_env('framed', true);
}
$this->output->set_env('task', $this->task);
$this->output->set_env('action', $this->action);
$this->output->set_env('comm_path', $this->comm_path);
$this->output->set_charset(RCUBE_CHARSET);
// add some basic labels to client
$this->output->add_label('loading', 'servererror', 'requesttimedout', 'refreshing');
return $this->output;
}
/**
* Create an output object for JSON responses
*
- * @return rcube_output_json Reference to JSON output object
+ * @return rcube_output Reference to JSON output object
*/
public function json_init()
{
- if (!($this->output instanceof rcube_output_json))
- $this->output = new rcube_output_json($this->task);
+ if (!($this->output instanceof rcmail_output_json))
+ $this->output = new rcmail_output_json($this->task);
return $this->output;
}
/**
* Create session object and start the session.
*/
public function session_init()
{
parent::session_init();
// set initial session vars
if (!$_SESSION['user_id'])
$_SESSION['temp'] = true;
// restore skin selection after logout
if ($_SESSION['temp'] && !empty($_SESSION['skin']))
$this->config->set('skin', $_SESSION['skin']);
}
/**
* Perfom login to the mail server and to the webmail service.
* This will also create a new user entry if auto_create_user is configured.
*
* @param string Mail storage (IMAP) user name
* @param string Mail storage (IMAP) password
* @param string Mail storage (IMAP) host
* @param bool Enables cookie check
*
* @return boolean True on success, False on failure
*/
function login($username, $pass, $host = null, $cookiecheck = false)
{
$this->login_error = null;
if (empty($username)) {
return false;
}
if ($cookiecheck && empty($_COOKIE)) {
$this->login_error = self::ERROR_COOKIES_DISABLED;
return false;
}
$config = $this->config->all();
if (!$host)
$host = $config['default_host'];
// Validate that selected host is in the list of configured hosts
if (is_array($config['default_host'])) {
$allowed = false;
foreach ($config['default_host'] as $key => $host_allowed) {
if (!is_numeric($key))
$host_allowed = $key;
if ($host == $host_allowed) {
$allowed = true;
break;
}
}
if (!$allowed) {
$host = null;
}
}
else if (!empty($config['default_host']) && $host != rcube_utils::parse_host($config['default_host'])) {
$host = null;
}
if (!$host) {
$this->login_error = self::ERROR_INVALID_HOST;
return false;
}
// parse $host URL
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port']))
$port = $a_host['port'];
else if ($ssl && $ssl != 'tls' && (!$config['default_port'] || $config['default_port'] == 143))
$port = 993;
}
if (!$port) {
$port = $config['default_port'];
}
/* Modify username with domain if required
Inspired by Marco <P0L0_notspam_binware.org>
*/
// 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_utils::parse_host($config['username_domain'][$host], $host);
else if (is_string($config['username_domain']))
$username .= '@'.rcube_utils::parse_host($config['username_domain'], $host);
}
if (!isset($config['login_lc'])) {
$config['login_lc'] = 2; // default
}
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username (#1487113)
if ($config['login_lc']) {
if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
$username = mb_strtolower($username);
}
else if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
}
}
// try to resolve email address from virtuser table
if (strpos($username, '@') && ($virtuser = rcube_user::email2user($username))) {
$username = $virtuser;
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_utils::idn_to_ascii($host);
$username = rcube_utils::idn_to_ascii($username);
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
}
$storage = $this->get_storage();
// try to log in
if (!$storage->connect($host, $username, $pass, $port, $ssl)) {
return false;
}
// user already registered -> update user's record
if (is_object($user)) {
// update last login timestamp
$user->touch();
}
// create new system user
else if ($config['auto_create_user']) {
if ($created = rcube_user::create($username, $host)) {
$user = $created;
}
else {
self::raise_error(array(
'code' => 620, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record. Maybe aborted by a plugin?"
), true, false);
}
}
else {
self::raise_error(array(
'code' => 621, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
), true, false);
}
// login succeeded
if (is_object($user) && $user->ID) {
// Configure environment
$this->set_user($user);
$this->set_storage_prop();
// fix some old settings according to namespace prefix
$this->fix_namespace_settings($user);
// create default folders on first login
if ($config['create_default_folders'] && (!empty($created) || empty($user->data['last_login']))) {
$storage->create_default_folders();
}
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
$_SESSION['storage_host'] = $host;
$_SESSION['storage_port'] = $port;
$_SESSION['storage_ssl'] = $ssl;
$_SESSION['password'] = $this->encrypt($pass);
$_SESSION['login_time'] = time();
if (isset($_REQUEST['_timezone']) && $_REQUEST['_timezone'] != '_default_')
$_SESSION['timezone'] = rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_GPC);
// force reloading complete list of subscribed mailboxes
$storage->clear_cache('mailboxes', true);
return true;
}
return false;
}
/**
* Returns error code of last login operation
*
* @return int Error code
*/
public function login_error()
{
if ($this->login_error) {
return $this->login_error;
}
if ($this->storage && $this->storage->get_error_code() < -1) {
return self::ERROR_STORAGE;
}
}
/**
* Auto-select IMAP host based on the posted login information
*
* @return string Selected IMAP host
*/
public function autoselect_host()
{
$default_host = $this->config->get('default_host');
$host = null;
if (is_array($default_host)) {
$post_host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
$post_user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST);
list($user, $domain) = explode('@', $post_user);
// direct match in default_host array
if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
$host = $post_host;
}
// try to select host by mail domain
else if (!empty($domain)) {
foreach ($default_host as $storage_host => $mail_domains) {
if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
$host = $storage_host;
break;
}
else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
$host = is_numeric($storage_host) ? $mail_domains : $storage_host;
break;
}
}
}
// take the first entry if $host is still not set
if (empty($host)) {
list($key, $val) = each($default_host);
$host = is_numeric($key) ? $val : $key;
}
}
else if (empty($default_host)) {
$host = rcube_utils::get_input_value('_host', rcube_utils::INPUT_POST);
}
else
$host = rcube_utils::parse_host($default_host);
return $host;
}
/**
* Destroy session data and remove cookie
*/
public function kill_session()
{
$this->plugins->exec_hook('session_destroy');
$this->session->kill();
$_SESSION = array('language' => $this->user->language, 'temp' => true, 'skin' => $this->config->get('skin'));
$this->user->reset();
}
/**
* Do server side actions on logout
*/
public function logout_actions()
{
$config = $this->config->all();
$storage = $this->get_storage();
if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
$storage->clear_folder($config['trash_mbox']);
}
if ($config['logout_expunge']) {
$storage->expunge_folder('INBOX');
}
// Try to save unsaved user preferences
if (!empty($_SESSION['preferences'])) {
$this->user->save_prefs(unserialize($_SESSION['preferences']));
}
}
/**
* Generate a unique token to be used in a form request
*
* @return string The request token
*/
public function get_request_token()
{
$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->get_user_id() . $this->config->get('des_key') . $sess_id)));
return $plugin['value'];
}
/**
* Check if the current request contains a valid token
*
* @param int Request method
* @return boolean True if request token is valid false if not
*/
public function check_request($mode = rcube_utils::INPUT_POST)
{
$token = rcube_utils::get_input_value('_token', $mode);
$sess_id = $_COOKIE[ini_get('session.name')];
return !empty($sess_id) && $token == $this->get_request_token();
}
/**
* Create unique authorization hash
*
* @param string Session ID
* @param int Timestamp
* @return string The generated auth hash
*/
private function get_auth_hash($sess_id, $ts)
{
$auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
$sess_id,
$ts,
$this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
$_SERVER['HTTP_USER_AGENT']);
if (function_exists('sha1'))
return sha1($auth_string);
else
return md5($auth_string);
}
/**
* 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)
{
if (!is_array($p)) {
if (strpos($p, 'http') === 0)
return $p;
$p = array('_action' => @func_get_arg(0));
}
$task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task);
$p['_task'] = $task;
unset($p['task']);
$url = './';
$delm = '?';
foreach (array_reverse($p) as $key => $val) {
if ($val !== '' && $val !== null) {
$par = $key[0] == '_' ? $key : '_'.$key;
$url .= $delm.urlencode($par).'='.urlencode($val);
$delm = '&';
}
}
return $url;
}
/**
* Function to be executed in script shutdown
*/
public function shutdown()
{
parent::shutdown();
foreach ($this->address_books as $book) {
if (is_object($book) && is_a($book, 'rcube_addressbook'))
$book->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 = $this->show_bytes(memory_get_usage());
if (function_exists('memory_get_peak_usage'))
$mem .= '/'.$this->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);
}
}
/**
* Registers action aliases for current task
*
* @param array $map Alias-to-filename hash array
*/
public function register_action_map($map)
{
if (is_array($map)) {
foreach ($map as $idx => $val) {
$this->action_map[$idx] = $val;
}
}
}
/**
* Returns current action filename
*
* @param array $map Alias-to-filename hash array
*/
public function get_action_file()
{
if (!empty($this->action_map[$this->action])) {
return $this->action_map[$this->action];
}
return strtr($this->action, '-', '_') . '.inc';
}
/**
* Fixes some user preferences according to namespace handling change.
* Old Roundcube versions were using folder names with removed namespace prefix.
* Now we need to add the prefix on servers where personal namespace has prefix.
*
* @param rcube_user $user User object
*/
private function fix_namespace_settings($user)
{
$prefix = $this->storage->get_namespace('prefix');
$prefix_len = strlen($prefix);
if (!$prefix_len)
return;
$prefs = $this->config->all();
if (!empty($prefs['namespace_fixed']))
return;
// Build namespace prefix regexp
$ns = $this->storage->get_namespace();
$regexp = array();
foreach ($ns as $entry) {
if (!empty($entry)) {
foreach ($entry as $item) {
if (strlen($item[0])) {
$regexp[] = preg_quote($item[0], '/');
}
}
}
}
$regexp = '/^('. implode('|', $regexp).')/';
// Fix preferences
$opts = array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox', 'archive_mbox');
foreach ($opts as $opt) {
if ($value = $prefs[$opt]) {
if ($value != 'INBOX' && !preg_match($regexp, $value)) {
$prefs[$opt] = $prefix.$value;
}
}
}
if (!empty($prefs['default_folders'])) {
foreach ($prefs['default_folders'] as $idx => $name) {
if ($name != 'INBOX' && !preg_match($regexp, $name)) {
$prefs['default_folders'][$idx] = $prefix.$name;
}
}
}
if (!empty($prefs['search_mods'])) {
$folders = array();
foreach ($prefs['search_mods'] as $idx => $value) {
if ($idx != 'INBOX' && $idx != '*' && !preg_match($regexp, $idx)) {
$idx = $prefix.$idx;
}
$folders[$idx] = $value;
}
$prefs['search_mods'] = $folders;
}
if (!empty($prefs['message_threading'])) {
$folders = array();
foreach ($prefs['message_threading'] as $idx => $value) {
if ($idx != 'INBOX' && !preg_match($regexp, $idx)) {
$idx = $prefix.$idx;
}
$folders[$prefix.$idx] = $value;
}
$prefs['message_threading'] = $folders;
}
if (!empty($prefs['collapsed_folders'])) {
$folders = explode('&&', $prefs['collapsed_folders']);
$count = count($folders);
$folders_str = '';
if ($count) {
$folders[0] = substr($folders[0], 1);
$folders[$count-1] = substr($folders[$count-1], 0, -1);
}
foreach ($folders as $value) {
if ($value != 'INBOX' && !preg_match($regexp, $value)) {
$value = $prefix.$value;
}
$folders_str .= '&'.$value.'&';
}
$prefs['collapsed_folders'] = $folders_str;
}
$prefs['namespace_fixed'] = true;
// save updated preferences and reset imap settings (default folders)
$user->save_prefs($prefs);
$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');
}
/**
* 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, rcube_utils::remote_ip(), session_id()));
}
/**
* 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 function table_output($attrib, $table_data, $a_show_cols, $id_col)
{
$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, $this->Q($this->gettext($col)));
}
}
if (!is_array($table_data)) {
$db = $this->get_dbh();
while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
$table->add_row(array('id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])));
// format each col
foreach ($a_show_cols as $col) {
$table->add($col, $this->Q($sql_arr[$col]));
}
}
}
else {
foreach ($table_data as $row_data) {
$class = !empty($row_data['class']) ? $row_data['class'] : '';
$rowid = 'rcmrow' . rcube_utils::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, $this->Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
}
}
}
return $table->show($attrib);
}
/**
* 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 function format_date($date, $format = null, $convert = true)
{
if (is_object($date) && is_a($date, 'DateTime')) {
$timestamp = $date->format('U');
}
else {
if (!empty($date)) {
$timestamp = rcube_utils::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($this->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']);
$pretty_date = $this->config->get('prettydate');
if ($pretty_date && $timestamp > $today_limit && $timestamp < $now) {
$format = $this->config->get('date_today', $this->config->get('time_format', 'H:i'));
$today = true;
}
else if ($pretty_date && $timestamp > $week_limit && $timestamp < $now) {
$format = $this->config->get('date_short', 'D H:i');
}
else {
$format = $this->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 ? ($this->gettext('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 .= $this->gettext(strtolower(date('D', $timestamp)));
}
// weekday long
else if ($format[$i] == 'l') {
$out .= $this->gettext(strtolower(date('l', $timestamp)));
}
// month name (short)
else if ($format[$i] == 'M') {
$out .= $this->gettext(strtolower(date('M', $timestamp)));
}
// month name (long)
else if ($format[$i] == 'F') {
$out .= $this->gettext('long'.strtolower(date('M', $timestamp)));
}
else if ($format[$i] == 'x') {
$out .= strftime('%x %X', $timestamp);
}
else {
$out .= date($format[$i], $timestamp);
}
}
if ($today) {
$label = $this->gettext('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 function folder_list($attrib)
{
static $a_mailboxes;
$attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
// 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 = $storage->get_folder();
// build the folders tree
if (empty($a_mailboxes)) {
// get mailbox list
$a_folders = $storage->list_folders_subscribed(
'', $attrib['folder_name'], $attrib['folder_filter']);
$delimiter = $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') {
$attrib['is_escaped'] = true;
$select = new html_select($attrib);
// add no-selection option
if ($attrib['noselection']) {
$select->add(html::quote($rcmail->gettext($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 folders list as html_select object
*
* @param array $p Named parameters
*
* @return html_select HTML drop-down object
*/
public function folder_selector($p = array())
{
$p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true);
$a_mailboxes = array();
$storage = $this->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'])) {
$this->build_folder_tree($a_mailboxes, $folder, $delimiter);
}
}
$select = new html_select($p);
if ($p['noselection']) {
$select->add(html::quote($p['noselection']), '');
}
$this->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
return $select;
}
/**
* Create a hierarchical array of the mailbox list
*/
public function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
{
// Handle namespace prefix
$prefix = '';
if (!$path) {
$n_folder = $folder;
$folder = $this->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)) {
$this->build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
}
/**
* Return html for a structured list &lt;ul&gt; for the mailbox tree
*/
public function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
{
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $this->storage->get_cache('messagecount');
$collapsed = $this->config->get('collapsed_folders');
$out = '';
foreach ($arrFolders as $key => $folder) {
$title = null;
$folder_class = $this->folder_classname($folder['id']);
$is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
$unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
if ($folder_class && !$realnames) {
$foldername = $this->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 = rcube_utils::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 = $this->JQ($folder['id']);
$html_name = $this->Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
$link_attrib = $folder['virtual'] ? array() : array(
'href' => $this->url(array('_mbox' => $folder['id'])),
- 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail::JS_OBJECT_NAME, $js_name),
+ 'onclick' => sprintf("return %s.command('list','%s',this)", rcmail_output::JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
);
$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' => ($is_collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute",
- 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail::JS_OBJECT_NAME, $js_name)
+ 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail_output::JS_OBJECT_NAME, $js_name)
), '&nbsp;') : ''));
$jslist[$folder_id] = array(
'id' => $folder['id'],
'name' => $foldername,
'virtual' => $folder['virtual']
);
if (!empty($folder['folders'])) {
$out .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
$this->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
*/
public function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
{
$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 = $this->storage->folder_attributes($folder['id']);
if ($attrs && in_array('\\Noinferiors', $attrs)) {
continue;
}
}
if (!$realnames && ($folder_class = $this->folder_classname($folder['id']))) {
$foldername = $this->gettext($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('&nbsp;', $nestLevel*4) . html::quote($foldername), $folder['id']);
if (!empty($folder['folders'])) {
$out .= $this->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
*/
public function folder_classname($folder_id)
{
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 === $this->config->get($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 function localize_foldername($name)
{
if ($folder_class = $this->folder_classname($name)) {
return $this->gettext($folder_class);
}
else {
return rcube_charset::convert($name, 'UTF7-IMAP');
}
}
public function localize_folderpath($path)
{
$protect_folders = $this->config->get('protect_default_folders');
$default_folders = (array) $this->config->get('default_folders');
$delimiter = $this->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[] = $this->localize_foldername($directory);
}
else {
$result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
}
}
return implode($delimiter, $result);
}
public static function quota_display($attrib)
{
$rcmail = rcmail::get_instance();
if (!$attrib['id']) {
$attrib['id'] = 'rcmquotadisplay';
}
$_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
$rcmail->output->add_gui_object('quotadisplay', $attrib['id']);
$quota = $rcmail->quota_content($attrib);
$rcmail->output->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
return html::span($attrib, '');
}
public function quota_content($attrib = null)
{
$quota = $this->storage->get_quota();
$quota = $this->plugins->exec_hook('quota', $quota);
$quota_result = (array) $quota;
$quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
if (!$quota['total'] && $this->config->get('quota_zero_as_unlimited')) {
$quota_result['title'] = $this->gettext('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%%)',
$this->show_bytes($quota['used'] * 1024), $this->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'] = $this->gettext('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 function display_server_error($fallback = null, $fallback_args = null)
{
$err_code = $this->storage->get_error_code();
$res_code = $this->storage->get_response_code();
if ($res_code == rcube_storage::NOPERM) {
$this->output->show_message('errornoperm', 'error');
}
else if ($res_code == rcube_storage::READONLY) {
$this->output->show_message('errorreadonly', 'error');
}
else if ($err_code && ($err_str = $this->storage->get_error_str())) {
// try to detect access rights problem and display appropriate message
if (stripos($err_str, 'Permission denied') !== false) {
$this->output->show_message('errornoperm', 'error');
}
else {
$this->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
}
}
else if ($err_code < 0) {
$this->output->show_message('storageerror', 'error');
}
else if ($fallback) {
$this->output->show_message($fallback, 'error', $fallback_args);
}
}
/**
* Output HTML editor scripts
*
* @param string $mode Editor mode
*/
public function html_editor($mode = '')
{
$hook = $this->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' => $this->output->get_skin_path(),
'spellcheck' => intval($this->config->get('enable_spellcheck')),
'spelldict' => intval($this->config->get('spellcheck_dictionary'))
));
$this->output->include_script('tiny_mce/tiny_mce.js');
$this->output->include_script('editor.js');
$this->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 function upload_progress()
{
$prefix = ini_get('apc.rfc1867_prefix');
$params = array(
'action' => $this->action,
'name' => rcube_utils::get_input_value('_progress', rcube_utils::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'] = $this->gettext(array('name' => 'uploadprogress', 'vars' => array(
'percent' => $params['percent'] . '%',
'current' => $this->show_bytes($params['current']),
'total' => $this->show_bytes($params['total'])
)));
$this->output->command('upload_progress_update', $params);
$this->output->send();
}
/**
* Initializes file uploading interface.
*/
public function upload_init()
{
// Enable upload progress bar
if (($seconds = $this->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
if ($field_name = ini_get('apc.rfc1867_name')) {
$this->output->set_env('upload_progress_name', $field_name);
$this->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;
}
$this->output->set_env('max_filesize', $max_filesize);
$max_filesize = self::show_bytes($max_filesize);
$this->output->set_env('filesizeerror', $this->gettext(array(
'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
return $max_filesize;
}
/**
* Initializes client-side autocompletion.
*/
public function autocomplete_init()
{
static $init;
if ($init) {
return;
}
$init = 1;
if (($threads = (int)$this->config->get('autocomplete_threads')) > 0) {
$book_types = (array) $this->config->get('autocomplete_addressbooks', 'sql');
if (count($book_types) > 1) {
$this->output->set_env('autocomplete_threads', $threads);
$this->output->set_env('autocomplete_sources', $book_types);
}
}
$this->output->set_env('autocomplete_max', (int)$this->config->get('autocomplete_max', 15));
$this->output->set_env('autocomplete_min_length', $this->config->get('autocomplete_min_length'));
$this->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 function show_bytes($bytes)
{
if ($bytes >= 1073741824) {
$gb = $bytes/1073741824;
$str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . $this->gettext('GB');
}
else if ($bytes >= 1048576) {
$mb = $bytes/1048576;
$str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . $this->gettext('MB');
}
else if ($bytes >= 1024) {
$str = sprintf("%d ", round($bytes/1024)) . $this->gettext('KB');
}
else {
$str = sprintf('%d ', $bytes) . $this->gettext('B');
}
return $str;
}
/**
* Returns real size (calculated) of the message part
*
* @param rcube_message_part Message part
*
* @return string Part size (and unit)
*/
public function message_part_size($part)
{
if (isset($part->d_parameters['size'])) {
$size = $this->show_bytes((int)$part->d_parameters['size']);
}
else {
$size = $part->size;
if ($part->encoding == 'base64') {
$size = $size / 1.33;
}
$size = '~' . $this->show_bytes($size);
}
return $size;
}
/************************************************************************
********* Deprecated methods (to be removed) *********
***********************************************************************/
public static function setcookie($name, $value, $exp = 0)
{
rcube_utils::setcookie($name, $value, $exp);
}
public function imap_connect()
{
return $this->storage_connect();
}
public function imap_init()
{
return $this->storage_init();
}
/**
* 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('storageerror', 'error');
}
}
else {
$this->set_storage_prop();
}
}
return $storage->is_connected();
}
}
diff --git a/program/include/rcmail_output.php b/program/include/rcmail_output.php
new file mode 100644
index 000000000..36512ad48
--- /dev/null
+++ b/program/include/rcmail_output.php
@@ -0,0 +1,120 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail_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> |
+ +-----------------------------------------------------------------------+
+*/
+
+/**
+ * Class for output generation
+ *
+ * @package Core
+ * @subpackage View
+ */
+abstract class rcmail_output extends rcube_output
+{
+ const JS_OBJECT_NAME = 'rcmail';
+
+ public $type = 'html';
+ public $ajax_call = false;
+ public $framed = false;
+
+ protected $pagetitle = '';
+ protected $object_handlers = array();
+
+
+ /**
+ * Object constructor
+ */
+ public function __construct($task = null, $framed = false)
+ {
+ parent::__construct();
+ }
+
+
+ /**
+ * Setter for page title
+ *
+ * @param string $title Page title
+ */
+ public function set_pagetitle($title)
+ {
+ $this->pagetitle = $title;
+ }
+
+
+ /**
+ * Getter for the current skin path property
+ */
+ public function get_skin_path()
+ {
+ return $this->config->get('skin_path');
+ }
+
+
+ /**
+ * Delete all stored env variables and commands
+ */
+ public function reset()
+ {
+ parent::reset();
+
+ $this->object_handlers = array();
+ $this->pagetitle = '';
+ }
+
+
+ /**
+ * Call a client method
+ *
+ * @param string Method to call
+ * @param ... Additional arguments
+ */
+ abstract function command();
+
+
+ /**
+ * Add a localized label to the client environment
+ */
+ abstract function add_label();
+
+
+ /**
+ * Register a template object handler
+ *
+ * @param string Object name
+ * @param string Function name to call
+ * @return void
+ */
+ public function add_handler($obj, $func)
+ {
+ $this->object_handlers[$obj] = $func;
+ }
+
+
+ /**
+ * Register a list of template object handlers
+ *
+ * @param array Hash array with object=>handler pairs
+ * @return void
+ */
+ public function add_handlers($arr)
+ {
+ $this->object_handlers = array_merge($this->object_handlers, $arr);
+ }
+
+}
diff --git a/program/lib/Roundcube/rcube_output_html.php b/program/include/rcmail_output_html.php
similarity index 98%
rename from program/lib/Roundcube/rcube_output_html.php
rename to program/include/rcmail_output_html.php
index f861ff95e..1290e173e 100644
--- a/program/lib/Roundcube/rcube_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -1,1775 +1,1775 @@
<?php
/*
+-----------------------------------------------------------------------+
- | program/include/rcubeoutput_html.php |
+ | program/include/rcmail_output_html.php |
| |
| This file is part of the Roundcube Webmail client |
| 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. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Class to handle HTML page output using a skin template. |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Class to create HTML page output using a skin template
*
- * @package Framework
+ * @package Core
* @subpackage View
*/
-class rcube_output_html extends rcube_output
+class rcmail_output_html extends rcmail_output
{
public $type = 'html';
protected $message = null;
protected $js_env = array();
protected $js_labels = array();
protected $js_commands = array();
protected $skin_paths = array();
protected $template_name;
protected $scripts_path = '';
protected $script_files = array();
protected $css_files = array();
protected $scripts = array();
protected $default_template = "<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
protected $deprecated_templates = array(
'contact' => 'showcontact',
'contactadd' => 'addcontact',
'contactedit' => 'editcontact',
'identityedit' => 'editidentity',
'messageprint' => 'printmessage',
);
/**
* Constructor
*
* @todo Replace $this->config with the real rcube_config object
*/
public function __construct($task = null, $framed = false)
{
parent::__construct();
//$this->framed = $framed;
$this->set_env('task', $task);
$this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
// add cookie info
$this->set_env('cookie_domain', ini_get('session.cookie_domain'));
$this->set_env('cookie_path', ini_get('session.cookie_path'));
$this->set_env('cookie_secure', ini_get('session.cookie_secure'));
// load the correct skin (in case user-defined)
$skin = $this->config->get('skin');
$this->set_skin($skin);
$this->set_env('skin', $skin);
if (!empty($_REQUEST['_extwin']))
$this->set_env('extwin', 1);
// add common javascripts
- $this->add_script('var '.rcmail::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
+ $this->add_script('var '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
// don't wait for page onload. Call init at the bottom of the page (delayed)
- $this->add_script(rcmail::JS_OBJECT_NAME.'.init();', 'docready');
+ $this->add_script(self::JS_OBJECT_NAME.'.init();', 'docready');
$this->scripts_path = 'program/js/';
$this->include_script('jquery.min.js');
$this->include_script('common.js');
$this->include_script('app.js');
// register common UI objects
$this->add_handlers(array(
'loginform' => array($this, 'login_form'),
'preloader' => array($this, 'preloader'),
'username' => array($this, 'current_username'),
'message' => array($this, 'message_container'),
'charsetselector' => array($this, 'charset_selector'),
'aboutcontent' => array($this, 'about_content'),
));
}
/**
* Set environment variable
*
* @param string Property name
* @param mixed Property value
* @param boolean True if this property should be added to client environment
*/
public function set_env($name, $value, $addtojs = true)
{
$this->env[$name] = $value;
if ($addtojs || isset($this->js_env[$name])) {
$this->js_env[$name] = $value;
}
}
/**
* Getter for the current page title
*
* @return string The page title
*/
protected function get_pagetitle()
{
if (!empty($this->pagetitle)) {
$title = $this->pagetitle;
}
else if ($this->env['task'] == 'login') {
$title = $this->app->gettext(array(
'name' => 'welcome',
'vars' => array('product' => $this->config->get('product_name')
)));
}
else {
$title = ucfirst($this->env['task']);
}
return $title;
}
/**
* Set skin
*/
public function set_skin($skin)
{
$valid = false;
if (!empty($skin) && is_dir('skins/'.$skin) && is_readable('skins/'.$skin)) {
$skin_path = 'skins/'.$skin;
$valid = true;
}
else {
$skin_path = $this->config->get('skin_path');
if (!$skin_path) {
$skin_path = 'skins/' . rcube_config::DEFAULT_SKIN;
}
$valid = !$skin;
}
$this->config->set('skin_path', $skin_path);
// register skin path(s)
$this->skin_paths = array();
$this->load_skin($skin_path);
return $valid;
}
/**
* Helper method to recursively read skin meta files and register search paths
*/
private function load_skin($skin_path)
{
$this->skin_paths[] = $skin_path;
// read meta file and check for dependecies
$meta = @json_decode(@file_get_contents($skin_path.'/meta.json'), true);
if ($meta['extends'] && is_dir('skins/' . $meta['extends'])) {
$this->load_skin('skins/' . $meta['extends']);
}
}
/**
* Check if a specific template exists
*
* @param string Template name
* @return boolean True if template exists
*/
public function template_exists($name)
{
$found = false;
foreach ($this->skin_paths as $skin_path) {
$filename = $skin_path . '/templates/' . $name . '.html';
$found = (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
if ($found)
break;
}
return $found;
}
/**
* Find the given file in the current skin path stack
*
* @param string File name/path to resolve (starting with /)
* @param string Reference to the base path of the matching skin
* @param string Additional path to search in
* @return mixed Relative path to the requested file or False if not found
*/
public function get_skin_file($file, &$skin_path, $add_path = null)
{
$skin_paths = $this->skin_paths;
if ($add_path)
array_unshift($skin_paths, $add_path);
foreach ($skin_paths as $skin_path) {
$path = realpath($skin_path . $file);
if (is_file($path)) {
return $skin_path . $file;
}
}
return false;
}
/**
* Register a GUI object to the client script
*
* @param string Object name
* @param string Object ID
* @return void
*/
public function add_gui_object($obj, $id)
{
- $this->add_script(rcmail::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
+ $this->add_script(self::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
}
/**
* Call a client method
*
* @param string Method to call
* @param ... Additional arguments
*/
public function command()
{
$cmd = func_get_args();
if (strpos($cmd[0], 'plugin.') !== false)
$this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
else
$this->js_commands[] = $cmd;
}
/**
* Add a localized label to the client environment
*/
public function add_label()
{
$args = func_get_args();
if (count($args) == 1 && is_array($args[0]))
$args = $args[0];
foreach ($args as $name) {
$this->js_labels[$name] = $this->app->gettext($name);
}
}
/**
* Invoke display_message command
*
* @param string $message Message to display
* @param string $type Message type [notice|confirm|error]
* @param array $vars Key-value pairs to be replaced in localized text
* @param boolean $override Override last set message
* @param int $timeout Message display time in seconds
* @uses self::command()
*/
public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
if (!empty($vars))
$vars = array_map('Q', $vars);
$msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
}
else
$msgtext = $message;
$this->message = $message;
$this->command('display_message', $msgtext, $type, $timeout * 1000);
}
}
/**
* Delete all stored env variables and commands
*/
public function reset()
{
parent::reset();
$this->js_env = array();
$this->js_labels = array();
$this->js_commands = array();
$this->script_files = array();
$this->scripts = array();
$this->header = '';
$this->footer = '';
$this->body = '';
}
/**
* Redirect to a certain url
*
* @param mixed $p Either a string with the action or url parameters as key-value pairs
* @param int $delay Delay in seconds
*/
public function redirect($p = array(), $delay = 1)
{
if ($this->env['extwin'])
$p['extwin'] = 1;
$location = $this->app->url($p);
header('Location: ' . $location);
exit;
}
/**
* Send the request output to the client.
* This will either parse a skin tempalte or send an AJAX response
*
* @param string Template name
* @param boolean True if script should terminate (default)
*/
public function send($templ = null, $exit = true)
{
if ($templ != 'iframe') {
// prevent from endless loops
if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
rcube::raise_error(array('code' => 505, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => 'Recursion alert: ignoring output->send()'), true, false);
return;
}
$this->parse($templ, false);
}
else {
$this->framed = $templ == 'iframe' ? true : $this->framed;
$this->write();
}
// set output asap
ob_flush();
flush();
if ($exit) {
exit;
}
}
/**
* Process template and write to stdOut
*
* @param string $template HTML template content
*/
public function write($template = '')
{
// unlock interface after iframe load
$unlock = preg_replace('/[^a-z0-9]/i', '', $_REQUEST['_unlock']);
if ($this->framed) {
array_unshift($this->js_commands, array('set_busy', false, null, $unlock));
}
else if ($unlock) {
array_unshift($this->js_commands, array('hide_message', $unlock));
}
if (!empty($this->script_files))
$this->set_env('request_token', $this->app->get_request_token());
// write all env variables to client
$js = $this->framed ? "if(window.parent) {\n" : '';
$js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
$this->add_script($js, 'head_top');
// send clickjacking protection headers
$iframe = $this->framed || !empty($_REQUEST['_framed']);
if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')))
header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
// call super method
$this->_write($template, $this->config->get('skin_path'));
}
/**
* Parse a specific skin template and deliver to stdout (or return)
*
* @param string Template name
* @param boolean Exit script
* @param boolean Don't write to stdout, return parsed content instead
*
* @link http://php.net/manual/en/function.exit.php
*/
function parse($name = 'main', $exit = true, $write = true)
{
$plugin = false;
$realname = $name;
$this->template_name = $realname;
$temp = explode('.', $name, 2);
if (count($temp) > 1) {
$plugin = $temp[0];
$name = $temp[1];
$skin_dir = $plugin . '/skins/' . $this->config->get('skin');
// apply skin search escalation list to plugin directory
$plugin_skin_paths = array();
foreach ($this->skin_paths as $skin_path) {
$plugin_skin_paths[] = $this->app->plugins->url . $plugin . '/' . $skin_path;
}
// add fallback to default skin
if (is_dir($this->app->plugins->dir . $plugin . '/skins/default')) {
$skin_dir = $plugin . '/skins/default';
$plugin_skin_paths[] = $this->app->plugins->url . $skin_dir;
}
// add plugin skin paths to search list
$this->skin_paths = array_merge($plugin_skin_paths, $this->skin_paths);
}
// find skin template
$path = false;
foreach ($this->skin_paths as $skin_path) {
$path = "$skin_path/templates/$name.html";
// fallback to deprecated template names
if (!is_readable($path) && $this->deprecated_templates[$realname]) {
$path = "$skin_path/templates/" . $this->deprecated_templates[$realname] . ".html";
if (is_readable($path)) {
rcube::raise_error(array(
'code' => 502, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Using deprecated template '" . $this->deprecated_templates[$realname]
. "' in $skin_path/templates. Please rename to '$realname'"),
true, false);
}
}
if (is_readable($path)) {
$this->config->set('skin_path', $skin_path);
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
break;
}
else {
$path = false;
}
}
// read template file
if (!$path || ($templ = @file_get_contents($path)) === false) {
rcube::raise_error(array(
'code' => 501,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => 'Error loading template for '.$realname
), true, $write);
return false;
}
// replace all path references to plugins/... with the configured plugins dir
// and /this/ to the current plugin skin directory
if ($plugin) {
$templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ);
}
// parse for specialtags
$output = $this->parse_conditions($templ);
$output = $this->parse_xml($output);
// trigger generic hook where plugins can put additional content to the page
$hook = $this->app->plugins->exec_hook("render_page", array('template' => $realname, 'content' => $output));
// save some memory
$output = $hook['content'];
unset($hook['content']);
// make sure all <form> tags have a valid request token
$output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
$this->footer = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $this->footer);
if ($write) {
// add debug console
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>'
);
$this->add_script(
"if (!window.console || !window.console.log) {\n".
" window.console = new rcube_console();\n".
" $('#console').show();\n".
"}", 'foot');
}
$this->write(trim($output));
}
else {
return $output;
}
if ($exit) {
exit;
}
}
/**
* Return executable javascript code for all registered commands
*
* @return string $out
*/
protected function get_js_commands()
{
$out = '';
if (!$this->framed && !empty($this->js_env)) {
- $out .= rcmail::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
+ $out .= self::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
}
if (!empty($this->js_labels)) {
$this->command('add_label', $this->js_labels);
}
foreach ($this->js_commands as $i => $args) {
$method = array_shift($args);
foreach ($args as $i => $arg) {
$args[$i] = self::json_serialize($arg);
}
$parent = $this->framed || preg_match('/^parent\./', $method);
$out .= sprintf(
"%s.%s(%s);\n",
- ($parent ? 'if(window.parent && parent.'.rcmail::JS_OBJECT_NAME.') parent.' : '') . rcmail::JS_OBJECT_NAME,
+ ($parent ? 'if(window.parent && parent.'.self::JS_OBJECT_NAME.') parent.' : '') . self::JS_OBJECT_NAME,
preg_replace('/^parent\./', '', $method),
implode(',', $args)
);
}
return $out;
}
/**
* Make URLs starting with a slash point to skin directory
*
* @param string Input string
* @param boolean True if URL should be resolved using the current skin path stack
* @return string
*/
public function abs_url($str, $search_path = false)
{
if ($str[0] == '/') {
if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path)))
return $file_url;
return $this->base_path . $str;
}
else
return $str;
}
/**
* 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 RCUBE_INSTALL_PATH . 'program/steps/utils/error.inc';
exit;
}
/***** Template parsing methods *****/
/**
* Replace all strings ($varname)
* with the content of the according global variable.
*/
protected function parse_with_globals($input)
{
$GLOBALS['__version'] = html::quote(RCMAIL_VERSION);
$GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
$GLOBALS['__skin_path'] = html::quote($this->base_path);
return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
array($this, 'globals_callback'), $input);
}
/**
* Callback funtion for preg_replace_callback() in parse_with_globals()
*/
protected function globals_callback($matches)
{
return $GLOBALS[$matches[1]];
}
/**
* Correct absolute paths in images and other tags
* add timestamp to .js and .css filename
*/
protected function fix_paths($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
array($this, 'file_callback'), $output);
}
/**
* Callback function for preg_replace_callback in write()
*
* @return string Parsed string
*/
protected function file_callback($matches)
{
$file = $matches[3];
// correct absolute paths
if ($file[0] == '/') {
$file = $this->base_path . $file;
}
// add file modification timestamp
if (preg_match('/\.(js|css)$/', $file)) {
if ($fs = @filemtime($file)) {
$file .= '?s=' . $fs;
}
}
return $matches[1] . '=' . $matches[2] . $file . $matches[4];
}
/**
* Public wrapper to dipp into template parsing.
*
* @param string $input
* @return string
* @uses rcube_output_html::parse_xml()
* @since 0.1-rc1
*/
public function just_parse($input)
{
return $this->parse_xml($input);
}
/**
* Parse for conditional tags
*
* @param string $input
* @return string
*/
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 = 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);
if ($condmet) {
$result = $submatches[0];
$result.= ($submatches[1] != 'endif' ? preg_replace('/.*<roundcube:endif\s+[^>]+>\n?/Uis', '', $submatches[3], 1) : $submatches[3]);
}
else {
$result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
}
return $matches[0] . $this->parse_conditions($result);
}
rcube::raise_error(array(
'code' => 500,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Unable to parse conditional tag " . $matches[2]
), true, false);
}
return $input;
}
/**
* Determines if a given condition is met
*
* @todo Get rid off eval() once I understand what this does.
* @todo Extend this to allow real conditions, not just "set"
* @param string Condition statement
* @return boolean True if condition is met, False if not
*/
protected function check_condition($condition)
{
return eval("return (".$this->parse_expression($condition).");");
}
/**
* Inserts hidden field with CSRF-prevention-token into POST forms
*/
protected function alter_form_tag($matches)
{
$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()));
$out .= "\n" . $hidden->show();
}
return $out;
}
/**
* Parses expression and replaces variables
*
* @param string Expression statement
* @return string Expression value
*/
protected function parse_expression($expression)
{
return preg_replace(
array(
'/session:([a-z0-9_]+)/i',
'/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i',
'/env:([a-z0-9_]+)/i',
'/request:([a-z0-9_]+)/i',
'/cookie:([a-z0-9_]+)/i',
'/browser:([a-z0-9_]+)/i',
'/template:name/i',
),
array(
"\$_SESSION['\\1']",
"\$this->app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
"\$this->env['\\1']",
"rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
"\$_COOKIE['\\1']",
"\$this->browser->{'\\1'}",
$this->template_name,
),
$expression);
}
/**
* Search for special tags in input and replace them
* with the appropriate content
*
* @param string Input string to parse
* @return string Altered input string
* @todo Use DOM-parser to traverse template HTML
* @todo Maybe a cache.
*/
protected function parse_xml($input)
{
return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input);
}
/**
* Callback function for parsing an xml command tag
* and turn it into real html content
*
* @param array Matches array of preg_replace_callback
* @return string Tag/Object content
*/
protected function xml_command($matches)
{
$command = strtolower($matches[1]);
$attrib = html::parse_attrib_string($matches[2]);
// empty output if required condition is not met
if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
return '';
}
// execute command
switch ($command) {
// return a button
case 'button':
if ($attrib['name'] || $attrib['command']) {
return $this->button($attrib);
}
break;
// frame
case 'frame':
return $this->frame($attrib);
break;
// show a label
case 'label':
if ($attrib['expression'])
$attrib['name'] = eval("return " . $this->parse_expression($attrib['expression']) .";");
if ($attrib['name'] || $attrib['command']) {
// @FIXME: 'noshow' is useless, remove?
if ($attrib['noshow']) {
return '';
}
$vars = $attrib + array('product' => $this->config->get('product_name'));
unset($vars['name'], $vars['command']);
$label = $this->app->gettext($attrib + array('vars' => $vars));
$quoting = !empty($attrib['quoting']) ? strtolower($attrib['quoting']) : (rcube_utils::get_boolean((string)$attrib['html']) ? 'no' : '');
switch ($quoting) {
case 'no':
case 'raw':
break;
case 'javascript':
case 'js':
$label = rcube::JQ($label);
break;
default:
$label = html::quote($label);
break;
}
return $label;
}
break;
// include a file
case 'include':
$old_base_path = $this->base_path;
if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attrib['skinpath'])) {
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path); // set base_path to core skin directory (not plugin's skin)
$path = realpath($path);
}
if (is_readable($path)) {
if ($this->config->get('skin_include_php')) {
$incl = $this->include_php($path);
}
else {
$incl = file_get_contents($path);
}
$incl = $this->parse_conditions($incl);
$incl = $this->parse_xml($incl);
$incl = $this->fix_paths($incl);
$this->base_path = $old_base_path;
return $incl;
}
break;
case 'plugin.include':
$hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
return $hook['content'];
// define a container block
case 'container':
if ($attrib['name'] && $attrib['id']) {
$this->command('gui_container', $attrib['name'], $attrib['id']);
// let plugins insert some content here
$hook = $this->app->plugins->exec_hook("template_container", $attrib);
return $hook['content'];
}
break;
// return code for a specific application object
case 'object':
$object = strtolower($attrib['name']);
$content = '';
// we are calling a class/method
if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
(is_string($handler[0]) && class_exists($handler[0])))
$content = call_user_func($handler, $attrib);
}
// execute object handler function
else if (function_exists($handler)) {
$content = call_user_func($handler, $attrib);
}
else if ($object == 'doctype') {
$content = html::doctype($attrib['value']);
}
else if ($object == 'logo') {
$attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
if ($logo = $this->config->get('skin_logo'))
$attrib['src'] = $logo;
$content = html::img($attrib);
}
else if ($object == 'productname') {
$name = $this->config->get('product_name', 'Roundcube Webmail');
$content = html::quote($name);
}
else if ($object == 'version') {
$ver = (string)RCMAIL_VERSION;
if (is_file(RCUBE_INSTALL_PATH . '.svn/entries')) {
if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
$ver .= ' [SVN r'.$regs[1].']';
}
else if (is_file(RCUBE_INSTALL_PATH . '.git/index')) {
if (preg_match('/Date:\s+([^\n]+)/', @shell_exec('git log -1'), $regs)) {
if ($date = date('Ymd.Hi', strtotime($regs[1]))) {
$ver .= ' [GIT '.$date.']';
}
}
}
$content = html::quote($ver);
}
else if ($object == 'steptitle') {
$content = html::quote($this->get_pagetitle());
}
else if ($object == 'pagetitle') {
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 .= $this->get_pagetitle();
$content = html::quote($title);
}
// exec plugin hooks for this template object
$hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
return $hook['content'];
// return code for a specified eval expression
case 'exp':
$value = $this->parse_expression($attrib['expression']);
return eval("return html::quote($value);");
// return variable
case 'var':
$var = explode(':', $attrib['name']);
$name = $var[1];
$value = '';
switch ($var[0]) {
case 'env':
$value = $this->env[$name];
break;
case 'config':
$value = $this->config->get($name);
if (is_array($value) && $value[$_SESSION['storage_host']]) {
$value = $value[$_SESSION['storage_host']];
}
break;
case 'request':
$value = rcube_utils::get_input_value($name, rcube_utils::INPUT_GPC);
break;
case 'session':
$value = $_SESSION[$name];
break;
case 'cookie':
$value = htmlspecialchars($_COOKIE[$name]);
break;
case 'browser':
$value = $this->browser->{$name};
break;
}
if (is_array($value)) {
$value = implode(', ', $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
*/
protected function include_php($file)
{
ob_start();
include $file;
$out = ob_get_contents();
ob_end_clean();
return $out;
}
/**
* Create and register a button
*
* @param array Named button attributes
* @return string HTML button
* @todo Remove all inline JS calls and use jQuery instead.
* @todo Remove all sprintf()'s - they are pretty, but also slow.
*/
public function button($attrib)
{
static $s_button_count = 100;
// these commands can be called directly via url
$a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
if (!($attrib['command'] || $attrib['name'])) {
return '';
}
// try to find out the button type
if ($attrib['type']) {
$attrib['type'] = strtolower($attrib['type']);
}
else {
$attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
}
$command = $attrib['command'];
if ($attrib['task'])
$command = $attrib['task'] . '.' . $command;
if (!$attrib['image']) {
$attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
}
if (!$attrib['id']) {
$attrib['id'] = sprintf('rcmbtn%d', $s_button_count++);
}
// get localized text for labels and titles
if ($attrib['title']) {
$attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain']));
}
if ($attrib['label']) {
$attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain']));
}
if ($attrib['alt']) {
$attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain']));
}
// set title to alt attribute for IE browsers
if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) {
$attrib['title'] = $attrib['alt'];
}
// add empty alt attribute for XHTML compatibility
if (!isset($attrib['alt'])) {
$attrib['alt'] = '';
}
// register button in the system
if ($attrib['command']) {
$this->add_script(sprintf(
"%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
- rcmail::JS_OBJECT_NAME,
+ self::JS_OBJECT_NAME,
$command,
$attrib['id'],
$attrib['type'],
$attrib['imageact'] ? $this->abs_url($attrib['imageact']) : $attrib['classact'],
$attrib['imagesel'] ? $this->abs_url($attrib['imagesel']) : $attrib['classsel'],
$attrib['imageover'] ? $this->abs_url($attrib['imageover']) : ''
));
// make valid href to specific buttons
if (in_array($attrib['command'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('task' => $attrib['command']));
- $attrib['onclick'] = sprintf("return %s.command('switch-task','%s',this,event)", rcmail::JS_OBJECT_NAME, $attrib['command']);
+ $attrib['onclick'] = sprintf("return %s.command('switch-task','%s',this,event)", self::JS_OBJECT_NAME, $attrib['command']);
}
else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
}
else if (in_array($attrib['command'], $a_static_commands)) {
$attrib['href'] = $this->app->url(array('action' => $attrib['command']));
}
else if (($attrib['command'] == 'permaurl' || $attrib['command'] == 'extwin') && !empty($this->env['permaurl'])) {
$attrib['href'] = $this->env['permaurl'];
}
}
// overwrite attributes
if (!$attrib['href']) {
$attrib['href'] = '#';
}
if ($attrib['task']) {
if ($attrib['classact'])
$attrib['class'] = $attrib['classact'];
}
else if ($command && !$attrib['onclick']) {
$attrib['onclick'] = sprintf(
"return %s.command('%s','%s',this,event)",
- rcmail::JS_OBJECT_NAME,
+ self::JS_OBJECT_NAME,
$command,
$attrib['prop']
);
}
$out = '';
// generate image tag
if ($attrib['type'] == 'image') {
$attrib_str = html::attrib_string(
$attrib,
array(
'style', 'class', 'id', 'width', 'height', 'border', 'hspace',
'vspace', 'align', 'alt', 'tabindex', 'title'
)
);
$btn_content = sprintf('<img src="%s"%s />', $this->abs_url($attrib['image']), $attrib_str);
if ($attrib['label']) {
$btn_content .= ' '.$attrib['label'];
}
$link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
}
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') {
$attrib['type'] = 'button';
if ($attrib['label']) {
$attrib['value'] = $attrib['label'];
}
if ($attrib['command']) {
$attrib['disabled'] = 'disabled';
}
$out = html::tag('input', $attrib, null, array('type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex', 'disabled'));
}
// generate html code for button
if ($btn_content) {
$attrib_str = html::attrib_string($attrib, $link_attrib);
$out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
}
return $out;
}
/**
* 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);
}
$output = $this->parse_with_globals($this->fix_paths($output));
// trigger hook with final HTML content to be sent
$hook = $this->app->plugins->exec_hook("send_page", array('content' => $output));
if (!$hook['abort']) {
if ($this->charset != RCUBE_CHARSET) {
echo rcube_charset::convert($hook['content'], RCUBE_CHARSET, $this->charset);
}
else {
echo $hook['content'];
}
}
}
/**
* Returns iframe object, registers some related env variables
*
* @param array $attrib HTML attributes
* @param boolean $is_contentframe Register this iframe as the 'contentframe' gui object
* @return string IFRAME element
*/
public function frame($attrib, $is_contentframe = false)
{
static $idcount = 0;
if (!$attrib['id']) {
$attrib['id'] = 'rcmframe' . ++$idcount;
}
$attrib['name'] = $attrib['id'];
$attrib['src'] = $attrib['src'] ? $this->abs_url($attrib['src'], true) : 'program/resources/blank.gif';
// register as 'contentframe' object
if ($is_contentframe || $attrib['contentframe']) {
$this->set_env('contentframe', $attrib['contentframe'] ? $attrib['contentframe'] : $attrib['name']);
$this->set_env('blankpage', $attrib['src']);
}
return html::iframe($attrib);
}
/* ************* common functions delivering gui objects ************** */
/**
* Create a form tag with the necessary hidden fields
*
* @param array Named tag parameters
* @return string HTML code for the form
*/
public function form_tag($attrib, $content = null)
{
if ($this->framed || !empty($_REQUEST['_framed'])) {
$hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$hidden = $hiddenfield->show();
}
if ($this->env['extwin']) {
$hiddenfield = new html_hiddenfield(array('name' => '_extwin', 'value' => '1'));
$hidden = $hiddenfield->show();
}
if (!$content)
$attrib['noclose'] = true;
return html::tag('form',
$attrib + array('action' => "./", 'method' => "get"),
$hidden . $content,
array('id','class','style','name','method','action','enctype','onsubmit'));
}
/**
* Build a form tag with a unique request token
*
* @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields
* @param string Form content
* @return string HTML code for the form
*/
public function request_form($attrib, $content = '')
{
$hidden = new html_hiddenfield();
if ($attrib['task']) {
$hidden->add(array('name' => '_task', 'value' => $attrib['task']));
}
if ($attrib['action']) {
$hidden->add(array('name' => '_action', 'value' => $attrib['action']));
}
unset($attrib['task'], $attrib['request']);
$attrib['action'] = './';
// we already have a <form> tag
if ($attrib['form']) {
if ($this->framed || !empty($_REQUEST['_framed']))
$hidden->add(array('name' => '_framed', 'value' => '1'));
return $hidden->show() . $content;
}
else
return $this->form_tag($attrib, $hidden->show() . $content);
}
/**
* GUI object 'username'
* Showing IMAP username of the current session
*
* @param array Named tag parameters (currently not used)
* @return string HTML code for the gui object
*/
public function current_username($attrib)
{
static $username;
// alread fetched
if (!empty($username)) {
return $username;
}
// Current username is an e-mail address
if (strpos($_SESSION['username'], '@')) {
$username = $_SESSION['username'];
}
// get e-mail address from default identity
else if ($sql_arr = $this->app->user->get_identity()) {
$username = $sql_arr['email'];
}
else {
$username = $this->app->user->get_username();
}
return rcube_utils::idn_to_utf8($username);
}
/**
* GUI object 'loginform'
* Returns code for the webmail login form
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
protected function login_form($attrib)
{
$default_host = $this->config->get('default_host');
$autocomplete = (int) $this->config->get('login_autocomplete');
$_SESSION['temp'] = true;
// save original url
$url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST);
if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
$url = $_SERVER['QUERY_STRING'];
// Disable autocapitalization on iPad/iPhone (#1488609)
$attrib['autocapitalize'] = 'off';
// set atocomplete attribute
$user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
$host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
$pass_attrib = $autocomplete > 1 ? array() : array('autocomplete' => 'off');
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
$input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser')
+ $attrib + $user_attrib);
$input_pass = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd')
+ $attrib + $pass_attrib);
$input_host = null;
if (is_array($default_host) && count($default_host) > 1) {
$input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost'));
foreach ($default_host as $key => $value) {
if (!is_array($value)) {
$input_host->add($value, (is_numeric($key) ? $value : $key));
}
else {
$input_host = null;
break;
}
}
}
else if (is_array($default_host) && ($host = array_pop($default_host))) {
$hide_host = true;
$input_host = new html_hiddenfield(array(
'name' => '_host', 'id' => 'rcmloginhost', 'value' => $host) + $attrib);
}
else if (empty($default_host)) {
$input_host = new html_inputfield(array('name' => '_host', 'id' => 'rcmloginhost')
+ $attrib + $host_attrib);
}
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
$this->add_gui_object('loginform', $form_name);
// create HTML table with two cols
$table = new html_table(array('cols' => 2));
$table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username'))));
$table->add('input', $input_user->show(rcube_utils::get_input_value('_user', rcube_utils::INPUT_GPC)));
$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', html::quote($this->app->gettext('server'))));
$table->add('input', $input_host->show(rcube_utils::get_input_value('_host', rcube_utils::INPUT_GPC)));
}
$out = $input_task->show();
$out .= $input_action->show();
$out .= $input_tzone->show();
$out .= $input_url->show();
$out .= $table->show();
if ($hide_host) {
$out .= $input_host->show();
}
// surround html output with a form tag
if (empty($attrib['form'])) {
$out = $this->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
}
// include script for timezone detection
$this->include_script('jstz.min.js');
return $out;
}
/**
* GUI object 'preloader'
* Loads javascript code for images preloading
*
* @param array Named parameters
* @return void
*/
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);
if (empty($images) || $this->app->task == 'logout')
return;
$this->add_script('var images = ' . self::json_serialize($images) .';
for (var i=0; i<images.length; i++) {
img = new Image();
img.src = images[i];
}', 'docready');
}
/**
* GUI object 'searchform'
* Returns code for search function
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
protected function search_form($attrib)
{
// add some labels to client
$this->add_label('searching');
$attrib['name'] = '_q';
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmqsearchbox';
}
if ($attrib['type'] == 'search' && !$this->browser->khtml) {
unset($attrib['type'], $attrib['results']);
}
$input_q = new html_inputfield($attrib);
$out = $input_q->show();
$this->add_gui_object('qsearchbox', $attrib['id']);
// add form tag around text field
if (empty($attrib['form'])) {
$out = $this->form_tag(array(
'name' => "rcmqsearchform",
- 'onsubmit' => rcmail::JS_OBJECT_NAME . ".command('search'); return false",
+ 'onsubmit' => self::JS_OBJECT_NAME . ".command('search'); return false",
'style' => "display:inline"),
$out);
}
return $out;
}
/**
* Builder for GUI object 'message'
*
* @param array Named tag parameters
* @return string HTML code for the gui object
*/
protected function message_container($attrib)
{
if (isset($attrib['id']) === false) {
$attrib['id'] = 'rcmMessageContainer';
}
$this->add_gui_object('message', $attrib['id']);
return html::div($attrib, '');
}
/**
* GUI object 'charsetselector'
*
* @param array Named parameters for the select tag
* @return string HTML code for the gui object
*/
public function charset_selector($attrib)
{
// pass the following attributes to the form class
$field_attrib = array('name' => '_charset');
foreach ($attrib as $attr => $value) {
if (in_array($attr, array('id', 'name', 'class', 'style', 'size', 'tabindex'))) {
$field_attrib[$attr] = $value;
}
}
$charsets = array(
'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'])) {
$set = $_POST['_charset'];
}
else if (!empty($attrib['selected'])) {
$set = $attrib['selected'];
}
else {
$set = $this->get_charset();
}
$set = strtoupper($set);
if (!isset($charsets[$set])) {
$charsets[$set] = $set;
}
$select = new html_select($field_attrib);
$select->add(array_values($charsets), array_keys($charsets));
return $select->show($set);
}
/**
* Include content from config/about.<LANG>.html if available
*/
protected function about_content($attrib)
{
$content = '';
$filenames = array(
'about.' . $_SESSION['language'] . '.html',
'about.' . substr($_SESSION['language'], 0, 2) . '.html',
'about.html',
);
foreach ($filenames as $file) {
$fn = RCUBE_CONFIG_DIR . $file;
if (is_readable($fn)) {
$content = file_get_contents($fn);
$content = $this->parse_conditions($content);
$content = $this->parse_xml($content);
break;
}
}
return $content;
}
}
diff --git a/program/lib/Roundcube/rcube_output_json.php b/program/include/rcmail_output_json.php
similarity index 97%
rename from program/lib/Roundcube/rcube_output_json.php
rename to program/include/rcmail_output_json.php
index fcd52c789..22edce9bd 100644
--- a/program/lib/Roundcube/rcube_output_json.php
+++ b/program/include/rcmail_output_json.php
@@ -1,258 +1,258 @@
<?php
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_output_json.php |
+ | program/include/rc,aiö_output_json.php |
| |
| This file is part of the Roundcube Webmail client |
| 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 JSON (AJAX) output |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* View class to produce JSON responses
*
- * @package Framework
+ * @package Core
* @subpackage View
*/
-class rcube_output_json extends rcube_output
+class rcmail_output_json extends rcmail_output
{
protected $texts = array();
protected $commands = array();
protected $callbacks = array();
protected $message = null;
public $type = 'js';
public $ajax_call = true;
/**
* Issue command to set page title
*
* @param string $title New page title
*/
public function set_pagetitle($title)
{
if ($this->config->get('devel_mode') && !empty($_SESSION['username']))
$name = $_SESSION['username'];
else
$name = $this->config->get('product_name');
$this->command('set_pagetitle', empty($name) ? $title : $name.' :: '.$title);
}
/**
* Register a template object handler
*
* @param string $obj Object name
* @param string $func Function name to call
*/
public function add_handler($obj, $func)
{
// ignore
}
/**
* Register a list of template object handlers
*
* @param array $arr Hash array with object=>handler pairs
*/
public function add_handlers($arr)
{
// ignore
}
/**
* Call a client method
*
* @param string Method to call
* @param ... Additional arguments
*/
public function command()
{
$cmd = func_get_args();
if (strpos($cmd[0], 'plugin.') === 0)
$this->callbacks[] = $cmd;
else
$this->commands[] = $cmd;
}
/**
* Add a localized label to the client environment
*/
public function add_label()
{
$args = func_get_args();
if (count($args) == 1 && is_array($args[0]))
$args = $args[0];
foreach ($args as $name) {
$this->texts[$name] = $this->app->gettext($name);
}
}
/**
* Invoke display_message command
*
* @param string $message Message to display
* @param string $type Message type [notice|confirm|error]
* @param array $vars Key-value pairs to be replaced in localized text
* @param boolean $override Override last set message
* @param int $timeout Message displaying time in seconds
* @uses self::command()
*/
public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
if (!empty($vars)) {
$vars = array_map(array('rcmail', 'Q'), $vars);
}
$msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
}
else
$msgtext = $message;
$this->message = $message;
$this->command('display_message', $msgtext, $type, $timeout * 1000);
}
}
/**
* Delete all stored env variables and commands
*/
public function reset()
{
parent::reset();
$this->texts = array();
$this->commands = array();
}
/**
* 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
* @see rcmail::url()
*/
public function redirect($p = array(), $delay = 1)
{
$location = $this->app->url($p);
$this->remote_response(sprintf("window.setTimeout(function(){ %s.redirect('%s',true); }, %d);",
- rcmail::JS_OBJECT_NAME, $location, $delay));
+ self::JS_OBJECT_NAME, $location, $delay));
exit;
}
/**
* Send an AJAX response to the client.
*/
public function send()
{
$this->remote_response();
exit;
}
/**
* 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
* @param boolean True if output buffer should be flushed
* @return void
* @deprecated
*/
protected function remote_response($add='')
{
static $s_header_sent = false;
if (!$s_header_sent) {
$s_header_sent = true;
$this->nocacheing_headers();
header('Content-Type: text/plain; charset=' . $this->get_charset());
}
// unset default env vars
unset($this->env['task'], $this->env['action'], $this->env['comm_path']);
$rcmail = rcmail::get_instance();
$response['action'] = $rcmail->action;
if ($unlock = rcube_utils::get_input_value('_unlock', rcube_utils::INPUT_GPC)) {
$response['unlock'] = $unlock;
}
if (!empty($this->env))
$response['env'] = $this->env;
if (!empty($this->texts))
$response['texts'] = $this->texts;
// send function calls
$response['exec'] = $this->get_js_commands() . $add;
if (!empty($this->callbacks))
$response['callbacks'] = $this->callbacks;
echo self::json_serialize($response);
}
/**
* Return executable javascript code for all registered commands
*
* @return string $out
*/
protected function get_js_commands()
{
$out = '';
foreach ($this->commands as $i => $args) {
$method = array_shift($args);
foreach ($args as $i => $arg) {
$args[$i] = self::json_serialize($arg);
}
$out .= sprintf(
"this.%s(%s);\n",
preg_replace('/^parent\./', '', $method),
implode(',', $args)
);
}
return $out;
}
}
diff --git a/program/include/rcmail_string_replacer.php b/program/include/rcmail_string_replacer.php
new file mode 100644
index 000000000..4fbc611c9
--- /dev/null
+++ b/program/include/rcmail_string_replacer.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail_string_replacer.php |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 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: |
+ | Turn URLs and email addresses into clickable links |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+*/
+
+
+/**
+ * Helper class for turning URLs and email addresses in plaintext content
+ * into clickable links.
+ *
+ * @package Core
+ * @subpackage Utils
+ */
+class rcmail_string_replacer extends rcube_string_replacer
+{
+ /**
+ * Callback function used to build mailto: links around e-mail strings
+ *
+ * This also adds an onclick-handler to open the Rouncube compose message screen on such links
+ *
+ * @param array Matches result from preg_replace_callback
+ * @return int Index of saved string value
+ * @see rcube_string_replacer::mailto_callback()
+ */
+ public function mailto_callback($matches)
+ {
+ $href = $matches[1];
+ $suffix = $this->parse_url_brackets($href);
+
+ $i = $this->add(html::a(array(
+ 'href' => 'mailto:' . $href,
+ 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('compose','".rcube::JQ($href)."',this)",
+ ), rcube::Q($href)) . $suffix);
+
+ return $i >= 0 ? $this->get_replacement($i) : '';
+ }
+
+}
\ No newline at end of file
diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
index 3f881fe06..ca8d83ef3 100644
--- a/program/lib/Roundcube/rcube_output.php
+++ b/program/lib/Roundcube/rcube_output.php
@@ -1,284 +1,271 @@
<?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> |
+-----------------------------------------------------------------------+
*/
/**
* Class for output generation
*
* @package Framework
* @subpackage View
*/
abstract class rcube_output
{
public $browser;
- public $type = 'html';
- public $ajax_call = false;
- public $framed = false;
protected $app;
protected $config;
- protected $charset = RCUBE_CHARSET;
+ protected $charset = RCMAIL_CHARSET;
protected $env = array();
- protected $pagetitle = '';
- protected $object_handlers = array();
/**
* Object constructor
*/
- public function __construct($task = null, $framed = false)
+ public function __construct()
{
$this->app = rcube::get_instance();
$this->config = $this->app->config;
$this->browser = new rcube_browser();
}
/**
* Magic getter
*/
public function __get($var)
{
// allow read-only access to $env
if ($var == 'env')
return $this->env;
return null;
}
- /**
- * 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_utils::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
*/
public function future_expire_header($offset = 2600000)
{
if (headers_sent())
return;
header("Expires: " . gmdate("D, d M Y H:i:s", time()+$offset) . " GMT");
header("Cache-Control: max-age=$offset");
header("Pragma: ");
}
/**
* 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);
}
+ /**
+ * 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 = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
+ $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
+ }
+
+ $out = $input->show($value);
+
+ return $out;
+ }
+
+
/**
* 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/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 0467b866a..584b9f68c 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -1,195 +1,191 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_string_replacer.php |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2009, The Roundcube Dev Team |
+ | Copyright (C) 2009-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: |
| Handle string replacements based on preg_replace_callback |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Helper class for string replacements based on preg_replace_callback
*
* @package Framework
* @subpackage Utils
*/
class rcube_string_replacer
{
public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
public $mailto_pattern;
public $link_pattern;
private $values = array();
function __construct()
{
// Simplified domain expression for UTF8 characters handling
// Support unicode/punycode in top-level domain part
$utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
$url1 = '.:;,';
$url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
$this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
$this->mailto_pattern = "/("
."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part
."@$utf_domain" // domain-part
."(\?[$url1$url2]+)?" // e.g. ?subject=test...
.")/";
}
/**
* Add a string to the internal list
*
* @param string String value
* @return int Index of value for retrieval
*/
public function add($str)
{
$i = count($this->values);
$this->values[$i] = $str;
return $i;
}
/**
* Build replacement string
*/
public function get_replacement($i)
{
return '##str_replacement['.$i.']##';
}
/**
* Callback function used to build HTML links around URL strings
*
* @param array Matches result from preg_replace_callback
* @return int Index of saved string value
*/
public function link_callback($matches)
{
$i = -1;
$scheme = strtolower($matches[1]);
if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
$url = $matches[1] . $matches[2];
}
else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
$url = $m[2] . $matches[2];
$url_prefix = 'http://';
$prefix = $m[1];
}
if ($url) {
$suffix = $this->parse_url_brackets($url);
$i = $this->add($prefix . html::a(array(
'href' => $url_prefix . $url,
'target' => '_blank'
), rcube::Q($url)) . $suffix);
}
// Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
return $i >= 0 ? $this->get_replacement($i) : $matches[0];
}
/**
* Callback function used to build mailto: links around e-mail strings
*
* @param array Matches result from preg_replace_callback
* @return int Index of saved string value
*/
public function mailto_callback($matches)
{
$href = $matches[1];
$suffix = $this->parse_url_brackets($href);
-
- $i = $this->add(html::a(array(
- 'href' => 'mailto:' . $href,
- 'onclick' => "return ".rcmail::JS_OBJECT_NAME.".command('compose','".rcube::JQ($href)."',this)",
- ), rcmail::Q($href)) . $suffix);
+ $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
return $i >= 0 ? $this->get_replacement($i) : '';
}
/**
* Look up the index from the preg_replace matches array
* and return the substitution value.
*
* @param array Matches result from preg_replace_callback
* @return string Value at index $matches[1]
*/
public function replace_callback($matches)
{
return $this->values[$matches[1]];
}
/**
* Replace all defined (link|mailto) patterns with replacement string
*
* @param string $str Text
*
* @return string Text
*/
public function replace($str)
{
// search for patterns like links and e-mail addresses
$str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
$str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
return $str;
}
/**
* Replace substituted strings with original values
*/
public function resolve($str)
{
return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
}
/**
* Fixes bracket characters in URL handling
*/
public static function parse_url_brackets(&$url)
{
// #1487672: special handling of square brackets,
// URL regexp allows [] characters in URL, for example:
// "http://example.com/?a[b]=c". However we need to handle
// properly situation when a bracket is placed at the end
// of the link e.g. "[http://example.com]"
if (preg_match('/(\\[|\\])/', $url)) {
$in = false;
for ($i=0, $len=strlen($url); $i<$len; $i++) {
if ($url[$i] == '[') {
if ($in)
break;
$in = true;
}
else if ($url[$i] == ']') {
if (!$in)
break;
$in = false;
}
}
if ($i<$len) {
$suffix = substr($url, $i);
$url = substr($url, 0, $i);
}
}
return $suffix;
}
}
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index df77dfe42..500f2c371 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -1,977 +1,924 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_utils.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: |
| Utility class providing common functions |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Utility class providing common functions
*
* @package Framework
* @subpackage Utils
*/
class rcube_utils
{
// define constants for input reading
const INPUT_GET = 0x0101;
const INPUT_POST = 0x0102;
const INPUT_GPC = 0x0103;
/**
* Helper method to set a cookie with the current path and host settings
*
* @param string Cookie name
* @param string Cookie value
* @param string Expiration time
*/
public static function setcookie($name, $value, $exp = 0)
{
if (headers_sent()) {
return;
}
$cookie = session_get_cookie_params();
$secure = $cookie['secure'] || self::https_check();
setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], $secure, true);
}
/**
* 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 static 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;
}
// Validate domain part
if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 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;
}
}
// last domain part
if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) {
return false;
}
$rcube = rcube::get_instance();
if (!$dns_check || !$rcube->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 (!function_exists('getmxrr') || getmxrr($domain_part, $mx_records)) {
return true;
}
// find any DNS record
if (!function_exists('checkdnsrr') || checkdnsrr($domain_part, 'ANY')) {
return true;
}
}
return false;
}
/**
* Validates IPv4 or IPv6 address
*
* @param string $ip IP address in v4 or v6 format
*
* @return bool True if the address is valid
*/
public static function check_ip($ip)
{
// IPv6, but there's no build-in IPv6 support
if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
$parts = explode(':', $domain_part);
$count = count($parts);
if ($count > 8 || $count < 2) {
return false;
}
foreach ($parts as $idx => $part) {
$length = strlen($part);
if (!$length) {
// there can be only one ::
if ($found_empty) {
return false;
}
$found_empty = true;
}
// last part can be an IPv4 address
else if ($idx == $count - 1) {
if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
return @inet_pton($part) !== false;
}
}
else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
return false;
}
}
return true;
}
return @inet_pton($ip) !== false;
}
/**
* 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(self::request_header('Referer'));
return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
}
/**
* 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;
if (!is_string($str)) {
$str = strval($str);
}
// 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);
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['&'] = '&amp;';
// can be increased to support more charsets
for ($c=160; $c<256; $c++) {
$xml_rep_table[chr($c)] = "&#$c;";
}
$xml_rep_table['"'] = '&quot;';
$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))] = '&#8232;';
$js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
}
// 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;
}
/**
* 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 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
*/
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;
}
/**
* 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));
}
/**
* 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
*/
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;
}
/**
* 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']);
// %t - host name without first part, e.g. %n=mail.domain.tld, %t=domain.tld
$t = preg_replace('/^[^\.]+\./', '', $n);
// %d - domain name without first part
$d = preg_replace('/^[^\.]+\./', '', $_SERVER['HTTP_HOST']);
// %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 = self::get_input_value('_user', self::INPUT_POST);
$user_email = self::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', '%t', '%d', '%h', '%z', '%s'), array($n, $t, $d, $h, $z, $s[2]), $name);
return $name;
}
/**
* 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;
}
/**
* Read a specific HTTP request header.
*
* @param string $name Header name
*
* @return mixed Header value or null if not available
*/
public static function 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];
}
/**
* Explode quoted string
*
* @param string Delimiter expression string for preg_match()
* @param string Input string
*
* @return array String items
*/
public static function explode_quoted_string($delimiter, $string)
{
$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;
}
}
$result[] = (string) substr($string, $p);
return $result;
}
/**
* Improved equivalent to strtotime()
*
* @param string $date Date string
*
* @return int Unix timestamp
*/
public static function 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;
}
/*
* Idn_to_ascii wrapper.
* Intl/Idn modules version of this function doesn't work with e-mail address
*/
public static function idn_to_ascii($str)
{
return self::idn_convert($str, true);
}
/*
* Idn_to_ascii wrapper.
* Intl/Idn modules version of this function doesn't work with e-mail address
*/
public static function idn_to_utf8($str)
{
return self::idn_convert($str, false);
}
public static function 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;
}
/**
* Split the given string into word tokens
*
* @param string Input to tokenize
* @return array List of tokens
*/
public static function tokenize_string($str)
{
return explode(" ", preg_replace(
array('/[\s;\/+-]+/i', '/(\d)[-.\s]+(\d)/', '/\s\w{1,3}\s/u'),
array(' ', '\\1\\2', ' '),
$str));
}
/**
* Normalize the given string for fulltext search.
* Currently only optimized for Latin-1 characters; to be extended
*
* @param string Input string (UTF-8)
* @param boolean True to return list of words as array
* @return mixed Normalized string or a list of normalized tokens
*/
public static function normalize_string($str, $as_array = false)
{
// split by words
$arr = self::tokenize_string($str);
foreach ($arr as $i => $part) {
if (utf8_encode(utf8_decode($part)) == $part) { // is latin-1 ?
$arr[$i] = utf8_encode(strtr(strtolower(strtr(utf8_decode($part),
'ÇçäâàåéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ',
'ccaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy')),
array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u')));
}
else
$arr[$i] = mb_strtolower($part);
}
return $as_array ? $arr : join(" ", $arr);
}
/**
* Parse commandline arguments into a hash array
*
* @param array $aliases Argument alias names
*
* @return array Argument values hash
*/
public static function get_opt($aliases = array())
{
$args = array();
for ($i=1; $i < count($_SERVER['argv']); $i++) {
$arg = $_SERVER['argv'][$i];
$value = true;
$key = null;
if ($arg[0] == '-') {
$key = preg_replace('/^-+/', '', $arg);
$sp = strpos($arg, '=');
if ($sp > 0) {
$key = substr($key, 0, $sp - 2);
$value = substr($arg, $sp+1);
}
else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
$value = $_SERVER['argv'][++$i];
}
$args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
}
else {
$args[] = $arg;
}
if ($alias = $aliases[$key]) {
$args[$alias] = $args[$key];
}
}
return $args;
}
/**
* Safe password prompt for command line
* from http://blogs.sitepoint.com/2009/05/01/interactive-cli-password-prompt-in-php/
*
* @return string Password
*/
public static function prompt_silent($prompt = "Password:")
{
if (preg_match('/^win/i', PHP_OS)) {
$vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
$vbcontent = 'wscript.echo(InputBox("' . addslashes($prompt) . '", "", "password here"))';
file_put_contents($vbscript, $vbcontent);
$command = "cscript //nologo " . escapeshellarg($vbscript);
$password = rtrim(shell_exec($command));
unlink($vbscript);
return $password;
}
else {
$command = "/usr/bin/env bash -c 'echo OK'";
if (rtrim(shell_exec($command)) !== 'OK') {
echo $prompt;
$pass = trim(fgets(STDIN));
echo chr(8)."\r" . $prompt . str_repeat("*", strlen($pass))."\n";
return $pass;
}
$command = "/usr/bin/env bash -c 'read -s -p \"" . addslashes($prompt) . "\" mypassword && echo \$mypassword'";
$password = rtrim(shell_exec($command));
echo "\n";
return $password;
}
}
/**
* Find out if the string content means true or false
*
* @param string $str Input value
*
* @return boolean Boolean value
*/
public static function get_boolean($str)
{
$str = strtolower($str);
return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
}
}
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 33ac1a206..ff442ad60 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1865 +1,1865 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.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 webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
// setup some global vars used by mail steps
$SENT_MBOX = $RCMAIL->config->get('sent_mbox');
$DRAFTS_MBOX = $RCMAIL->config->get('drafts_mbox');
$SEARCH_MODS_DEFAULT = array(
'*' => array('subject'=>1, 'from'=>1),
$SENT_MBOX => array('subject'=>1, 'to'=>1),
$DRAFTS_MBOX => array('subject'=>1, 'to'=>1)
);
// always instantiate storage object (but not connect to server yet)
$RCMAIL->storage_init();
// set imap properties and session vars
if (strlen(trim($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC, true))))
$RCMAIL->storage->set_folder(($_SESSION['mbox'] = $mbox));
else if ($RCMAIL->storage)
$_SESSION['mbox'] = $RCMAIL->storage->get_folder();
if (!empty($_GET['_page']))
$RCMAIL->storage->set_page(($_SESSION['page'] = intval($_GET['_page'])));
// set default sort col/order to session
if (!isset($_SESSION['sort_col']))
$_SESSION['sort_col'] = !empty($CONFIG['message_sort_col']) ? $CONFIG['message_sort_col'] : '';
if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = strtoupper($CONFIG['message_sort_order']) == 'ASC' ? 'ASC' : 'DESC';
// set threads mode
$a_threading = $RCMAIL->config->get('message_threading', array());
if (isset($_GET['_threads'])) {
if ($_GET['_threads'])
$a_threading[$_SESSION['mbox']] = true;
else
unset($a_threading[$_SESSION['mbox']]);
$RCMAIL->user->save_prefs(array('message_threading' => $a_threading));
}
$RCMAIL->storage->set_threading($a_threading[$_SESSION['mbox']]);
// set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
&& $_SESSION['search_request'] == $_REQUEST['_search']
) {
$RCMAIL->storage->set_search_set($_SESSION['search']);
$OUTPUT->set_env('search_request', $_REQUEST['_search']);
$OUTPUT->set_env('search_text', $_SESSION['last_text_search']);
}
// set main env variables, labels and page title
if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
// connect to storage server and trigger error on failure
$RCMAIL->storage_connect();
$mbox_name = $RCMAIL->storage->get_folder();
if (empty($RCMAIL->action)) {
// initialize searching result if search_filter is used
if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
$search_request = md5($mbox_name.$_SESSION['search_filter']);
$RCMAIL->storage->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, rcmail_sort_column());
$_SESSION['search'] = $RCMAIL->storage->get_search_set();
$_SESSION['search_request'] = $search_request;
$OUTPUT->set_env('search_request', $search_request);
}
$search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT);
$OUTPUT->set_env('search_mods', $search_mods);
}
$threading = (bool) $RCMAIL->storage->get_threading();
// set current mailbox and some other vars in client environment
$OUTPUT->set_env('mailbox', $mbox_name);
$OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
$OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter());
$OUTPUT->set_env('threading', $threading);
$OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
$OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
if ($RCMAIL->storage->get_capability('QUOTA')) {
$OUTPUT->set_env('quota', true);
}
foreach (array('delete_junk','flag_for_deletion','read_when_deleted','skip_deleted','display_next','message_extwin','compose_extwin','forward_attachment') as $prop) {
if ($CONFIG[$prop])
$OUTPUT->set_env($prop, true);
}
if ($CONFIG['trash_mbox'])
$OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
if ($CONFIG['drafts_mbox'])
$OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
if ($CONFIG['junk_mbox'])
$OUTPUT->set_env('junk_mailbox', $CONFIG['junk_mbox']);
if (!empty($_SESSION['browser_caps']))
$OUTPUT->set_env('browser_capabilities', $_SESSION['browser_caps']);
if (!$OUTPUT->ajax_call)
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
'copy', 'move', 'quota');
$OUTPUT->set_pagetitle(rcmail_localize_foldername($RCMAIL->storage->mod_folder($mbox_name)));
}
/**
* Returns 'to' if current folder is configured Sent or Drafts
* or their subfolders, otherwise returns 'from'.
*
* @return string Column name
*/
function rcmail_message_list_smart_column_name()
{
global $RCMAIL;
$delim = $RCMAIL->storage->get_hierarchy_delimiter();
$mbox = $RCMAIL->storage->get_folder();
$sent_mbox = $RCMAIL->config->get('sent_mbox');
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
if (strpos($mbox.$delim, $sent_mbox.$delim) === 0 || strpos($mbox.$delim, $drafts_mbox.$delim) === 0) {
return 'to';
}
return 'from';
}
/**
* Returns configured messages list sorting column name
* The name is context-sensitive, which means if sorting is set to 'fromto'
* it will return 'from' or 'to' according to current folder type.
*
* @return string Column name
*/
function rcmail_sort_column()
{
global $RCMAIL;
if (isset($_SESSION['sort_col'])) {
$column = $_SESSION['sort_col'];
}
else {
$column = $RCMAIL->config->get('message_sort_col');
}
// get name of smart From/To column in folder context
if ($column == 'fromto') {
$column = rcmail_message_list_smart_column_name();
}
return $column;
}
/**
* Returns configured message list sorting order
*
* @return string Sorting order (ASC|DESC)
*/
function rcmail_sort_order()
{
global $RCMAIL;
if (isset($_SESSION['sort_order'])) {
return $_SESSION['sort_order'];
}
return $RCMAIL->config->get('message_sort_order');
}
/**
* return the message list as HTML table
*/
function rcmail_message_list($attrib)
{
global $CONFIG, $OUTPUT;
// add some labels to client
$OUTPUT->add_label('from', 'to');
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist';
// define list of cols to be displayed based on parameter or config
if (empty($attrib['columns'])) {
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
$OUTPUT->set_env('col_movable', !in_array('list_cols', (array)$CONFIG['dont_override']));
}
else {
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
$attrib['columns'] = $a_show_cols;
}
// save some variables for use in ajax list
$_SESSION['list_attrib'] = $attrib;
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
array_unshift($a_show_cols, 'subject');
if (!in_array('threads', $a_show_cols))
array_unshift($a_show_cols, 'threads');
$skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
// set client env
$OUTPUT->add_gui_object('messagelist', $attrib['id']);
$OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads']));
$OUTPUT->set_env('sort_col', $_SESSION['sort_col']);
$OUTPUT->set_env('sort_order', $_SESSION['sort_order']);
$OUTPUT->set_env('messages', array());
$OUTPUT->set_env('coltypes', $a_show_cols);
$OUTPUT->include_script('list.js');
$thead = '';
foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
$thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
return html::tag('table',
$attrib,
html::tag('thead', null, html::tag('tr', null, $thead)) .
html::tag('tbody', null, ''),
array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
}
/**
* return javascript commands to add rows to the message list
*/
function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null)
{
global $CONFIG, $RCMAIL, $OUTPUT;
if (empty($a_show_cols)) {
if (!empty($_SESSION['list_attrib']['columns']))
$a_show_cols = $_SESSION['list_attrib']['columns'];
else
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
}
else {
if (!is_array($a_show_cols))
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols));
$head_replace = true;
}
$mbox = $RCMAIL->storage->get_folder();
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
array_unshift($a_show_cols, 'subject');
if (!in_array('threads', $a_show_cols))
array_unshift($a_show_cols, 'threads');
$_SESSION['list_attrib']['columns'] = $a_show_cols;
// 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_message_header variables
// and list columns
$plugin = $RCMAIL->plugins->exec_hook('messages_list',
array('messages' => $a_headers, 'cols' => $a_show_cols));
$a_show_cols = $plugin['cols'];
$a_headers = $plugin['messages'];
$thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
// get name of smart From/To column in folder context
if (($f = array_search('fromto', $a_show_cols)) !== false) {
$smart_col = rcmail_message_list_smart_column_name();
}
$OUTPUT->command('set_message_coltypes', $a_show_cols, $thead, $smart_col);
if (empty($a_headers))
return;
// remove 'threads', 'attachment', 'flag', 'status' columns, we don't need them here
foreach (array('threads', 'attachment', 'flag', 'status', 'priority') as $col) {
if (($key = array_search($col, $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]);
}
// loop through message headers
foreach ($a_headers as $n => $header) {
if (empty($header))
continue;
$a_msg_cols = array();
$a_msg_flags = array();
// format each col; similar as in rcmail_message_list()
foreach ($a_show_cols as $col) {
$col_name = $col == 'fromto' ? $smart_col : $col;
if (in_array($col_name, array('from', 'to', 'cc', 'replyto')))
$cont = rcmail_address_string($header->$col_name, 3, false, null, $header->charset);
else if ($col == 'subject') {
$cont = trim(rcube_mime::decode_header($header->$col, $header->charset));
if (!$cont) $cont = rcube_label('nosubject');
$cont = Q($cont);
}
else if ($col == 'size')
$cont = show_bytes($header->$col);
else if ($col == 'date')
$cont = format_date($header->date);
else
$cont = Q($header->$col);
$a_msg_cols[$col] = $cont;
}
$a_msg_flags = array_change_key_case(array_map('intval', (array) $header->flags));
if ($header->depth)
$a_msg_flags['depth'] = $header->depth;
else if ($header->has_children)
$roots[] = $header->uid;
if ($header->parent_uid)
$a_msg_flags['parent_uid'] = $header->parent_uid;
if ($header->has_children)
$a_msg_flags['has_children'] = $header->has_children;
if ($header->unread_children)
$a_msg_flags['unread_children'] = $header->unread_children;
if ($header->others['list-post'])
$a_msg_flags['ml'] = 1;
if ($header->priority)
$a_msg_flags['prio'] = (int) $header->priority;
$a_msg_flags['ctype'] = Q($header->ctype);
$a_msg_flags['mbox'] = $mbox;
// merge with plugin result (Deprecated, use $header->flags)
if (!empty($header->list_flags) && is_array($header->list_flags))
$a_msg_flags = array_merge($a_msg_flags, $header->list_flags);
if (!empty($header->list_cols) && is_array($header->list_cols))
$a_msg_cols = array_merge($a_msg_cols, $header->list_cols);
$OUTPUT->command('add_message_row',
$header->uid,
$a_msg_cols,
$a_msg_flags,
$insert_top);
}
if ($RCMAIL->storage->get_threading()) {
$OUTPUT->command('init_threads', (array) $roots, $mbox);
}
}
/*
* Creates <THEAD> for message list table
*/
function rcmail_message_list_head($attrib, $a_show_cols)
{
global $RCMAIL;
$skin_path = $_SESSION['skin_path'];
$image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
// check to see if we have some settings for sorting
$sort_col = $_SESSION['sort_col'];
$sort_order = $_SESSION['sort_order'];
$dont_override = (array)$RCMAIL->config->get('dont_override');
$disabled_sort = in_array('message_sort_col', $dont_override);
$disabled_order = in_array('message_sort_order', $dont_override);
$RCMAIL->output->set_env('disabled_sort_col', $disabled_sort);
$RCMAIL->output->set_env('disabled_sort_order', $disabled_order);
// define sortable columns
if ($disabled_sort)
$a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array();
else
$a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc');
if (!empty($attrib['optionsmenuicon'])) {
$onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')";
if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true')
$list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu',
'id' => 'listmenulink', 'title' => rcube_label('listoptions')));
else
$list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
'id' => 'listmenulink', 'title' => rcube_label('listoptions')))
);
}
else
$list_menu = '';
$cells = array();
// get name of smart From/To column in folder context
if (($f = array_search('fromto', $a_show_cols)) !== false) {
$smart_col = rcmail_message_list_smart_column_name();
}
foreach ($a_show_cols as $col) {
// get column name
switch ($col) {
case 'flag':
$col_name = '<span class="flagged">&nbsp;</span>';
break;
case 'attachment':
case 'priority':
case 'status':
$col_name = '<span class="' . $col .'">&nbsp;</span>';
break;
case 'threads':
$col_name = $list_menu;
break;
case 'fromto':
$col_name = Q(rcube_label($smart_col));
break;
default:
$col_name = Q(rcube_label($col));
}
// make sort links
if (in_array($col, $a_sort_cols))
$col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name);
else if ($col_name[0] != '<')
$col_name = '<span class="' . $col .'">' . $col_name . '</span>';
$sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : '';
$class_name = $col.$sort_class;
// put it all together
$cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name);
}
return $cells;
}
/**
* return an HTML iframe for loading mail content
*/
function rcmail_messagecontent_frame($attrib)
{
global $OUTPUT, $RCMAIL;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailcontentwindow';
$attrib['name'] = $attrib['id'];
if ($RCMAIL->config->get('preview_pane'))
$OUTPUT->set_env('contentframe', $attrib['id']);
$OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/resources/blank.gif');
return $OUTPUT->frame($attrib, true);
}
function rcmail_messagecount_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$RCMAIL->output->add_gui_object('countdisplay', $attrib['id']);
$content = $RCMAIL->action != 'show' ? rcmail_get_messagecount_text() : rcube_label('loading');
return html::span($attrib, $content);
}
function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{
global $RCMAIL;
if ($page === NULL) {
$page = $RCMAIL->storage->get_page();
}
$page_size = $RCMAIL->storage->get_pagesize();
$start_msg = ($page-1) * $page_size + 1;
if ($count!==NULL)
$max = $count;
else if ($RCMAIL->action)
$max = $RCMAIL->storage->count(NULL, $RCMAIL->storage->get_threading() ? 'THREADS' : 'ALL');
if ($max==0)
$out = rcube_label('mailboxempty');
else
$out = rcube_label(array('name' => $RCMAIL->storage->get_threading() ? 'threadsfromto' : 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $page_size - 1),
'count' => $max)));
return Q($out);
}
function rcmail_mailbox_name_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmmailboxname';
$RCMAIL->output->add_gui_object('mailboxname', $attrib['id']);
return html::span($attrib, rcmail_get_mailbox_name_text());
}
function rcmail_get_mailbox_name_text()
{
global $RCMAIL;
return rcmail_localize_foldername($RCMAIL->storage->get_folder());
}
function rcmail_send_unread_count($mbox_name, $force=false, $count=null, $mark='')
{
global $RCMAIL;
$old_unseen = rcmail_get_unseen_count($mbox_name);
if ($count === null)
$unseen = $RCMAIL->storage->count($mbox_name, 'UNSEEN', $force);
else
$unseen = $count;
if ($unseen != $old_unseen || ($mbox_name == 'INBOX'))
$RCMAIL->output->command('set_unread_count', $mbox_name, $unseen,
($mbox_name == 'INBOX'), $unseen && $mark ? $mark : '');
rcmail_set_unseen_count($mbox_name, $unseen);
return $unseen;
}
function rcmail_set_unseen_count($mbox_name, $count)
{
// @TODO: this data is doubled (session and cache tables) if caching is enabled
// Make sure we have an array here (#1487066)
if (!is_array($_SESSION['unseen_count']))
$_SESSION['unseen_count'] = array();
$_SESSION['unseen_count'][$mbox_name] = $count;
}
function rcmail_get_unseen_count($mbox_name)
{
if (is_array($_SESSION['unseen_count']) && array_key_exists($mbox_name, $_SESSION['unseen_count']))
return $_SESSION['unseen_count'][$mbox_name];
else
return null;
}
/**
* Sets message is_safe flag according to 'show_images' option value
*
* @param object rcube_message Message
*/
function rcmail_check_safe(&$message)
{
global $RCMAIL;
if (!$message->is_safe
&& ($show_images = $RCMAIL->config->get('show_images'))
&& $message->has_html_part()
) {
switch ($show_images) {
case 1: // known senders only
// get default addressbook, like in addcontact.inc
$CONTACTS = $RCMAIL->get_address_book(-1, true);
if ($CONTACTS) {
$result = $CONTACTS->search('email', $message->sender['mailto'], 1, false);
if ($result->count) {
$message->set_safe(true);
}
}
break;
case 2: // always
$message->set_safe(true);
break;
}
}
}
/**
* Cleans up the given message HTML Body (for displaying)
*
* @param string HTML
* @param array Display parameters
* @param array CID map replaces (inline images)
* @return string Clean HTML
*/
function rcmail_wash_html($html, $p, $cid_replaces)
{
global $REMOTE_OBJECTS;
$p += array('safe' => false, 'inline_html' => true);
// special replacements (not properly handled by washtml class)
$html_search = array(
'/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR>
'/<title[^>]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag
'/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?)
'/<html\s[^>]+>/i', // washtml/DOMDocument cannot handle xml namespaces
);
$html_replace = array(
'\\1'.' &nbsp; '.'\\3',
'',
'',
'<html>',
);
$html = preg_replace($html_search, $html_replace, trim($html));
// PCRE errors handling (#1486856), should we use something like for every preg_* use?
if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR)
$errstr .= " Consider raising pcre.backtrack_limit!";
if ($preg_error == PREG_RECURSION_LIMIT_ERROR)
$errstr .= " Consider raising pcre.recursion_limit!";
raise_error(array('code' => 620, 'type' => 'php',
'line' => __LINE__, 'file' => __FILE__,
'message' => $errstr), true, false);
return '';
}
// fix (unknown/malformed) HTML tags before "wash"
$html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html);
// charset was converted to UTF-8 in rcube_storage::get_message_part(),
// change/add charset specification in HTML accordingly,
// washtml cannot work without that
$meta = '<meta http-equiv="Content-Type" content="text/html; charset='.RCMAIL_CHARSET.'" />';
// remove old meta tag and add the new one, making sure
// that it is placed in the head (#1488093)
$html = preg_replace('/<meta[^>]+charset=[a-z0-9-_]+[^>]*>/Ui', '', $html);
$html = preg_replace('/(<head[^>]*>)/Ui', '\\1'.$meta, $html, -1, $rcount);
if (!$rcount) {
$html = '<head>' . $meta . '</head>' . $html;
}
// turn relative into absolute urls
$html = rcmail_resolve_base($html);
// clean HTML with washhtml by Frederic Motte
$wash_opts = array(
'show_washed' => false,
'allow_remote' => $p['safe'],
'blocked_src' => "./program/resources/blocked.gif",
'charset' => RCMAIL_CHARSET,
'cid_map' => $cid_replaces,
'html_elements' => array('body'),
);
if (!$p['inline_html']) {
$wash_opts['html_elements'] = array('html','head','title','body');
}
if ($p['safe']) {
$wash_opts['html_elements'][] = 'link';
$wash_opts['html_attribs'] = array('rel','type');
}
// overwrite washer options with options from plugins
if (isset($p['html_elements']))
$wash_opts['html_elements'] = $p['html_elements'];
if (isset($p['html_attribs']))
$wash_opts['html_attribs'] = $p['html_attribs'];
// initialize HTML washer
$washer = new washtml($wash_opts);
if (!$p['skip_washer_form_callback'])
$washer->add_callback('form', 'rcmail_washtml_callback');
// allow CSS styles, will be sanitized by rcmail_washtml_callback()
if (!$p['skip_washer_style_callback'])
$washer->add_callback('style', 'rcmail_washtml_callback');
// Remove non-UTF8 characters (#1487813)
$html = rc_utf8_clean($html);
$html = $washer->wash($html);
$REMOTE_OBJECTS = $washer->extlinks;
return $html;
}
/**
* Convert the given message part to proper HTML
* which can be displayed the message view
*
* @param object rcube_message_part Message part
* @param array Display parameters array
* @return string Formatted HTML string
*/
function rcmail_print_body($part, $p = array())
{
global $RCMAIL;
// trigger plugin hook
$data = $RCMAIL->plugins->exec_hook('message_part_before',
array('type' => $part->ctype_secondary, 'body' => $part->body, 'id' => $part->mime_id)
+ $p + array('safe' => false, 'plain' => false, 'inline_html' => true));
// convert html to text/plain
if ($data['type'] == 'html' && $data['plain']) {
$txt = new html2text($data['body'], false, true);
$body = $txt->get_text();
$part->ctype_secondary = 'plain';
}
// text/html
else if ($data['type'] == 'html') {
$body = rcmail_wash_html($data['body'], $data, $part->replaces);
$part->ctype_secondary = $data['type'];
}
// text/enriched
else if ($data['type'] == 'enriched') {
require_once(INSTALL_PATH . 'program/lib/enriched.inc');
$body = enriched_to_html($data['body']);
$body = rcmail_wash_html($body, $data, $part->replaces);
$part->ctype_secondary = 'html';
}
else {
// assert plaintext
$body = $part->body;
$part->ctype_secondary = $data['type'] = 'plain';
}
// free some memory (hopefully)
unset($data['body']);
// plaintext postprocessing
if ($part->ctype_secondary == 'plain')
$body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed');
// allow post-processing of the message body
$data = $RCMAIL->plugins->exec_hook('message_part_after',
array('type' => $part->ctype_secondary, 'body' => $body, 'id' => $part->mime_id) + $data);
return $data['type'] == 'html' ? $data['body'] : html::tag('pre', array(), $data['body']);
}
/**
* Handle links and citation marks in plain text message
*
* @param string Plain text string
* @param boolean Text uses format=flowed
*
* @return string Formatted HTML string
*/
function rcmail_plain_body($body, $flowed=false)
{
global $RCMAIL;
// make links and email-addresses clickable
- $replacer = new rcube_string_replacer;
+ $replacer = new rcmail_string_replacer;
// search for patterns like links and e-mail addresses and replace with tokens
$body = $replacer->replace($body);
// split body into single lines
$body = preg_split('/\r?\n/', $body);
$quote_level = 0;
$last = -1;
// find/mark quoted lines...
for ($n=0, $cnt=count($body); $n < $cnt; $n++) {
if ($body[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $body[$n], $regs)) {
$q = substr_count($regs[0], '>');
$body[$n] = substr($body[$n], strlen($regs[0]));
if ($q > $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('<blockquote>', $q - $quote_level))) . $body[$n];
}
else if ($q < $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level - $q))) . $body[$n];
}
else if ($flowed) {
// previous line is flowed
if (isset($body[$last]) && $body[$n]
&& $body[$last][strlen($body[$last])-1] == ' ') {
// merge lines
$body[$last] .= $body[$n];
unset($body[$n]);
}
else {
$last = $n;
}
}
}
else {
$q = 0;
if ($flowed) {
// sig separator - line is fixed
if ($body[$n] == '-- ') {
$last = $last_sig = $n;
}
else {
// remove space-stuffing
if ($body[$n][0] == ' ')
$body[$n] = substr($body[$n], 1);
// previous line is flowed?
if (isset($body[$last]) && $body[$n]
&& $last !== $last_sig
&& $body[$last][strlen($body[$last])-1] == ' '
) {
$body[$last] .= $body[$n];
unset($body[$n]);
}
else {
$last = $n;
}
}
if ($quote_level > 0)
$body[$last] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level))) . $body[$last];
}
else if ($quote_level > 0)
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level))) . $body[$n];
}
$quote_level = $q;
}
$body = join("\n", $body);
// quote plain text (don't use Q() here, to display entities "as is")
$table = get_html_translation_table(HTML_SPECIALCHARS);
unset($table['?']);
$body = strtr($body, $table);
// colorize signature (up to <sig_max_lines> lines)
$len = strlen($body);
$sig_max_lines = $RCMAIL->config->get('sig_max_lines', 15);
while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) {
if ($sp == 0 || $body[$sp-1] == "\n") {
// do not touch blocks with more that X lines
if (substr_count($body, "\n", $sp) < $sig_max_lines)
$body = substr($body, 0, max(0, $sp))
.'<span class="sig">'.substr($body, $sp).'</span>';
break;
}
}
// insert url/mailto links and citation tags
$body = $replacer->resolve($body);
return $body;
}
/**
* Callback function for washtml cleaning class
*/
function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
{
switch ($tagname) {
case 'form':
$out = html::div('form', $content);
break;
case 'style':
// decode all escaped entities and reduce to ascii strings
$stripped = preg_replace('/[^a-zA-Z\(:;]/', '', rcmail_xss_entity_decode($content));
// now check for evil strings like expression, behavior or url()
if (!preg_match('/expression|behavior|javascript:|import[^a]/i', $stripped)) {
if (!$washtml->get_config('allow_remote') && stripos($stripped, 'url('))
$washtml->extlinks = true;
else
$out = html::tag('style', array('type' => 'text/css'), $content);
break;
}
default:
$out = '';
}
return $out;
}
/**
* Callback function for HTML tags fixing
*/
function rcmail_html_tag_callback($matches)
{
$tagname = $matches[2];
$tagname = preg_replace(array(
'/:.*$/', // Microsoft's Smart Tags <st1:xxxx>
'/[^a-z0-9_\[\]\!-]/i', // forbidden characters
), '', $tagname);
return $matches[1].$tagname;
}
/**
* return table with message headers
*/
function rcmail_message_headers($attrib, $headers=NULL)
{
global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
static $sa_attrib;
// keep header table attrib
if (is_array($attrib) && !$sa_attrib)
$sa_attrib = $attrib;
else if (!is_array($attrib) && is_array($sa_attrib))
$attrib = $sa_attrib;
if (!isset($MESSAGE))
return FALSE;
// get associative array of headers object
if (!$headers)
$headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
// show these headers
$standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto',
'mail-reply-to', 'mail-followup-to', 'date', 'priority');
$exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array();
$output_headers = array();
foreach ($standard_headers as $hkey) {
$ishtml = false;
if ($headers[$hkey])
$value = $headers[$hkey];
else if ($headers['others'][$hkey])
$value = $headers['others'][$hkey];
else
continue;
if (in_array($hkey, $exclude_headers))
continue;
$header_title = rcube_label(preg_replace('/(^mail-|-)/', '', $hkey));
if ($hkey == 'date') {
if ($PRINT_MODE)
$header_value = format_date($value, $RCMAIL->config->get('date_long', 'x'));
else
$header_value = format_date($value);
}
else if ($hkey == 'priority') {
if ($value) {
$header_value = html::span('prio' . $value, rcmail_localized_priority($value));
}
else
continue;
}
else if ($hkey == 'replyto') {
if ($headers['replyto'] != $headers['from']) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else
continue;
}
else if ($hkey == 'mail-reply-to') {
if ($headers['mail-replyto'] != $headers['reply-to']
&& $headers['reply-to'] != $headers['from']
) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else
continue;
}
else if ($hkey == 'mail-followup-to') {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc'))) {
$header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
$ishtml = true;
}
else if ($hkey == 'subject' && empty($value))
$header_value = rcube_label('nosubject');
else
$header_value = trim(rcube_mime::decode_header($value, $headers['charset']));
$output_headers[$hkey] = array(
'title' => $header_title,
'value' => $header_value,
'raw' => $value,
'html' => $ishtml,
);
}
$plugin = $RCMAIL->plugins->exec_hook('message_headers_output',
array('output' => $output_headers, 'headers' => $MESSAGE->headers, 'exclude' => $exclude_headers));
// single header value is requested
if (!empty($attrib['valueof']))
return Q($plugin['output'][$attrib['valueof']]['value'], ($attrib['valueof'] == 'subject' ? 'strict' : 'show'));
// compose html table
$table = new html_table(array('cols' => 2));
foreach ($plugin['output'] as $hkey => $row) {
$table->add(array('class' => 'header-title'), Q($row['title']));
$table->add(array('class' => 'header '.$hkey), $row['html'] ? $row['value'] : Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
}
return $table->show($attrib);
}
/**
* Convert Priority header value into a localized string
*/
function rcmail_localized_priority($value)
{
$labels_map = array(
'1' => 'highest',
'2' => 'high',
'3' => 'normal',
'4' => 'low',
'5' => 'lowest',
);
if ($value && $labels_map[$value])
return rcube_label($labels_map[$value]);
return '';
}
/**
* return block to show full message headers
*/
function rcmail_message_full_headers($attrib, $headers=NULL)
{
global $OUTPUT;
$html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
$html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)", 'title' => rcube_label('togglefullheaders')), '');
$OUTPUT->add_gui_object('all_headers_row', 'all-headers');
$OUTPUT->add_gui_object('all_headers_box', 'headers-source');
return html::div($attrib, $html);
}
/**
* Handler for the 'messagebody' GUI object
*
* @param array Named parameters
* @return string HTML content showing the message body
*/
function rcmail_message_body($attrib)
{
global $CONFIG, $OUTPUT, $MESSAGE, $RCMAIL, $REMOTE_OBJECTS;
if (!is_array($MESSAGE->parts) && empty($MESSAGE->body))
return '';
if (!$attrib['id'])
$attrib['id'] = 'rcmailMsgBody';
$safe_mode = $MESSAGE->is_safe || intval($_GET['_safe']);
$out = '';
$header_attrib = array();
foreach ($attrib as $attr => $value)
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
$header_attrib[$regs[1]] = $value;
if (!empty($MESSAGE->parts)) {
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') {
// 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
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')));
continue;
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset']))
$part->ctype_parameters['charset'] = $MESSAGE->headers->charset;
// fetch part if not available
if (!isset($part->body))
$part->body = $MESSAGE->get_part_content($part->mime_id);
// message is cached but not exists (#1485443), or other error
if ($part->body === false) {
rcmail_message_error($MESSAGE->uid);
}
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $part, 'prefix' => ''));
$body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html']));
if ($part->ctype_secondary == 'html') {
$body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs, $safe_mode);
$div_attr = array('class' => 'message-htmlpart');
$style = array();
if (!empty($attrs)) {
foreach ($attrs as $a_idx => $a_val)
$style[] = $a_idx . ': ' . $a_val;
if (!empty($style))
$div_attr['style'] = implode('; ', $style);
}
$out .= html::div($div_attr, $plugin['prefix'] . $body);
}
else
$out .= html::div('message-part', $plugin['prefix'] . $body);
}
}
}
else {
// 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(strlen($MESSAGE->body) * 10)) {
$out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part=0'
.'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download')));
}
else {
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $MESSAGE, 'prefix' => ''));
$out .= html::div('message-part', $plugin['prefix'] . html::tag('pre', array(),
rcmail_plain_body(Q($MESSAGE->body, 'strict', false))));
}
}
// list images after mail body
if ($RCMAIL->config->get('inline_images', true) && !empty($MESSAGE->attachments)) {
$thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240);
$client_mimetypes = (array)$RCMAIL->config->get('client_mimetypes');
foreach ($MESSAGE->attachments as $attach_prop) {
// skip inline images
if ($attach_prop->content_id && $attach_prop->disposition == 'inline') {
continue;
}
// Content-Type: image/*...
if ($mimetype = rcmail_part_image_type($attach_prop)) {
// display thumbnails
if ($thumbnail_size) {
$show_link = array(
'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
'onclick' => sprintf(
'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)',
JS_OBJECT_NAME,
$attach_prop->mime_id,
$mimetype)
);
$out .= html::p('image-attachment',
html::a($show_link + array('class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)),
html::img(array(
'class' => 'image-thumbnail',
'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true) . '&_thumb=1',
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
))
) .
html::span('image-filename', Q($attach_prop->filename)) .
html::span('image-filesize', Q($RCMAIL->message_part_size($attach_prop))) .
html::span('attachment-links',
(in_array($mimetype, $client_mimetypes) ? html::a($show_link, rcube_label('showattachment')) . '&nbsp;' : '') .
html::a($show_link['href'] . '&_download=1', rcube_label('download'))
) .
html::br(array('style' => 'clear:both'))
);
}
else {
$out .= html::tag('fieldset', 'image-attachment',
html::tag('legend', 'image-filename', Q($attach_prop->filename)) .
html::p(array('align' => "center"),
html::img(array(
'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true),
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
)))
);
}
}
}
}
// tell client that there are blocked remote objects
if ($REMOTE_OBJECTS && !$safe_mode)
$OUTPUT->set_env('blockedobjects', true);
return html::div($attrib, $out);
}
function rcmail_part_image_type($part)
{
$rcmail = rcmail::get_instance();
// Skip TIFF images if browser doesn't support this format...
$tiff_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps']['tif']);
// until we can convert them to JPEG
$tiff_support = $tiff_support || $rcmail->config->get('im_convert_path');
// Content-type regexp
$mime_regex = $tiff_support ? '/^image\//i' : '/^image\/(?!tif)/i';
// Content-Type: image/*...
if (preg_match($mime_regex, $part->mimetype)) {
return $part->mimetype;
}
// Many clients use application/octet-stream, we'll detect mimetype
// by checking filename extension
// Supported image filename extensions to image type map
$types = array(
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
);
if ($tiff_support) {
$types['tif'] = 'image/tiff';
$types['tiff'] = 'image/tiff';
}
if ($part->filename
&& preg_match('/^application\/octet-stream$/i', $part->mimetype)
&& preg_match('/\.([^.]+)$/i', $part->filename, $m)
&& ($extension = strtolower($m[1]))
&& isset($types[$extension])
) {
return $types[$extension];
}
}
/**
* Convert all relative URLs according to a <base> in HTML
*/
function rcmail_resolve_base($body)
{
// check for <base href=...>
if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
$replacer = new rcube_base_replacer($regs[2]);
$body = $replacer->replace($body);
}
return $body;
}
/**
* modify a HTML message that it can be displayed inside a HTML page
*/
function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null, $allow_remote=false)
{
$last_style_pos = 0;
$cont_id = $container_id.($body_id ? ' div.'.$body_id : '');
// find STYLE tags
while (($pos = stripos($body, '<style', $last_style_pos)) && ($pos2 = stripos($body, '</style>', $pos)))
{
$pos = strpos($body, '>', $pos) + 1;
$len = $pos2 - $pos;
// replace all css definitions with #container [def]
$styles = substr($body, $pos, $len);
$styles = rcmail_mod_css_styles($styles, $cont_id, $allow_remote);
$body = substr_replace($body, $styles, $pos, $len);
$last_style_pos = $pos2 + strlen($styles) - $len;
}
// modify HTML links to open a new window if clicked
$GLOBALS['rcmail_html_container_id'] = $container_id;
$body = preg_replace_callback('/<(a|link|area)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
unset($GLOBALS['rcmail_html_container_id']);
$body = preg_replace(array(
// add comments arround html and other tags
'/(<!DOCTYPE[^>]*>)/i',
'/(<\?xml[^>]*>)/i',
'/(<\/?html[^>]*>)/i',
'/(<\/?head[^>]*>)/i',
'/(<title[^>]*>.*<\/title>)/Ui',
'/(<\/?meta[^>]*>)/i',
// quote <? of php and xml files that are specified as text/html
'/<\?/',
'/\?>/',
// replace <body> with <div>
'/<body([^>]*)>/i',
'/<\/body>/i',
),
array(
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'&lt;?',
'?&gt;',
'<div class="'.$body_id.'"\\1>',
'</div>',
),
$body);
$attributes = array();
// Handle body attributes that doesn't play nicely with div elements
$regexp = '/<div class="' . preg_quote($body_id, '/') . '"([^>]*)/';
if (preg_match($regexp, $body, $m)) {
$attrs = $m[0];
// Get bgcolor, we'll set it as background-color of the message container
if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) {
$attributes['background-color'] = $mb[1];
$attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs);
}
// Get background, we'll set it as background-image of the message container
if ($m[1] && preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) {
$attributes['background-image'] = 'url('.$mb[1].')';
$attrs = preg_replace('/background=["\']*([^"\'>\s]+)["\']*/', '', $attrs);
}
if (!empty($attributes)) {
$body = preg_replace($regexp, rtrim($attrs), $body, 1);
}
// handle body styles related to background image
if ($attributes['background-image']) {
// get body style
if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) {
// get background related style
if (preg_match_all('/(background-position|background-repeat)\s*:\s*([^;]+);/i', $m[1], $ma, PREG_SET_ORDER)) {
foreach ($ma as $style)
$attributes[$style[1]] = $style[2];
}
}
}
}
// make sure there's 'rcmBody' div, we need it for proper css modification
// its name is hardcoded in rcmail_message_body() also
else {
$body = '<div class="' . $body_id . '">' . $body . '</div>';
}
return $body;
}
/**
* parse link attributes and set correct target
*/
function rcmail_alter_html_link($matches)
{
global $RCMAIL;
// Support unicode/punycode in top-level domain part
$EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
$tag = $matches[1];
$attrib = parse_attrib_string($matches[2]);
$end = '>';
// Remove non-printable characters in URL (#1487805)
if ($attrib['href'])
$attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
$tempurl = 'tmp-' . md5($attrib['href']) . '.css';
$_SESSION['modcssurls'][$tempurl] = $attrib['href'];
$attrib['href'] = $RCMAIL->url(array('task' => 'utils', 'action' => 'modcss', 'u' => $tempurl, 'c' => $GLOBALS['rcmail_html_container_id']));
$end = ' />';
}
else if (preg_match('/^mailto:'.$EMAIL_PATTERN.'(\?[^"\'>]+)?/i', $attrib['href'], $mailto)) {
$attrib['href'] = $mailto[0];
$attrib['onclick'] = sprintf(
"return %s.command('compose','%s',this)",
JS_OBJECT_NAME,
JQ($mailto[1].$mailto[3]));
}
else if (empty($attrib['href']) && !$attrib['name']) {
$attrib['href'] = './#NOP';
$attrib['onclick'] = 'return false';
}
else if (!empty($attrib['href']) && $attrib['href'][0] != '#') {
$attrib['target'] = '_blank';
}
// allowed attributes for a|link|area tags
$allow = array('href','name','target','onclick','id','class','style','title',
'rel','type','media','alt','coords','nohref','hreflang','shape');
return "<$tag" . html::attrib_string($attrib, $allow) . $end;
}
/**
* decode address string and re-format it as HTML links
*/
function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null, $title=null)
{
global $RCMAIL, $PRINT_MODE, $CONFIG;
$a_parts = rcube_mime::decode_address_list($input, null, true, $default_charset);
if (!sizeof($a_parts))
return $input;
$c = count($a_parts);
$j = 0;
$out = '';
$allvalues = array();
if ($addicon && !isset($_SESSION['writeable_abook'])) {
$_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
}
foreach ($a_parts as $part) {
$j++;
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
// IDNA ASCII to Unicode
if ($name == $mailto)
$name = rcube_idn_to_utf8($name);
if ($string == $mailto)
$string = rcube_idn_to_utf8($string);
$mailto = rcube_idn_to_utf8($mailto);
if ($PRINT_MODE) {
$out .= ($out ? ', ' : '') . sprintf('%s &lt;%s&gt;', Q($name), $mailto);
// for printing we display all addresses
continue;
}
else if (check_email($part['mailto'], false)) {
if ($linked) {
$address = html::a(array(
'href' => 'mailto:'.$mailto,
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
'title' => $mailto,
'class' => "rcmContactAddress",
),
Q($name ? $name : $mailto));
}
else {
$address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
Q($name ? $name : $mailto));
}
if ($addicon && $_SESSION['writeable_abook']) {
$address .= html::a(array(
'href' => "#add",
'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, $string),
'title' => rcube_label('addtoaddressbook'),
'class' => 'rcmaddcontact',
),
html::img(array(
'src' => $CONFIG['skin_path'] . $addicon,
'alt' => "Add contact",
)));
}
}
else {
$address = '';
if ($name)
$address .= Q($name);
if ($mailto)
$address .= (strlen($address) ? ' ' : '') . sprintf('&lt;%s&gt;', Q($mailto));
}
$address = html::span('adr', $address);
$allvalues[] = $address;
if (!$moreadrs)
$out .= ($out ? ', ' : '') . $address;
if ($max && $j == $max && $c > $j) {
if ($linked) {
$moreadrs = $c - $j;
}
else {
$out .= '...';
break;
}
}
}
if ($moreadrs) {
$out .= ' ' . html::a(array(
'href' => '#more',
'class' => 'morelink',
'onclick' => sprintf("return %s.show_popup_dialog('%s','%s')",
JS_OBJECT_NAME,
JQ(join(', ', $allvalues)),
JQ($title))
),
Q(rcube_label(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs)))));
}
return $out;
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prpending the lines
* with >
*
* @param string Text to wrap
* @param int The line width
* @return string The wrapped text
*/
function rcmail_wrap_and_quote($text, $length = 72)
{
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max = max(75, $length + 8);
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
// don't wrap already quoted lines
if ($line[0] == '>')
$line = '>' . rtrim($line);
else if (mb_strlen($line) > $max) {
$newline = '';
foreach(explode("\n", rc_wordwrap($line, $length - 2)) as $l) {
if (strlen($l))
$newline .= '> ' . $l . "\n";
else
$newline .= ">\n";
}
$line = rtrim($newline);
}
else
$line = '> ' . $line;
// Append the line
$out .= $line . "\n";
}
return rtrim($out, "\n");
}
function rcmail_draftinfo_encode($p)
{
$parts = array();
foreach ($p as $key => $val)
$parts[] = $key . '=' . ($key == 'folder' ? base64_encode($val) : $val);
return join('; ', $parts);
}
function rcmail_draftinfo_decode($str)
{
$info = array();
foreach (preg_split('/;\s+/', $str) as $part) {
list($key, $val) = explode('=', $part, 2);
if ($key == 'folder')
$val = base64_decode($val);
$info[$key] = $val;
}
return $info;
}
function rcmail_message_part_controls($attrib)
{
global $MESSAGE, $RCMAIL;
$part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
return '';
$part = $MESSAGE->mime_parts[$part];
$table = new html_table(array('cols' => 3));
$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($filename));
$table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
}
$table->add('title', Q(rcube_label('filesize')));
$table->add('header', Q($RCMAIL->message_part_size($part)));
return $table->show($attrib);
}
function rcmail_message_part_frame($attrib)
{
global $MESSAGE;
$part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
$ctype_primary = strtolower($part->ctype_primary);
$attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
return html::iframe($attrib);
}
/**
* clear message composing settings
*/
function rcmail_compose_cleanup($id)
{
if (!isset($_SESSION['compose_data_'.$id]))
return;
$rcmail = rcmail::get_instance();
$rcmail->plugins->exec_hook('attachments_cleanup', array('group' => $id));
$rcmail->session->remove('compose_data_'.$id);
}
/**
* Send the MDN response
*
* @param mixed $message Original message object (rcube_message) or UID
* @param array $smtp_error SMTP error array (reference)
*
* @return boolean Send status
*/
function rcmail_send_mdn($message, &$smtp_error)
{
global $RCMAIL;
if (!is_object($message) || !is_a($message, 'rcube_message'))
$message = new rcube_message($message);
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
($RCMAIL->storage->check_permflag('MDNSENT') || $RCMAIL->storage->check_permflag('*')))
{
$identity = $RCMAIL->user->get_identity();
$sender = format_email_recipient($identity['email'], $identity['name']);
$recipient = array_shift(rcube_mime::decode_address_list(
$message->headers->mdn_to, 1, true, $message->headers->charset));
$mailto = $recipient['mailto'];
$compose = new Mail_mime("\r\n");
$compose->setParam('text_encoding', 'quoted-printable');
$compose->setParam('html_encoding', 'quoted-printable');
$compose->setParam('head_encoding', 'quoted-printable');
$compose->setParam('head_charset', RCMAIL_CHARSET);
$compose->setParam('html_charset', RCMAIL_CHARSET);
$compose->setParam('text_charset', RCMAIL_CHARSET);
// compose headers array
$headers = array(
'Date' => rcmail_user_date(),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => rcube_label('receiptread') . ': ' . $message->subject,
'Message-ID' => rcmail_gen_message_id(),
'X-Sender' => $identity['email'],
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
);
if ($agent = $RCMAIL->config->get('useragent'))
$headers['User-Agent'] = $agent;
$body = rcube_label("yourmessage") . "\r\n\r\n" .
"\t" . rcube_label("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
"\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
"\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" .
"\r\n" . rcube_label("receiptnote") . "\r\n";
$ua = $RCMAIL->config->get('useragent', "Roundcube Webmail (Version ".RCMAIL_VERSION.")");
$report = "Reporting-UA: $ua\r\n";
if ($message->headers->to)
$report .= "Original-Recipient: {$message->headers->to}\r\n";
$report .= "Final-Recipient: rfc822; {$identity['email']}\r\n" .
"Original-Message-ID: {$message->headers->messageID}\r\n" .
"Disposition: manual-action/MDN-sent-manually; displayed\r\n";
$compose->headers($headers);
$compose->setContentType('multipart/report', array('report-type'=> 'disposition-notification'));
$compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
$sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file);
if ($sent)
{
$RCMAIL->storage->set_flag($message->uid, 'MDNSENT');
return true;
}
}
return false;
}
// Fixes some content-type names
function rcmail_fix_mimetype($name)
{
// Some versions of Outlook create garbage Content-Type:
// application/pdf.A520491B_3BF7_494D_8855_7FAC2C6C0608
if (preg_match('/^application\/pdf.+/', $name))
$name = 'application/pdf';
return $name;
}
function rcmail_search_filter($attrib)
{
global $OUTPUT, $CONFIG;
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmlistfilter';
$attrib['onchange'] = JS_OBJECT_NAME.'.filter_mailbox(this.value)';
/*
RFC3501 (6.4.4): 'ALL', 'RECENT',
'ANSWERED', 'DELETED', 'FLAGGED', 'SEEN',
'UNANSWERED', 'UNDELETED', 'UNFLAGGED', 'UNSEEN',
'NEW', // = (RECENT UNSEEN)
'OLD' // = NOT RECENT
*/
$select_filter = new html_select($attrib);
$select_filter->add(rcube_label('all'), 'ALL');
$select_filter->add(rcube_label('unread'), 'UNSEEN');
$select_filter->add(rcube_label('flagged'), 'FLAGGED');
$select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
if (!$CONFIG['skip_deleted']) {
$select_filter->add(rcube_label('deleted'), 'DELETED');
$select_filter->add(rcube_label('undeleted'), 'UNDELETED');
}
$select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
$select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
$select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
$select_filter->add(rcube_label('priority').': '.rcube_label('low'), 'HEADER X-PRIORITY 4');
$select_filter->add(rcube_label('priority').': '.rcube_label('lowest'), 'HEADER X-PRIORITY 5');
$out = $select_filter->show($_SESSION['search_filter']);
$OUTPUT->add_gui_object('search_filter', $attrib['id']);
return $out;
}
function rcmail_message_error($uid=null)
{
global $RCMAIL;
// Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->storage->get_folder();
$RCMAIL->output->set_env('mailbox', $mbox_name);
$RCMAIL->output->set_env('uid', null);
}
// display error message
$RCMAIL->output->show_message('messageopenerror', 'error');
// ... display message error page
$RCMAIL->output->send('messageerror');
}
// register UI objects
$OUTPUT->add_handlers(array(
'mailboxlist' => 'rcmail_mailbox_list',
'messages' => 'rcmail_message_list',
'messagecountdisplay' => 'rcmail_messagecount_display',
'quotadisplay' => 'rcmail_quota_display',
'mailboxname' => 'rcmail_mailbox_name_display',
'messageheaders' => 'rcmail_message_headers',
'messagefullheaders' => 'rcmail_message_full_headers',
'messagebody' => 'rcmail_message_body',
'messagecontentframe' => 'rcmail_messagecontent_frame',
'messagepartframe' => 'rcmail_message_part_frame',
'messagepartcontrols' => 'rcmail_message_part_controls',
'searchfilter' => 'rcmail_search_filter',
'searchform' => array($OUTPUT, 'search_form'),
));
// register action aliases
$RCMAIL->register_action_map(array(
'refresh' => 'check_recent.inc',
'preview' => 'show.inc',
'print' => 'show.inc',
'moveto' => 'move_del.inc',
'delete' => 'move_del.inc',
'send' => 'sendmail.inc',
'expunge' => 'folders.inc',
'purge' => 'folders.inc',
'remove-attachment' => 'attachments.inc',
'display-attachment' => 'attachments.inc',
'upload' => 'attachments.inc',
'group-expand' => 'autocomplete.inc',
));

File Metadata

Mime Type
text/x-diff
Expires
Mon, Feb 2, 9:24 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
427033
Default Alt Text
(269 KB)

Event Timeline