Page MenuHomePhorge

No OneTemporary

Size
247 KB
Referenced Files
None
Subscribers
None
diff --git a/index.php b/index.php
index 5d5257142..2d220dede 100644
--- a/index.php
+++ b/index.php
@@ -1,308 +1,308 @@
<?php
/*
+-------------------------------------------------------------------------+
| Roundcube Webmail IMAP Client |
| Version 1.0-git |
| |
| Copyright (C) 2005-2013, The Roundcube Dev Team |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License (with exceptions |
| for skins & plugins) as published by the Free Software Foundation, |
| either version 3 of the License, or (at your option) any later version. |
| |
| This file forms part of the Roundcube Webmail Software for which the |
| following exception is added: Plugins and Skins which merely make |
| function calls to the Roundcube Webmail Software, and for that purpose |
| include it by reference shall not be considered modifications of |
| the software. |
| |
| If you wish to use this file in another project or create a modified |
| version that will not be part of the Roundcube Webmail Software, you |
| may remove the exception above and use this source code under the |
| original version of the license. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see http://www.gnu.org/licenses/. |
| |
+-------------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-------------------------------------------------------------------------+
*/
// include environment
require_once 'program/include/iniset.php';
// init application, start session, init output class, etc.
-$RCMAIL = rcmail::get_instance();
+$RCMAIL = rcmail::get_instance($GLOBALS['env']);
// Make the whole PHP output non-cacheable (#1487797)
$RCMAIL->output->nocacheing_headers();
// turn on output buffering
ob_start();
// check if config files had errors
if ($err_str = $RCMAIL->config->get_error()) {
rcmail::raise_error(array(
'code' => 601,
'type' => 'php',
'message' => $err_str), false, true);
}
// check DB connections and exit on failure
if ($err_str = $RCMAIL->db->is_error()) {
rcmail::raise_error(array(
'code' => 603,
'type' => 'db',
'message' => $err_str), FALSE, TRUE);
}
// error steps
if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) {
rcmail::raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
}
// check if https is required (for login) and redirect if necessary
if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) {
$https_port = is_bool($force_https) ? 443 : $force_https;
if (!rcube_utils::https_check($https_port)) {
$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
$host .= ($https_port != 443 ? ':' . $https_port : '');
header('Location: https://' . $host . $_SERVER['REQUEST_URI']);
exit;
}
}
// trigger startup plugin hook
$startup = $RCMAIL->plugins->exec_hook('startup', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
$RCMAIL->set_task($startup['task']);
$RCMAIL->action = $startup['action'];
// try to log in
if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
$request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_utils::INPUT_POST, 'login');
// purge the session in case of new login when a session already exists
$RCMAIL->kill_session();
$auth = $RCMAIL->plugins->exec_hook('authenticate', array(
'host' => $RCMAIL->autoselect_host(),
'user' => trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST)),
'pass' => rcube_utils::get_input_value('_pass', rcube_utils::INPUT_POST, true,
$RCMAIL->config->get('password_charset', 'ISO-8859-1')),
'cookiecheck' => true,
'valid' => $request_valid,
));
// Login
if ($auth['valid'] && !$auth['abort'] &&
$RCMAIL->login($auth['user'], $auth['pass'], $auth['host'], $auth['cookiecheck'])
) {
// create new session ID, don't destroy the current session
// it was destroyed already by $RCMAIL->kill_session() above
$RCMAIL->session->remove('temp');
$RCMAIL->session->regenerate_id(false);
// send auth cookie if necessary
$RCMAIL->session->set_auth_cookie();
// log successful login
$RCMAIL->log_login();
// restore original request parameters
$query = array();
if ($url = rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)) {
parse_str($url, $query);
// prevent endless looping on login page
if ($query['_task'] == 'login')
unset($query['_task']);
// prevent redirect to compose with specified ID (#1488226)
if ($query['_action'] == 'compose' && !empty($query['_id']))
$query = array();
}
// allow plugins to control the redirect url after login success
$redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('_task' => 'mail'));
unset($redir['abort'], $redir['_err']);
// send redirect
$OUTPUT->redirect($redir);
}
else {
if (!$auth['valid']) {
$error_code = RCMAIL::ERROR_INVALID_REQUEST;
}
else {
$error_code = $auth['error'] ? $auth['error'] : $RCMAIL->login_error();
}
$error_labels = array(
RCMAIL::ERROR_STORAGE => 'storageerror',
RCMAIL::ERROR_COOKIES_DISABLED => 'cookiesdisabled',
RCMAIL::ERROR_INVALID_REQUEST => 'invalidrequest',
RCMAIL::ERROR_INVALID_HOST => 'invalidhost',
);
$error_message = $error_labels[$error_code] ? $error_labels[$error_code] : 'loginfailed';
$OUTPUT->show_message($error_message, 'warning');
$RCMAIL->plugins->exec_hook('login_failed', array(
'code' => $error_code, 'host' => $auth['host'], 'user' => $auth['user']));
$RCMAIL->kill_session();
}
}
// end session (after optional referer check)
else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcube_utils::check_referer())) {
$userdata = array(
'user' => $_SESSION['username'],
'host' => $_SESSION['storage_host'],
'lang' => $RCMAIL->user->language,
);
$OUTPUT->show_message('loggedout');
$RCMAIL->logout_actions();
$RCMAIL->kill_session();
$RCMAIL->plugins->exec_hook('logout_after', $userdata);
}
// check session and auth cookie
else if ($RCMAIL->task != 'login' && $_SESSION['user_id'] && $RCMAIL->action != 'send') {
if (!$RCMAIL->session->check_auth()) {
$RCMAIL->kill_session();
$session_error = true;
}
}
// not logged in -> show login page
if (empty($RCMAIL->user->ID)) {
// log session failures
$task = rcube_utils::get_input_value('_task', rcube_utils::INPUT_GPC);
if ($task && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) {
$RCMAIL->session->log("Aborted session " . $sess_id . "; no valid session data found");
$session_error = true;
}
if ($OUTPUT->ajax_call)
$OUTPUT->redirect(array('_err' => 'session'), 2000);
if (!empty($_REQUEST['_framed']))
$OUTPUT->command('redirect', $RCMAIL->url(array('_err' => 'session')));
// check if installer is still active
if ($RCMAIL->config->get('enable_installer') && is_readable('./installer/index.php')) {
$OUTPUT->add_footer(html::div(array('style' => "background:#ef9398; border:2px solid #dc5757; padding:0.5em; margin:2em auto; width:50em"),
html::tag('h2', array('style' => "margin-top:0.2em"), "Installer script is still accessible") .
html::p(null, "The install script of your Roundcube installation is still stored in its default location!") .
html::p(null, "Please <b>remove</b> the whole <tt>installer</tt> folder from the Roundcube directory because .
these files may expose sensitive configuration data like server passwords and encryption keys
to the public. Make sure you cannot access the <a href=\"./installer/\">installer script</a> from your browser.")
)
);
}
if ($session_error || $_REQUEST['_err'] == 'session')
$OUTPUT->show_message('sessionerror', 'error', null, true, -1);
$plugin = $RCMAIL->plugins->exec_hook('unauthenticated', array('task' => 'login', 'error' => $session_error));
$RCMAIL->set_task($plugin['task']);
$OUTPUT->send($plugin['task']);
}
// CSRF prevention
else {
// don't check for valid request tokens in these actions
$request_check_whitelist = array('login'=>1, 'spell'=>1, 'spell_html'=>1);
if (!$request_check_whitelist[$RCMAIL->action]) {
// check client X-header to verify request origin
if ($OUTPUT->ajax_call) {
if (rcube_utils::request_header('X-Roundcube-Request') != $RCMAIL->get_request_token()) {
header('HTTP/1.1 403 Forbidden');
die("Invalid Request");
}
}
// check request token in POST form submissions
else if (!empty($_POST) && !$RCMAIL->check_request()) {
$OUTPUT->show_message('invalidrequest', 'error');
$OUTPUT->send($RCMAIL->task);
}
// check referer if configured
if ($RCMAIL->config->get('referer_check') && !rcube_utils::check_referer()) {
raise_error(array(
'code' => 403, 'type' => 'php',
'message' => "Referer check failed"), true, true);
}
}
}
// we're ready, user is authenticated and the request is safe
$plugin = $RCMAIL->plugins->exec_hook('ready', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
$RCMAIL->set_task($plugin['task']);
$RCMAIL->action = $plugin['action'];
// handle special actions
if ($RCMAIL->action == 'keep-alive') {
$OUTPUT->reset();
$RCMAIL->plugins->exec_hook('keep_alive', array());
$OUTPUT->send();
}
else if ($RCMAIL->action == 'save-pref') {
include INSTALL_PATH . 'program/steps/utils/save_pref.inc';
}
// include task specific functions
if (is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/func.inc'))
include_once $incfile;
// allow 5 "redirects" to another action
$redirects = 0; $incstep = null;
while ($redirects < 5) {
// execute a plugin action
if ($RCMAIL->plugins->is_plugin_task($RCMAIL->task)) {
if (!$RCMAIL->action) $RCMAIL->action = 'index';
$RCMAIL->plugins->exec_action($RCMAIL->task.'.'.$RCMAIL->action);
break;
}
else if (preg_match('/^plugin\./', $RCMAIL->action)) {
$RCMAIL->plugins->exec_action($RCMAIL->action);
break;
}
// try to include the step file
else if (($stepfile = $RCMAIL->get_action_file())
&& is_file($incfile = INSTALL_PATH . 'program/steps/'.$RCMAIL->task.'/'.$stepfile)
) {
// include action file only once (in case it don't exit)
include_once $incfile;
$redirects++;
}
else {
break;
}
}
if ($RCMAIL->action == 'refresh') {
$RCMAIL->plugins->exec_hook('refresh', array());
}
// parse main template (default)
$OUTPUT->send($RCMAIL->task);
// if we arrive here, something went wrong
rcmail::raise_error(array(
'code' => 404,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Invalid request"), true, true);
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index d430cd350..800eddac6 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1,1947 +1,1953 @@
<?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 = './';
+ public $filename = '';
private $address_books = array();
private $action_map = array();
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
*
+ * @param string Environment name to run (e.g. live, dev, test)
* @return rcmail The one and only instance
*/
- static function get_instance()
+ static function get_instance($env = '')
{
if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
- self::$instance = new rcmail();
+ self::$instance = new rcmail($env);
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);
+ // set filename if not index.php
+ if (($basename = basename($_SERVER['SCRIPT_FILENAME'])) && $basename != 'index.php')
+ $this->filename = $basename;
+
// 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') {
// we reset list page when switching to another task
// but only to the main task interface - empty action (#1489076)
// this will prevent from unintentional page reset on cross-task requests
if ($this->session && $_SESSION['task'] != $this->task && empty($this->action))
$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, true);
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 (version_compare(PHP_VERSION, '5.5.0', '<') && 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) {
// there's no default, just return
if ($default) {
return null;
}
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 = (string) $this->config->get('address_book_type');
$ldap_config = (array) $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 (!empty($abook_type) && strtolower($abook_type) != 'ldap') {
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 (!empty($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' => !empty($prop['groups']) || !empty($prop['group_filters']),
'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 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 Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
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 Reference to JSON output object
*/
public function json_init()
{
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(, $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();
}
/**
* 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 = './';
+ $url = './' . $this->filename;
$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();
}
// 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);
}
/**
* 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($attrib);
// 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->include_script('treelist.js');
$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');
$realnames = $this->config->get('show_real_foldernames');
$out = '';
foreach ($arrFolders as $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_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));
if (!empty($folder['folders'])) {
$out .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), '&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 $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
* @param bool $with_path Enable path localization
*
* @return string Localized folder name in UTF-8 encoding
*/
public function localize_foldername($name, $with_path = true)
{
$realnames = $this->config->get('show_real_foldernames');
// try to localize path of the folder
if ($with_path && !$realnames) {
$storage = $this->get_storage();
$delimiter = $storage->get_hierarchy_delimiter();
$path = explode($delimiter, $name);
$count = count($path);
if ($count > 1) {
for ($i = 0; $i < $count; $i++) {
$folder = implode($delimiter, array_slice($path, 0, -$i));
if ($folder_class = $this->folder_classname($folder)) {
$name = implode($delimiter, array_slice($path, $count - $i));
return $this->gettext($folder_class) . $delimiter . rcube_charset::convert($name, 'UTF7-IMAP');
}
}
}
}
if (!$realnames && ($folder_class = $this->folder_classname($name))) {
return $this->gettext($folder_class);
}
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'] > 0) {
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 {
$unlimited = $this->config->get('quota_zero_as_unlimited');
$quota_result['title'] = $this->gettext($unlimited ? 'unlimited' : '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
* @param string $suffix Message label suffix
*/
public function display_server_error($fallback = null, $fallback_args = null, $suffix = '')
{
$err_code = $this->storage->get_error_code();
$res_code = $this->storage->get_response_code();
$args = array();
if ($res_code == rcube_storage::NOPERM) {
$error = 'errornoperm';
}
else if ($res_code == rcube_storage::READONLY) {
$error = 'errorreadonly';
}
else if ($res_code == rcube_storage::OVERQUOTA) {
$error = 'errorroverquota';
}
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) {
$error = 'errornoperm';
}
// try to detect full mailbox problem and display appropriate message
// there can be e.g. "Quota exceeded" or "quotum would exceed"
else if (stripos($err_str, 'quot') !== false && stripos($err_str, 'exceed') !== false) {
$error = 'erroroverquota';
}
else {
$error = 'servererrormsg';
$args = array('msg' => $err_str);
}
}
else if ($err_code < 0) {
$error = 'storageerror';
}
else if ($fallback) {
$error = $fallback;
$args = $fallback_args;
}
if ($error) {
if ($suffix && $this->text_exists($error . $suffix)) {
$error .= $suffix;
}
$this->output->show_message($error, 'error', $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
$rfc1867 = filter_var(ini_get('apc.rfc1867'), FILTER_VALIDATE_BOOLEAN);
if ($rfc1867 && ($seconds = $this->config->get('upload_progress'))) {
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_html.php b/program/include/rcmail_output_html.php
index c96a40ab9..998779509 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -1,1774 +1,1776 @@
<?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 Core
* @subpackage View
*/
class rcmail_output_html extends rcmail_output
{
public $type = 'html';
protected $message = null;
protected $js_env = array();
protected $js_labels = array();
protected $js_commands = array();
protected $skin_paths = array();
protected $template_name;
protected $scripts_path = '';
protected $script_files = array();
protected $css_files = array();
protected $scripts = array();
protected $default_template = "<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'));
$this->set_env('standard_windows', (bool) $this->config->get('standard_windows'));
// 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', filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN));
// 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);
if ($this->framed || !empty($_REQUEST['_framed']))
$this->set_env('framed', 1);
// add common javascripts
$this->add_script('var '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
// don't wait for page onload. Call init at the bottom of the page (delayed)
$this->add_script(self::JS_OBJECT_NAME.'.init();', 'docready');
$this->scripts_path = 'program/js/';
$this->include_script('jquery.min.js');
$this->include_script('common.js');
$this->include_script('app.js');
// register common UI objects
$this->add_handlers(array(
'loginform' => array($this, 'login_form'),
'preloader' => array($this, 'preloader'),
'username' => array($this, 'current_username'),
'message' => array($this, 'message_container'),
'charsetselector' => array($this, 'charset_selector'),
'aboutcontent' => array($this, 'about_content'),
));
}
/**
* Set environment variable
*
* @param string Property name
* @param mixed Property value
* @param boolean True if this property should be added to client environment
*/
public function set_env($name, $value, $addtojs = true)
{
$this->env[$name] = $value;
if ($addtojs || isset($this->js_env[$name])) {
$this->js_env[$name] = $value;
}
}
/**
* Getter for the current page title
*
* @return string The page title
*/
protected function get_pagetitle()
{
if (!empty($this->pagetitle)) {
$title = $this->pagetitle;
}
else if ($this->env['task'] == 'login') {
$title = $this->app->gettext(array(
'name' => 'welcome',
'vars' => array('product' => $this->config->get('product_name')
)));
}
else {
$title = ucfirst($this->env['task']);
}
return $title;
}
/**
* Set skin
*/
public function set_skin($skin)
{
$valid = false;
$path = RCUBE_INSTALL_PATH . 'skins/';
if (!empty($skin) && is_dir($path . $skin) && is_readable($path . $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);
$this->base_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 = @file_get_contents(RCUBE_INSTALL_PATH . $skin_path . '/meta.json');
$meta = @json_decode($meta, true);
if ($meta['extends']) {
$path = RCUBE_INSTALL_PATH . 'skins/';
if (is_dir($path . $meta['extends']) && is_readable($path . $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)
{
foreach ($this->skin_paths as $skin_path) {
$filename = RCUBE_INSTALL_PATH . $skin_path . '/templates/' . $name . '.html';
if ((is_file($filename) && is_readable($filename))
|| ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]))
) {
return true;
}
}
return false;
}
/**
* 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 = null, $add_path = null)
{
$skin_paths = $this->skin_paths;
if ($add_path)
array_unshift($skin_paths, $add_path);
foreach ($skin_paths as $skin_path) {
$path = realpath($skin_path . $file);
if (is_file($path)) {
return $skin_path . $file;
}
}
return false;
}
/**
* Register a GUI object to the client script
*
* @param string Object name
* @param string Object ID
* @return void
*/
public function add_gui_object($obj, $id)
{
$this->add_script(self::JS_OBJECT_NAME.".gui_object('$obj', '$id');");
}
/**
* Call a client method
*
* @param string Method to call
* @param ... Additional arguments
*/
public function command()
{
$cmd = func_get_args();
if (strpos($cmd[0], 'plugin.') !== false)
$this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
else
$this->js_commands[] = $cmd;
}
/**
* Add a localized label to the client environment
*/
public function add_label()
{
$args = func_get_args();
if (count($args) == 1 && is_array($args[0]))
$args = $args[0];
foreach ($args as $name) {
$this->js_labels[$name] = $this->app->gettext($name);
}
}
/**
* Invoke display_message command
*
* @param string $message Message to display
* @param string $type Message type [notice|confirm|error]
* @param array $vars Key-value pairs to be replaced in localized text
* @param boolean $override Override last set message
* @param int $timeout Message display time in seconds
* @uses self::command()
*/
public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
{
if ($override || !$this->message) {
if ($this->app->text_exists($message)) {
if (!empty($vars))
$vars = array_map('Q', $vars);
$msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
}
else
$msgtext = $message;
$this->message = $message;
$this->command('display_message', $msgtext, $type, $timeout * 1000);
}
}
/**
* Delete all stored env variables and commands
*
* @param bool $all Reset all env variables (including internal)
*/
public function reset($all = false)
{
$env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
parent::reset();
// let some env variables survive
$this->env = $this->js_env = $env;
$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 = true;
$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('iframe_loaded', $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
if ($commands = $this->get_js_commands()) {
$js = $this->framed ? "if (window.parent) {\n" : '';
$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)
$skin_dir = preg_replace('!^plugins/!', '', $skin_path);
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 .= 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.'.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];
$file = preg_replace('!^/this/!', '/', $file);
// 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 rcmail_output_html::parse_xml()
* @since 0.1-rc1
*/
public function just_parse($input)
{
$input = $this->parse_conditions($input);
$input = $this->parse_xml($input);
return $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 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 $this->eval_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;
}
/**
* Parse & evaluate a given expression and return its result.
*
* @param string Expression statement
*
* @return mixed Expression result
*/
protected function eval_expression ($expression)
{
$expression = 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']",
"\$app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
"\$env['\\1']",
"rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
"\$_COOKIE['\\1']",
"\$browser->{'\\1'}",
$this->template_name,
),
$expression
);
$fn = create_function('$app,$browser,$env', "return ($expression);");
if (!$fn) {
rcube::raise_error(array(
'code' => 505,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Expression parse error on: ($expression)"), true, false);
return null;
}
return $fn($this->app, $this->browser, $this->env);
}
/**
* 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'] = $this->eval_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 (!empty($attrib['skin_path'])) $attrib['skinpath'] = $attrib['skin_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':
return html::quote($this->eval_expression($attrib['expression']));
// 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;
+
+ case 'form':
+ return $this->form_tag($attrib);
}
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');",
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)", 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)",
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);
}
if ($attrib['wrapper']) {
$out = html::tag($attrib['wrapper'], null, $out);
}
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"),
+ $attrib + array('action' => $this->app->comm_path, '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 = key($default_host)) !== null) {
$hide_host = true;
$input_host = new html_hiddenfield(array(
'name' => '_host', 'id' => 'rcmloginhost', 'value' => is_numeric($host) ? $default_host[$host] : $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' => 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.php b/program/lib/Roundcube/rcube.php
index 05a94f3be..ce97cd0a5 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1,1550 +1,1551 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Framework base class providing core functions and holding |
| instances of all 'global' objects like db- and storage-connections |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Base class of the Roundcube Framework
* implemented as singleton
*
* @package Framework
* @subpackage Core
*/
class rcube
{
const INIT_WITH_DB = 1;
const INIT_WITH_PLUGINS = 2;
/**
* Singleton instace of rcube
*
* @var rcube
*/
static protected $instance;
/**
* Stores instance of rcube_config.
*
* @var rcube_config
*/
public $config;
/**
* Instace of database class.
*
* @var rcube_db
*/
public $db;
/**
* Instace of Memcache class.
*
* @var Memcache
*/
public $memcache;
/**
* Instace of rcube_session class.
*
* @var rcube_session
*/
public $session;
/**
* Instance of rcube_smtp class.
*
* @var rcube_smtp
*/
public $smtp;
/**
* Instance of rcube_storage class.
*
* @var rcube_storage
*/
public $storage;
/**
* Instance of rcube_output class.
*
* @var rcube_output
*/
public $output;
/**
* Instance of rcube_plugin_api.
*
* @var rcube_plugin_api
*/
public $plugins;
/* private/protected vars */
protected $texts;
protected $caches = array();
protected $shutdown_functions = array();
/**
* This implements the 'singleton' design pattern
*
* @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
+ * @param string Environment name to run (e.g. live, dev, test)
*
* @return rcube The one and only instance
*/
- static function get_instance($mode = 0)
+ static function get_instance($mode = 0, $env = '')
{
if (!self::$instance) {
- self::$instance = new rcube();
+ self::$instance = new rcube($env);
self::$instance->init($mode);
}
return self::$instance;
}
/**
* Private constructor
*/
- protected function __construct()
+ protected function __construct($env = '')
{
// load configuration
- $this->config = new rcube_config;
+ $this->config = new rcube_config($env);
$this->plugins = new rcube_dummy_plugin_api;
register_shutdown_function(array($this, 'shutdown'));
}
/**
* Initial startup function
*/
protected function init($mode = 0)
{
// initialize syslog
if ($this->config->get('log_driver') == 'syslog') {
$syslog_id = $this->config->get('syslog_id', 'roundcube');
$syslog_facility = $this->config->get('syslog_facility', LOG_USER);
openlog($syslog_id, LOG_ODELAY, $syslog_facility);
}
// connect to database
if ($mode & self::INIT_WITH_DB) {
$this->get_dbh();
}
// create plugin API and load plugins
if ($mode & self::INIT_WITH_PLUGINS) {
$this->plugins = rcube_plugin_api::get_instance();
}
}
/**
* Get the current database connection
*
* @return rcube_db Database object
*/
public function get_dbh()
{
if (!$this->db) {
$config_all = $this->config->all();
$this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
$this->db->set_debug((bool)$config_all['sql_debug']);
}
return $this->db;
}
/**
* Get global handle for memcache access
*
* @return object Memcache
*/
public function get_memcache()
{
if (!isset($this->memcache)) {
// no memcache support in PHP
if (!class_exists('Memcache')) {
$this->memcache = false;
return false;
}
$this->memcache = new Memcache;
$this->mc_available = 0;
// add all configured hosts to pool
$pconnect = $this->config->get('memcache_pconnect', true);
foreach ($this->config->get('memcache_hosts', array()) as $host) {
if (substr($host, 0, 7) != 'unix://') {
list($host, $port) = explode(':', $host);
if (!$port) $port = 11211;
}
else {
$port = 0;
}
$this->mc_available += intval($this->memcache->addServer(
$host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
}
// test connection and failover (will result in $this->mc_available == 0 on complete failure)
$this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
if (!$this->mc_available) {
$this->memcache = false;
}
}
return $this->memcache;
}
/**
* Callback for memcache failure
*/
public function memcache_failure($host, $port)
{
static $seen = array();
// only report once
if (!$seen["$host:$port"]++) {
$this->mc_available--;
self::raise_error(array(
'code' => 604, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => "Memcache failure on host $host:$port"),
true, false);
}
}
/**
* Initialize and get cache object
*
* @param string $name Cache identifier
* @param string $type Cache type ('db', 'apc' or 'memcache')
* @param string $ttl Expiration time for cache items
* @param bool $packed Enables/disables data serialization
*
* @return rcube_cache Cache object
*/
public function get_cache($name, $type='db', $ttl=0, $packed=true)
{
if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
$this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
}
return $this->caches[$name];
}
/**
* Initialize and get shared cache object
*
* @param string $name Cache identifier
* @param bool $packed Enables/disables data serialization
*
* @return rcube_cache_shared Cache object
*/
public function get_cache_shared($name, $packed=true)
{
$shared_name = "shared_$name";
if (!array_key_exists($shared_name, $this->caches)) {
$opt = strtolower($name) . '_cache';
$type = $this->config->get($opt);
$ttl = $this->config->get($opt . '_ttl');
if (!$type) {
// cache is disabled
return $this->caches[$shared_name] = null;
}
if ($ttl === null) {
$ttl = $this->config->get('shared_cache_ttl', '10d');
}
$this->caches[$shared_name] = new rcube_cache_shared($type, $name, $ttl, $packed);
}
return $this->caches[$shared_name];
}
/**
* Create SMTP object and connect to server
*
* @param boolean True if connection should be established
*/
public function smtp_init($connect = false)
{
$this->smtp = new rcube_smtp();
if ($connect) {
$this->smtp->connect();
}
}
/**
* Initialize and get storage object
*
* @return rcube_storage Storage object
*/
public function get_storage()
{
// already initialized
if (!is_object($this->storage)) {
$this->storage_init();
}
return $this->storage;
}
/**
* Initialize storage object
*/
public function storage_init()
{
// already initialized
if (is_object($this->storage)) {
return;
}
$driver = $this->config->get('storage_driver', 'imap');
$driver_class = "rcube_{$driver}";
if (!class_exists($driver_class)) {
self::raise_error(array(
'code' => 700, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Storage driver class ($driver) not found!"),
true, true);
}
// Initialize storage object
$this->storage = new $driver_class;
// for backward compat. (deprecated, will be removed)
$this->imap = $this->storage;
// enable caching of mail data
$storage_cache = $this->config->get("{$driver}_cache");
$messages_cache = $this->config->get('messages_cache');
// for backward compatybility
if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
$storage_cache = 'db';
$messages_cache = true;
}
if ($storage_cache) {
$this->storage->set_caching($storage_cache);
}
if ($messages_cache) {
$this->storage->set_messages_caching(true);
}
// set pagesize from config
$pagesize = $this->config->get('mail_pagesize');
if (!$pagesize) {
$pagesize = $this->config->get('pagesize', 50);
}
$this->storage->set_pagesize($pagesize);
// set class options
$options = array(
'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
'auth_cid' => $this->config->get("{$driver}_auth_cid"),
'auth_pw' => $this->config->get("{$driver}_auth_pw"),
'debug' => (bool) $this->config->get("{$driver}_debug"),
'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
'disabled_caps' => $this->config->get("{$driver}_disabled_caps"),
'timeout' => (int) $this->config->get("{$driver}_timeout"),
'skip_deleted' => (bool) $this->config->get('skip_deleted'),
'driver' => $driver,
);
if (!empty($_SESSION['storage_host'])) {
$options['host'] = $_SESSION['storage_host'];
$options['user'] = $_SESSION['username'];
$options['port'] = $_SESSION['storage_port'];
$options['ssl'] = $_SESSION['storage_ssl'];
$options['password'] = $this->decrypt($_SESSION['password']);
$_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
}
$options = $this->plugins->exec_hook("storage_init", $options);
// for backward compat. (deprecated, to be removed)
$options = $this->plugins->exec_hook("imap_init", $options);
$this->storage->set_options($options);
$this->set_storage_prop();
}
/**
* Set storage parameters.
* This must be done AFTER connecting to the server!
*/
protected function set_storage_prop()
{
$storage = $this->get_storage();
$storage->set_charset($this->config->get('default_charset', RCUBE_CHARSET));
if ($default_folders = $this->config->get('default_folders')) {
$storage->set_default_folders($default_folders);
}
if (isset($_SESSION['mbox'])) {
$storage->set_folder($_SESSION['mbox']);
}
if (isset($_SESSION['page'])) {
$storage->set_page($_SESSION['page']);
}
}
/**
* Create session object and start the session.
*/
public function session_init()
{
// session started (Installer?)
if (session_id()) {
return;
}
$sess_name = $this->config->get('session_name');
$sess_domain = $this->config->get('session_domain');
$sess_path = $this->config->get('session_path');
$lifetime = $this->config->get('session_lifetime', 0) * 60;
$is_secure = $this->config->get('use_https') || rcube_utils::https_check();
// set session domain
if ($sess_domain) {
ini_set('session.cookie_domain', $sess_domain);
}
// set session path
if ($sess_path) {
ini_set('session.cookie_path', $sess_path);
}
// set session garbage collecting time according to session_lifetime
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
}
ini_set('session.cookie_secure', $is_secure);
ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_httponly', 1);
// use database for storing session data
$this->session = new rcube_session($this->get_dbh(), $this->config);
$this->session->register_gc_handler(array($this, 'gc'));
$this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
$this->session->set_ip_check($this->config->get('ip_check'));
// start PHP session (if not in CLI mode)
if ($_SERVER['REMOTE_ADDR']) {
$this->session->start();
}
}
/**
* Garbage collector - cache/temp cleaner
*/
public function gc()
{
rcube_cache::gc();
rcube_cache_shared::gc();
$this->get_storage()->cache_gc();
$this->gc_temp();
}
/**
* Garbage collector function for temp files.
* Remove temp files older than two days
*/
public function gc_temp()
{
$tmp = unslashify($this->config->get('temp_dir'));
$expire = time() - 172800; // expire in 48 hours
if ($tmp && ($dir = opendir($tmp))) {
while (($fname = readdir($dir)) !== false) {
if ($fname{0} == '.') {
continue;
}
if (filemtime($tmp.'/'.$fname) < $expire) {
@unlink($tmp.'/'.$fname);
}
}
closedir($dir);
}
}
/**
* Runs garbage collector with probability based on
* session settings. This is intended for environments
* without a session.
*/
public function gc_run()
{
$probability = (int) ini_get('session.gc_probability');
$divisor = (int) ini_get('session.gc_divisor');
if ($divisor > 0 && $probability > 0) {
$random = mt_rand(1, $divisor);
if ($random <= $probability) {
$this->gc();
}
}
}
/**
* Get localized text in the desired language
*
* @param mixed $attrib Named parameters array or label name
* @param string $domain Label domain (plugin) name
*
* @return string Localized text
*/
public function gettext($attrib, $domain=null)
{
// load localization files if not done yet
if (empty($this->texts)) {
$this->load_language();
}
// extract attributes
if (is_string($attrib)) {
$attrib = array('name' => $attrib);
}
$name = $attrib['name'] ? $attrib['name'] : '';
// attrib contain text values: use them from now
if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
$this->texts[$name] = $setval;
}
// check for text with domain
if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
}
// text does not exist
else if (!($text = $this->texts[$name])) {
return "[$name]";
}
// replace vars in text
if (is_array($attrib['vars'])) {
foreach ($attrib['vars'] as $var_key => $var_value) {
$text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
}
}
// format output
if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
return ucfirst($text);
}
else if ($attrib['uppercase']) {
return mb_strtoupper($text);
}
else if ($attrib['lowercase']) {
return mb_strtolower($text);
}
return strtr($text, array('\n' => "\n"));
}
/**
* Check if the given text label exists
*
* @param string $name Label name
* @param string $domain Label domain (plugin) name or '*' for all domains
* @param string $ref_domain Sets domain name if label is found
*
* @return boolean True if text exists (either in the current language or in en_US)
*/
public function text_exists($name, $domain = null, &$ref_domain = null)
{
// load localization files if not done yet
if (empty($this->texts)) {
$this->load_language();
}
if (isset($this->texts[$name])) {
$ref_domain = '';
return true;
}
// any of loaded domains (plugins)
if ($domain == '*') {
foreach ($this->plugins->loaded_plugins() as $domain) {
if (isset($this->texts[$domain.'.'.$name])) {
$ref_domain = $domain;
return true;
}
}
}
// specified domain
else if ($domain) {
$ref_domain = $domain;
return isset($this->texts[$domain.'.'.$name]);
}
return false;
}
/**
* Load a localization package
*
* @param string Language ID
* @param array Additional text labels/messages
*/
public function load_language($lang = null, $add = array())
{
$lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
// load localized texts
if (empty($this->texts) || $lang != $_SESSION['language']) {
$this->texts = array();
// handle empty lines after closing PHP tag in localization files
ob_start();
// get english labels (these should be complete)
@include(RCUBE_LOCALIZATION_DIR . 'en_US/labels.inc');
@include(RCUBE_LOCALIZATION_DIR . 'en_US/messages.inc');
if (is_array($labels))
$this->texts = $labels;
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
// include user language files
if ($lang != 'en' && $lang != 'en_US' && is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
include_once(RCUBE_LOCALIZATION_DIR . $lang . '/labels.inc');
include_once(RCUBE_LOCALIZATION_DIR . $lang . '/messages.inc');
if (is_array($labels))
$this->texts = array_merge($this->texts, $labels);
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
}
ob_end_clean();
$_SESSION['language'] = $lang;
}
// append additional texts (from plugin)
if (is_array($add) && !empty($add)) {
$this->texts += $add;
}
}
/**
* Check the given string and return a valid language code
*
* @param string Language code
*
* @return string Valid language code
*/
protected function language_prop($lang)
{
static $rcube_languages, $rcube_language_aliases;
// user HTTP_ACCEPT_LANGUAGE if no language is specified
if (empty($lang) || $lang == 'auto') {
$accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$lang = str_replace('-', '_', $accept_langs[0]);
}
if (empty($rcube_languages)) {
@include(RCUBE_LOCALIZATION_DIR . 'index.inc');
}
// check if we have an alias for that language
if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
$lang = $rcube_language_aliases[$lang];
}
// try the first two chars
else if (!isset($rcube_languages[$lang])) {
$short = substr($lang, 0, 2);
// check if we have an alias for the short language code
if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
$lang = $rcube_language_aliases[$short];
}
// expand 'nn' to 'nn_NN'
else if (!isset($rcube_languages[$short])) {
$lang = $short.'_'.strtoupper($short);
}
}
if (!isset($rcube_languages[$lang]) || !is_dir(RCUBE_LOCALIZATION_DIR . $lang)) {
$lang = 'en_US';
}
return $lang;
}
/**
* Read directory program/localization and return a list of available languages
*
* @return array List of available localizations
*/
public function list_languages()
{
static $sa_languages = array();
if (!sizeof($sa_languages)) {
@include(RCUBE_LOCALIZATION_DIR . 'index.inc');
if ($dh = @opendir(RCUBE_LOCALIZATION_DIR)) {
while (($name = readdir($dh)) !== false) {
if ($name[0] == '.' || !is_dir(RCUBE_LOCALIZATION_DIR . $name)) {
continue;
}
if ($label = $rcube_languages[$name]) {
$sa_languages[$name] = $label;
}
}
closedir($dh);
}
}
return $sa_languages;
}
/**
* Encrypt using 3DES
*
* @param string $clear clear text input
* @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
* @param boolean $base64 whether or not to base64_encode() the result before returning
*
* @return string encrypted text
*/
public function encrypt($clear, $key = 'des_key', $base64 = true)
{
if (!$clear) {
return '';
}
/*-
* Add a single canary byte to the end of the clear text, which
* will help find out how much of padding will need to be removed
* upon decryption; see http://php.net/mcrypt_generic#68082
*/
$clear = pack("a*H2", $clear, "80");
if (function_exists('mcrypt_module_open') &&
($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
) {
$iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
$cipher = $iv . mcrypt_generic($td, $clear);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else {
@include_once 'des.inc';
if (function_exists('des')) {
$des_iv_size = 8;
$iv = $this->create_iv($des_iv_size);
$cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
}
else {
self::raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
), true, true);
}
}
return $base64 ? base64_encode($cipher) : $cipher;
}
/**
* Decrypt 3DES-encrypted string
*
* @param string $cipher encrypted text
* @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
* @param boolean $base64 whether or not input is base64-encoded
*
* @return string decrypted text
*/
public function decrypt($cipher, $key = 'des_key', $base64 = true)
{
if (!$cipher) {
return '';
}
$cipher = $base64 ? base64_decode($cipher) : $cipher;
if (function_exists('mcrypt_module_open') &&
($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
) {
$iv_size = mcrypt_enc_get_iv_size($td);
$iv = substr($cipher, 0, $iv_size);
// session corruption? (#1485970)
if (strlen($iv) < $iv_size) {
return '';
}
$cipher = substr($cipher, $iv_size);
mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
$clear = mdecrypt_generic($td, $cipher);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else {
@include_once 'des.inc';
if (function_exists('des')) {
$des_iv_size = 8;
$iv = substr($cipher, 0, $des_iv_size);
$cipher = substr($cipher, $des_iv_size);
$clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
}
else {
self::raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
), true, true);
}
}
/*-
* Trim PHP's padding and the canary byte; see note in
* rcube::encrypt() and http://php.net/mcrypt_generic#68082
*/
$clear = substr(rtrim($clear, "\0"), 0, -1);
return $clear;
}
/**
* Generates encryption initialization vector (IV)
*
* @param int Vector size
*
* @return string Vector string
*/
private function create_iv($size)
{
// mcrypt_create_iv() can be slow when system lacks entrophy
// we'll generate IV vector manually
$iv = '';
for ($i = 0; $i < $size; $i++) {
$iv .= chr(mt_rand(0, 255));
}
return $iv;
}
/**
* Build a valid URL to this instance of Roundcube
*
* @param mixed Either a string with the action or url parameters as key-value pairs
* @return string Valid application URL
*/
public function url($p)
{
// STUB: should be overloaded by the application
return '';
}
/**
* Function to be executed in script shutdown
* Registered with register_shutdown_function()
*/
public function shutdown()
{
foreach ($this->shutdown_functions as $function) {
call_user_func($function);
}
// write session data as soon as possible and before
// closing database connection, don't do this before
// registered shutdown functions, they may need the session
// Note: this will run registered gc handlers (ie. cache gc)
if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
$this->session->write_close();
}
if (is_object($this->smtp)) {
$this->smtp->disconnect();
}
foreach ($this->caches as $cache) {
if (is_object($cache)) {
$cache->close();
}
}
if (is_object($this->storage)) {
$this->storage->close();
}
}
/**
* Registers shutdown function to be executed on shutdown.
* The functions will be executed before destroying any
* objects like smtp, imap, session, etc.
*
* @param callback Function callback
*/
public function add_shutdown_function($function)
{
$this->shutdown_functions[] = $function;
}
/**
* Quote a given string.
* Shortcut function for rcube_utils::rep_specialchars_output()
*
* @return string HTML-quoted string
*/
public static function Q($str, $mode = 'strict', $newlines = true)
{
return rcube_utils::rep_specialchars_output($str, 'html', $mode, $newlines);
}
/**
* Quote a given string for javascript output.
* Shortcut function for rcube_utils::rep_specialchars_output()
*
* @return string JS-quoted string
*/
public static function JQ($str)
{
return rcube_utils::rep_specialchars_output($str, 'js');
}
/**
* Construct shell command, execute it and return output as string.
* Keywords {keyword} are replaced with arguments
*
* @param $cmd Format string with {keywords} to be replaced
* @param $values (zero, one or more arrays can be passed)
*
* @return output of command. shell errors not detectable
*/
public static function exec(/* $cmd, $values1 = array(), ... */)
{
$args = func_get_args();
$cmd = array_shift($args);
$values = $replacements = array();
// merge values into one array
foreach ($args as $arg) {
$values += (array)$arg;
}
preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
foreach ($matches as $tags) {
list(, $tag, $option, $key) = $tags;
$parts = array();
if ($option) {
foreach ((array)$values["-$key"] as $key => $value) {
if ($value === true || $value === false || $value === null) {
$parts[] = $value ? $key : "";
}
else {
foreach ((array)$value as $val) {
$parts[] = "$key " . escapeshellarg($val);
}
}
}
}
else {
foreach ((array)$values[$key] as $value) {
$parts[] = escapeshellarg($value);
}
}
$replacements[$tag] = join(" ", $parts);
}
// use strtr behaviour of going through source string once
$cmd = strtr($cmd, $replacements);
return (string)shell_exec($cmd);
}
/**
* Print or write debug messages
*
* @param mixed Debug message or data
*/
public static function console()
{
$args = func_get_args();
if (class_exists('rcube', false)) {
$rcube = self::get_instance();
$plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
if ($plugin['abort']) {
return;
}
$args = $plugin['args'];
}
$msg = array();
foreach ($args as $arg) {
$msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
}
self::write_log('console', join(";\n", $msg));
}
/**
* Append a line to a logfile in the logs directory.
* Date will be added automatically to the line.
*
* @param $name name of log file
* @param line Line to append
*/
public static function write_log($name, $line)
{
if (!is_string($line)) {
$line = var_export($line, true);
}
$date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
$log_driver = self::$instance ? self::$instance->config->get('log_driver') : null;
if (empty($date_format)) {
$date_format = 'd-M-Y H:i:s O';
}
$date = date($date_format);
// trigger logging hook
if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
$log = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
$name = $log['name'];
$line = $log['line'];
$date = $log['date'];
if ($log['abort'])
return true;
}
if ($log_driver == 'syslog') {
$prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
syslog($prio, $line);
return true;
}
// log_driver == 'file' is assumed here
$line = sprintf("[%s]: %s\n", $date, $line);
$log_dir = self::$instance ? self::$instance->config->get('log_dir') : null;
if (empty($log_dir)) {
$log_dir = RCUBE_INSTALL_PATH . 'logs';
}
// try to open specific log file for writing
$logfile = $log_dir.'/'.$name;
if ($fp = @fopen($logfile, 'a')) {
fwrite($fp, $line);
fflush($fp);
fclose($fp);
return true;
}
trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
return false;
}
/**
* Throw system error (and show error page).
*
* @param array Named parameters
* - code: Error code (required)
* - type: Error type [php|db|imap|javascript] (required)
* - message: Error message
* - file: File where error occured
* - line: Line where error occured
* @param boolean True to log the error
* @param boolean Terminate script execution
*/
public static function raise_error($arg = array(), $log = false, $terminate = false)
{
// handle PHP exceptions
if (is_object($arg) && is_a($arg, 'Exception')) {
$arg = array(
'type' => 'php',
'code' => $arg->getCode(),
'line' => $arg->getLine(),
'file' => $arg->getFile(),
'message' => $arg->getMessage(),
);
}
else if (is_string($arg)) {
$arg = array('message' => $arg, 'type' => 'php');
}
if (empty($arg['code'])) {
$arg['code'] = 500;
}
// installer
if (class_exists('rcube_install', false)) {
$rci = rcube_install::get_instance();
$rci->raise_error($arg);
return;
}
$cli = php_sapi_name() == 'cli';
if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) {
$arg['fatal'] = $terminate;
self::log_bug($arg);
}
// terminate script
if ($terminate) {
// display error page
if (is_object(self::$instance->output)) {
self::$instance->output->raise_error($arg['code'], $arg['message']);
}
else if ($cli) {
fwrite(STDERR, 'ERROR: ' . $arg['message']);
}
exit(1);
}
}
/**
* Report error according to configured debug_level
*
* @param array Named parameters
* @see self::raise_error()
*/
public static function log_bug($arg_arr)
{
$program = strtoupper($arg_arr['type']);
$level = self::get_instance()->config->get('debug_level');
// disable errors for ajax requests, write to log instead (#1487831)
if (($level & 4) && !empty($_REQUEST['_remote'])) {
$level = ($level ^ 4) | 1;
}
// write error to local log file
if (($level & 1) || !empty($arg_arr['fatal'])) {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
}
else {
$post_query = '';
}
$log_entry = sprintf("%s Error: %s%s (%s %s)",
$program,
$arg_arr['message'],
$arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI'] . $post_query);
if (!self::write_log('errors', $log_entry)) {
// send error to PHPs error handler if write_log didn't succeed
trigger_error($arg_arr['message'], E_USER_WARNING);
}
}
// report the bug to the global bug reporting system
if ($level & 2) {
// TODO: Send error via HTTP
}
// show error if debug_mode is on
if ($level & 4) {
print "<b>$program Error";
if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
print " in $arg_arr[file] ($arg_arr[line])";
}
print ':</b>&nbsp;';
print nl2br($arg_arr['message']);
print '<br />';
flush();
}
}
/**
* Returns current time (with microseconds).
*
* @return float Current time in seconds since the Unix
*/
public static function timer()
{
return microtime(true);
}
/**
* Logs time difference according to provided timer
*
* @param float $timer Timer (self::timer() result)
* @param string $label Log line prefix
* @param string $dest Log file name
*
* @see self::timer()
*/
public static function print_timer($timer, $label = 'Timer', $dest = 'console')
{
static $print_count = 0;
$print_count++;
$now = self::timer();
$diff = $now - $timer;
if (empty($label)) {
$label = 'Timer '.$print_count;
}
self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
}
/**
* Getter for logged user ID.
*
* @return mixed User identifier
*/
public function get_user_id()
{
if (is_object($this->user)) {
return $this->user->ID;
}
else if (isset($_SESSION['user_id'])) {
return $_SESSION['user_id'];
}
return null;
}
/**
* Getter for logged user name.
*
* @return string User name
*/
public function get_user_name()
{
if (is_object($this->user)) {
return $this->user->get_username();
}
else if (isset($_SESSION['username'])) {
return $_SESSION['username'];
}
}
/**
* Getter for logged user email (derived from user name not identity).
*
* @return string User email address
*/
public function get_user_email()
{
if (is_object($this->user)) {
return $this->user->get_username('mail');
}
}
/**
* Getter for logged user password.
*
* @return string User password
*/
public function get_user_password()
{
if ($this->password) {
return $this->password;
}
else if ($_SESSION['password']) {
return $this->decrypt($_SESSION['password']);
}
}
/**
* Getter for logged user language code.
*
* @return string User language code
*/
public function get_user_language()
{
if (is_object($this->user)) {
return $this->user->language;
}
else if (isset($_SESSION['language'])) {
return $_SESSION['language'];
}
}
/**
* Unique Message-ID generator.
*
* @return string Message-ID
*/
public function gen_message_id()
{
$local_part = md5(uniqid('rcube'.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);
}
/**
* 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 $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 $options SMTP options (e.g. DSN request)
*
* @return boolean Send status.
*/
public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
{
$plugin = $this->plugins->exec_hook('message_before_send', array(
'message' => $message,
'from' => $from,
'mailto' => $mailto,
'options' => $options,
));
$from = $plugin['from'];
$mailto = $plugin['mailto'];
$options = $plugin['options'];
$message = $plugin['message'];
$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, $options);
$response = $this->smtp->get_response();
$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", $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 (filter_var(ini_get('safe_mode'), FILTER_VALIDATE_BOOLEAN))
$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($response) ? join('; ', $response) : ''));
}
}
if (is_resource($msg_body)) {
fclose($msg_body);
}
$message->_headers = array();
$message->headers($headers);
return $sent;
}
}
/**
* Lightweight plugin API class serving as a dummy if plugins are not enabled
*
* @package Framework
* @subpackage Core
*/
class rcube_dummy_plugin_api
{
/**
* Triggers a plugin hook.
* @see rcube_plugin_api::exec_hook()
*/
public function exec_hook($hook, $args = array())
{
return $args;
}
}
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index 90bb85348..62567a0e0 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -1,468 +1,497 @@
<?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 read configuration settings |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Configuration class for Roundcube
*
* @package Framework
* @subpackage Core
*/
class rcube_config
{
const DEFAULT_SKIN = 'larry';
+ private $env = '';
+ private $basedir = 'config/';
private $prop = array();
private $errors = array();
private $userprefs = array();
/**
* Renamed options
*
* @var array
*/
private $legacy_props = array(
// new name => old name
'default_folders' => 'default_imap_folders',
'mail_pagesize' => 'pagesize',
'addressbook_pagesize' => 'pagesize',
'reply_mode' => 'top_posting',
'refresh_interval' => 'keep_alive',
'min_refresh_interval' => 'min_keep_alive',
'messages_cache_ttl' => 'message_cache_lifetime',
'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
);
/**
* Object constructor
+ *
+ * @param string Environment suffix for config files to load
*/
- public function __construct()
+ public function __construct($env = '')
{
+ $this->env = $env;
+ $this->basedir = RCUBE_CONFIG_DIR;
+
$this->load();
// Defaults, that we do not require you to configure,
// but contain information that is used in various
// locations in the code:
$this->set('contactlist_fields', array('name', 'firstname', 'surname', 'email'));
}
/**
* Load config from local config file
*
* @todo Remove global $CONFIG
*/
private function load()
{
// Load default settings
- if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'defaults.inc.php')) {
+ if (!$this->load_from_file('defaults.inc.php')) {
$this->errors[] = 'defaults.inc.php was not found.';
}
// load main config file
- if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'config.inc.php')) {
+ if (!$this->load_from_file('config.inc.php')) {
// Old configuration files
- if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'main.inc.php') ||
- !$this->load_from_file(RCUBE_CONFIG_DIR . 'db.inc.php')) {
+ if (!$this->load_from_file('main.inc.php') ||
+ !$this->load_from_file('db.inc.php')) {
$this->errors[] = 'config.inc.php was not found.';
}
else if (rand(1,100) == 10) { // log warning on every 100th request (average)
trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING);
}
}
// load host-specific configuration
- $this->load_host_config();
+ if (!empty($_SERVER['HTTP_HOST']))
+ $this->load_host_config();
// set skin (with fallback to old 'skin_path' property)
if (empty($this->prop['skin'])) {
if (!empty($this->prop['skin_path'])) {
$this->prop['skin'] = str_replace('skins/', '', unslashify($this->prop['skin_path']));
}
else {
$this->prop['skin'] = self::DEFAULT_SKIN;
}
}
// larry is the new default skin :-)
if ($this->prop['skin'] == 'default')
$this->prop['skin'] = self::DEFAULT_SKIN;
// fix paths
$this->prop['log_dir'] = $this->prop['log_dir'] ? realpath(unslashify($this->prop['log_dir'])) : RCUBE_INSTALL_PATH . 'logs';
$this->prop['temp_dir'] = $this->prop['temp_dir'] ? realpath(unslashify($this->prop['temp_dir'])) : RCUBE_INSTALL_PATH . 'temp';
// fix default imap folders encoding
foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
$this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCUBE_CHARSET, 'UTF7-IMAP');
if (!empty($this->prop['default_folders']))
foreach ($this->prop['default_folders'] as $n => $folder)
$this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
// set PHP error logging according to config
if ($this->prop['debug_level'] & 1) {
ini_set('log_errors', 1);
if ($this->prop['log_driver'] == 'syslog') {
ini_set('error_log', 'syslog');
}
else {
ini_set('error_log', $this->prop['log_dir'].'/errors');
}
}
// enable display_errors in 'show' level, but not for ajax requests
ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
// set timezone auto settings values
if ($this->prop['timezone'] == 'auto') {
$this->prop['_timezone_value'] = $this->client_timezone();
}
else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
$this->prop['timezone'] = $tz;
}
else if (empty($this->prop['timezone'])) {
$this->prop['timezone'] = 'UTC';
}
// remove deprecated properties
unset($this->prop['dst_active']);
// export config data
$GLOBALS['CONFIG'] = &$this->prop;
}
/**
* Load a host-specific config file if configured
* This will merge the host specific configuration with the given one
*/
private function load_host_config()
{
$fname = null;
if (is_array($this->prop['include_host_config'])) {
$fname = $this->prop['include_host_config'][$_SERVER['HTTP_HOST']];
}
else if (!empty($this->prop['include_host_config'])) {
$fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php';
}
if ($fname) {
- $this->load_from_file(RCUBE_CONFIG_DIR . $fname);
+ $this->load_from_file($fname);
}
}
/**
* Read configuration from a file
* and merge with the already stored config values
*
- * @param string $fpath Full path to the config file to be loaded
+ * @param string $file Name of the config file to be loaded
* @return booelan True on success, false on failure
*/
- public function load_from_file($fpath)
+ public function load_from_file($file)
{
- if (is_file($fpath) && is_readable($fpath)) {
+ $fpath = $this->resolve_path($file);
+ if ($fpath && is_file($fpath) && is_readable($fpath)) {
// use output buffering, we don't need any output here
ob_start();
include($fpath);
ob_end_clean();
if (is_array($config)) {
$this->merge($config);
return true;
}
// deprecated name of config variable
else if (is_array($rcmail_config)) {
$this->merge($rcmail_config);
return true;
}
}
return false;
}
+ /**
+ * Helper method to resolve the absolute path to the given config file.
+ * This also takes the 'env' property into account.
+ */
+ public function resolve_path($file, $use_env = true)
+ {
+ if (strpos($file, '/') === false) {
+ $file = realpath($this->basedir . '/' . $file);
+ }
+
+ // check if <file>-env.ini exists
+ if ($file && $use_env && !empty($this->env)) {
+ $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $file);
+ if (is_file($envfile))
+ return $envfile;
+ }
+
+ return $file;
+ }
+
/**
* Getter for a specific config parameter
*
* @param string $name Parameter name
* @param mixed $def Default value if not set
* @return mixed The requested config value
*/
public function get($name, $def = null)
{
if (isset($this->prop[$name])) {
$result = $this->prop[$name];
}
else {
$result = $def;
}
$rcube = rcube::get_instance();
if ($name == 'timezone' && isset($this->prop['_timezone_value'])) {
$result = $this->prop['_timezone_value'];
}
else if ($name == 'client_mimetypes') {
if ($result == null && $def == null)
$result = 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,image/bmp,image/tiff,application/x-javascript,application/pdf,application/x-shockwave-flash';
if ($result && is_string($result))
$result = explode(',', $result);
}
$plugin = $rcube->plugins->exec_hook('config_get', array(
'name' => $name, 'default' => $def, 'result' => $result));
return $plugin['result'];
}
/**
* Setter for a config parameter
*
* @param string $name Parameter name
* @param mixed $value Parameter value
*/
public function set($name, $value)
{
$this->prop[$name] = $value;
}
/**
* Override config options with the given values (eg. user prefs)
*
* @param array $prefs Hash array with config props to merge over
*/
public function merge($prefs)
{
$this->prop = array_merge($this->prop, $prefs, $this->userprefs);
$this->fix_legacy_props();
}
/**
* Merge the given prefs over the current config
* and make sure that they survive further merging.
*
* @param array $prefs Hash array with user prefs
*/
public function set_user_prefs($prefs)
{
// Honor the dont_override setting for any existing user preferences
$dont_override = $this->get('dont_override');
if (is_array($dont_override) && !empty($dont_override)) {
foreach ($dont_override as $key) {
unset($prefs[$key]);
}
}
// convert user's timezone into the new format
if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
$prefs['timezone'] = $tz;
}
// larry is the new default skin :-)
if ($prefs['skin'] == 'default') {
$prefs['skin'] = self::DEFAULT_SKIN;
}
$this->userprefs = $prefs;
$this->prop = array_merge($this->prop, $prefs);
$this->fix_legacy_props();
// override timezone settings with client values
if ($this->prop['timezone'] == 'auto') {
$this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
}
else if (isset($this->prop['_timezone_value']))
unset($this->prop['_timezone_value']);
}
/**
* Getter for all config options
*
* @return array Hash array containg all config properties
*/
public function all()
{
return $this->prop;
}
/**
* Special getter for user's timezone offset including DST
*
* @return float Timezone offset (in hours)
* @deprecated
*/
public function get_timezone()
{
if ($tz = $this->get('timezone')) {
try {
$tz = new DateTimeZone($tz);
return $tz->getOffset(new DateTime('now')) / 3600;
}
catch (Exception $e) {
}
}
return 0;
}
/**
* Return requested DES crypto key.
*
* @param string $key Crypto key name
* @return string Crypto key
*/
public function get_crypto_key($key)
{
// Bomb out if the requested key does not exist
if (!array_key_exists($key, $this->prop)) {
rcube::raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Request for unconfigured crypto key \"$key\""
), true, true);
}
$key = $this->prop[$key];
// Bomb out if the configured key is not exactly 24 bytes long
if (strlen($key) != 24) {
rcube::raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
), true, true);
}
return $key;
}
/**
* Try to autodetect operating system and find the correct line endings
*
* @return string The appropriate mail header delimiter
*/
public function header_delimiter()
{
// use the configured delimiter for headers
if (!empty($this->prop['mail_header_delimiter'])) {
$delim = $this->prop['mail_header_delimiter'];
if ($delim == "\n" || $delim == "\r\n")
return $delim;
else
rcube::raise_error(array(
'code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid mail_header_delimiter setting"
), true, false);
}
$php_os = strtolower(substr(PHP_OS, 0, 3));
if ($php_os == 'win')
return "\r\n";
if ($php_os == 'mac')
return "\r\n";
return "\n";
}
/**
* Return the mail domain configured for the given host
*
* @param string $host IMAP host
* @param boolean $encode If true, domain name will be converted to IDN ASCII
* @return string Resolved SMTP host
*/
public function mail_domain($host, $encode=true)
{
$domain = $host;
if (is_array($this->prop['mail_domain'])) {
if (isset($this->prop['mail_domain'][$host]))
$domain = $this->prop['mail_domain'][$host];
}
else if (!empty($this->prop['mail_domain'])) {
$domain = rcube_utils::parse_host($this->prop['mail_domain']);
}
if ($encode) {
$domain = rcube_utils::idn_to_ascii($domain);
}
return $domain;
}
/**
* Getter for error state
*
* @return mixed Error message on error, False if no errors
*/
public function get_error()
{
return empty($this->errors) ? false : join("\n", $this->errors);
}
/**
* Internal getter for client's (browser) timezone identifier
*/
private function client_timezone()
{
if (isset($_SESSION['timezone']) && is_numeric($_SESSION['timezone'])
&& ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0))) {
return $ctz;
}
else if (!empty($_SESSION['timezone'])) {
try {
$tz = timezone_open($_SESSION['timezone']);
return $tz->getName();
}
catch (Exception $e) { /* gracefully ignore */ }
}
// fallback to server's timezone
return date_default_timezone_get();
}
/**
* Convert legacy options into new ones
*/
private function fix_legacy_props()
{
foreach ($this->legacy_props as $new => $old) {
if (isset($this->prop[$old])) {
if (!isset($this->prop[$new])) {
$this->prop[$new] = $this->prop[$old];
}
unset($this->prop[$old]);
}
}
}
}
diff --git a/skins/classic/templates/compose.html b/skins/classic/templates/compose.html
index 660f354cd..5e259e11c 100644
--- a/skins/classic/templates/compose.html
+++ b/skins/classic/templates/compose.html
@@ -1,205 +1,205 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="productname" /> :: <roundcube:label name="compose" /></title>
<roundcube:include file="/includes/links.html" />
<roundcube:if condition="config:enable_spellcheck" />
<link rel="stylesheet" type="text/css" href="/googiespell.css" />
<roundcube:endif />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#compose-contacts { width: <roundcube:exp expression="!empty(cookie:composesplitterv1) ? cookie:composesplitterv1-5 : 195" />px; }
#compose-container { left: <roundcube:exp expression="!empty(cookie:composesplitterv1) ? cookie:composesplitterv1+5 : 205" />px;
<roundcube:exp expression="browser:ie ? ('width: expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:composesplitterv1) ? cookie:composesplitterv1+5 : 200).')+\\'px\\');') : ''" />
}
</style>
</head>
<roundcube:if condition="env:extwin" />
<body class="extwin">
<roundcube:object name="message" id="message" />
<roundcube:else />
<body>
<roundcube:include file="/includes/taskbar.html" />
<roundcube:include file="/includes/header.html" />
<roundcube:endif />
<div id="messagetoolbar">
<roundcube:if condition="env:extwin" />
<roundcube:button command="close" type="link" class="button back" classAct="button back" classSel="button backSel" title="close" content=" " />
<roundcube:else />
<roundcube:button command="list" type="link" class="button back" classAct="button back" classSel="button backSel" title="backtolist" content=" " />
<roundcube:endif />
<roundcube:button command="send" type="link" class="buttonPas send" classAct="button send" classSel="button sendSel" title="sendmessage" content=" " />
<roundcube:button name="addattachment" type="link" class="button attach" classAct="button attach" classSel="button attachSel" title="addattachment" onclick="rcmail_ui.show_popup('uploadmenu', true);return false" content=" " />
<roundcube:button command="insert-sig" type="link" class="buttonPas insertsig" classAct="button insertsig" classSel="button insertsigSel" title="insertsignature" content=" " />
<roundcube:button command="savedraft" type="link" class="buttonPas savedraft" classAct="button savedraft" classSel="button savedraftSel" title="savemessage" content=" " />
<roundcube:if condition="config:enable_spellcheck" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="buttonPas spellcheck" classAct="button spellcheck" classSel="button spellcheckSel" title="checkspelling" content=" " />
<span id="spellmenulink" onclick="rcmail_ui.show_popup('spellmenu');return false"></span>
</span>
<roundcube:endif />
<roundcube:container name="toolbar" id="compose-toolbar" />
<roundcube:button name="messageoptions" id="composemenulink" type="link" class="button messagemenu" title="messageoptions" onclick="rcmail_ui.show_popup('composemenu', true);return false" content=" " />
</div>
-<form name="form" action="./" method="post">
+<roundcube:form name="form" method="post">
<div id="mainscreen">
<div id="compose-contacts">
<div class="boxtitle"><roundcube:label name="contacts" /></div>
<div class="boxlistcontent">
<div id="quicksearchbar">
<img id="searchmenulink" src="/images/icons/glass.png" width="16" height="16" />
<roundcube:object name="searchform" id="quicksearchbox" form="true" tabindex="13" />
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" width="13" height="13" />
</div>
<roundcube:object name="addressbooks" id="directorylist" />
<roundcube:object name="addresslist" id="contacts-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="abookactions" class="pagenav">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="button disabled" classAct="button" content="To &amp;raquo;" />
<roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="button disabled" classAct="button" content="Cc &amp;raquo;" />
<roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="button disabled" classAct="button" content="Bcc &amp;raquo;" />
</div>
<div id="abookcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<span style="float:left">&nbsp;</span>
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
</div>
<script type="text/javascript">
var composesplitv1 = new rcube_splitter({id:'composesplitterv1', p1: 'compose-contacts', p2: 'compose-container', orientation: 'v', relative: true, start: 200});
rcmail.add_onload('composesplitv1.init()');
</script>
<div id="compose-container">
<div id="compose-headers-div" style="width: 100%;">
<table border="0" cellspacing="0" cellpadding="1" id="compose-headers">
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield formlinks">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('identities')"><roundcube:label name="editidents" /></a>
<roundcube:button command="extwin" image="/images/icons/extwin.png" width="15" height="15" title="openinextwin" id="openextwinlink" condition="!env:extwin" />
</td>
</tr><tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="2" tabindex="2" /></td>
</tr><tr id="compose-cc">
<td class="title top">
<a href="#cc" onclick="return rcmail_ui.hide_header_form('cc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_cc"><roundcube:label name="cc" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="2" tabindex="3" /></td>
</tr><tr id="compose-bcc">
<td class="title top">
<a href="#bcc" onclick="return rcmail_ui.hide_header_form('bcc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_bcc"><roundcube:label name="bcc" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
</tr><tr id="compose-replyto">
<td class="title top">
<a href="#replyto" onclick="return rcmail_ui.hide_header_form('replyto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_replyto"><roundcube:label name="replyto" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="5" /></td>
</tr><tr id="compose-followupto">
<td class="title top">
<a href="#followupto" onclick="return rcmail_ui.hide_header_form('followupto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_followupto"><roundcube:label name="followupto" /></label>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="7" /></td>
</tr><tr>
<td></td>
<td class="formlinks">
<a href="#cc" onclick="return rcmail_ui.show_header_form('cc')" id="cc-link"><roundcube:label name="addcc" /></a>
<span class="separator">|</span>
<a href="#bcc" onclick="return rcmail_ui.show_header_form('bcc')" id="bcc-link"><roundcube:label name="addbcc" /></a>
<span class="separator">|</span>
<a href="#reply-to" onclick="return rcmail_ui.show_header_form('replyto')" id="replyto-link"><roundcube:label name="addreplyto" /></a>
<span class="separator">|</span>
<a href="#followup-to" onclick="return rcmail_ui.show_header_form('followupto')" id="followupto-link"><roundcube:label name="addfollowupto" /></a>
</td>
</tr><tr>
<td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td>
<td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="8" /></td>
</tr>
</table>
</div>
<div id="compose-div">
<div id="compose-body-div">
<div class="boxlistcontent" style="overflow: hidden; top: 0">
<roundcube:object name="composeBody" id="compose-body" form="form" cols="70" rows="20" tabindex="9" />
</div>
<div class="boxfooter">
<div id="compose-buttons">
<roundcube:button type="input" command="send" class="button mainaction" label="sendmessage" tabindex="10" />
<roundcube:button type="input" command="list" class="button" label="cancel" tabindex="11" />
</div>
<div id="compose-editorfooter" class="pagenav">
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<span>
<label><roundcube:label name="editortype" /></label>
<roundcube:object name="editorSelector" editorid="compose-body" tabindex="12" />
</span>
<roundcube:endif />
</div>
</div>
</div>
<script type="text/javascript">
var composesplitv2 = new rcube_splitter({id:'composesplitterv2', p1: 'compose-body-div', p2: 'compose-attachments', orientation: 'v', relative: true, start: $('#compose-headers-div').width() - 175});
rcmail.add_onload('composesplitv2.init()');
</script>
<div id="compose-attachments">
<div class="boxtitle"><roundcube:label name="attachments" /></div>
<div class="boxlistcontent">
<roundcube:object name="composeAttachmentList" id="attachmentslist" loadingIcon="/images/display/loading_blue.gif" />
</div>
<div class="boxfooter">
<roundcube:button name="uploadmenulink" id="uploadmenulink" type="link" title="addattachment" class="button addgroup" onclick="rcmail_ui.show_popup('uploadmenu', true);return false" content=" " />
</div>
</div>
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div>
</div>
</div>
<div id="composeoptionsmenu" class="popupmenu">
<table>
<tr>
<td><label for="rcmcomposereceipt"><roundcube:label name="returnreceipt" />:</label></td>
<td><roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" /></td>
</tr><tr>
<td><label for="rcmcomposedsn"><roundcube:label name="dsn" />:</label></td>
<td><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" /></td>
</tr><tr>
<td><label for="rcmcomposepriority"><roundcube:label name="priority" />:</label></td>
<td><roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></td>
</tr><roundcube:if condition="!config:no_save_sent_messages" /><tr>
<td><label><roundcube:label name="savesentmessagein" />:</label></td>
<td><roundcube:object name="storetarget" maxlength="30" /></td>
</tr><roundcube:endif />
</table>
</div>
<div id="spellmenu" class="popupmenu selectable"></div>
</form>
<roundcube:object name="composeAttachmentForm" id="attachment-form" attachmentFieldSize="40" class="popupmenu" />
<script type="text/javascript">
rcube_init_mail_ui();
</script>
</body>
</html>
diff --git a/skins/classic/templates/contactadd.html b/skins/classic/templates/contactadd.html
index 86bca46ea..bad6daf28 100644
--- a/skins/classic/templates/contactadd.html
+++ b/skins/classic/templates/contactadd.html
@@ -1,44 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script>
</head>
<body class="iframe">
<div id="contact-title" class="boxtitle"><roundcube:label name="addcontact" /></div>
<div id="contact-details" class="boxcontent">
-<form name="editform" method="post" action="./">
+<roundcube:form name="editform" method="post">
<roundcube:if condition="strlen(env:sourcename)" />
<div id="sourcename"><roundcube:label name="addressbook" />: <roundcube:object name="sourceselector" class="hint" id="sourceselect" /></div>
<roundcube:endif />
<div id="contactphoto">
<roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" />
<div class="formlinks">
<roundcube:button command="upload-photo" id="uploadformlink" type="link" label="addphoto" class="disabled" classAct="active" onclick="rcmail_ui.show_popup('uploadform', true);return false" condition="env:photocol" /><br/>
<roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" />
</div>
</div>
<roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" />
<div style="clear:both"></div>
<div id="contacttabs">
<roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" />
</div>
<p>
<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
</p>
</form>
</div>
<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" />
<roundcube:object name="fileDropArea" id="contactpic" />
<script type="text/javascript">
rcube_init_tabs('contacttabs');
rcube_init_mail_ui();
</script>
</body>
</html>
diff --git a/skins/classic/templates/contactedit.html b/skins/classic/templates/contactedit.html
index 8ab801d05..c51cbf296 100644
--- a/skins/classic/templates/contactedit.html
+++ b/skins/classic/templates/contactedit.html
@@ -1,44 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script>
</head>
<body class="iframe">
<div id="contact-title" class="boxtitle"><roundcube:label name="editcontact" /></div>
<div id="contact-details" class="boxcontent">
-<form name="editform" method="post" action="./">
+<roundcube:form name="editform" method="post">
<roundcube:if condition="strlen(env:sourcename)" />
<div id="sourcename"><roundcube:label name="addressbook" />: <roundcube:var name="env:sourcename" /></div>
<roundcube:endif />
<div id="contactphoto">
<roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" />
<div class="formlinks">
<roundcube:button command="upload-photo" id="uploadformlink" type="link" label="replacephoto" class="disabled" classAct="active" onclick="rcmail_ui.show_popup('uploadform', true);return false" condition="env:photocol" /><br/>
<roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" />
</div>
</div>
<roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" />
<div style="clear:both"></div>
<div id="contacttabs">
<roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" />
</div>
<p>
<roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp;
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
</p>
</form>
</div>
<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" />
<roundcube:object name="fileDropArea" id="contactpic" />
<script type="text/javascript">
rcube_init_tabs('contacttabs');
rcube_init_mail_ui();
</script>
</body>
</html>
diff --git a/skins/classic/templates/login.html b/skins/classic/templates/login.html
index cca2bd934..2dacd48ff 100644
--- a/skins/classic/templates/login.html
+++ b/skins/classic/templates/login.html
@@ -1,55 +1,55 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<meta name="Robots" content="noindex,nofollow" />
<roundcube:include file="/includes/links.html" />
</head>
<body>
<roundcube:object name="logo" src="/images/roundcube_logo.png" id="logo" border="0" style="margin:0 11px" />
<roundcube:object name="message" id="message" />
<div id="login-form">
<div class="boxtitle"><roundcube:label name="welcome" /></div>
<div class="boxcontent">
-<form name="form" action="./" method="post">
+<roundcube:form name="form" method="post">
<roundcube:object name="loginform" form="form" />
<p style="text-align:center;"><input type="submit" class="button mainaction" value="<roundcube:label name='login' />" /></p>
</form>
</div>
</div>
<noscript>
<p id="login-noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
</noscript>
<div id="login-bottomline">
<roundcube:var name="config:product_name"> <roundcube:object name="version" condition="config:display_version" />
<roundcube:if condition="config:support_url" />
&nbsp;&#9679;&nbsp; <a href="<roundcube:var name='config:support_url' />" target="_blank" class="support-link"><roundcube:label name="support" /></a>
<roundcube:endif />
<roundcube:container name="loginfooter" id="bottomline" />
</div>
<roundcube:object name="preloader" images="
/images/icons/folders.png
/images/mail_footer.png
/images/taskicons.gif
/images/display/loading.gif
/images/pagenav.gif
/images/mail_toolbar.png
/images/searchfield.gif
/images/messageicons.png
/images/icons/reset.gif
/images/abook_toolbar.png
/images/icons/groupactions.png
/images/watermark.gif
" />
</body>
</html>
diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html
index 9cfe7fe4c..09eafe73b 100644
--- a/skins/larry/templates/compose.html
+++ b/skins/larry/templates/compose.html
@@ -1,200 +1,200 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<roundcube:if condition="config:enable_spellcheck" />
<link rel="stylesheet" type="text/css" href="/googiespell.css" />
<roundcube:endif />
</head>
<roundcube:if condition="env:extwin" /><body class="extwin"><roundcube:else /><body><roundcube:endif />
<roundcube:include file="/includes/header.html" />
<div id="mainscreen">
<!-- toolbar -->
<div id="messagetoolbar" class="fullwidth">
<div id="mailtoolbar" class="toolbar">
<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="cancel" condition="!env:extwin" />
<roundcube:button command="close" type="link" class="button close disabled" classAct="button close" classSel="button close pressed" label="cancel" condition="env:extwin" />
<span class="spacer"></span>
<roundcube:button command="send" type="link" class="button send" classAct="button send" classSel="button send pressed" label="send" title="sendmessage" />
<roundcube:button command="savedraft" type="link" class="button savedraft" classAct="button savedraft" classSel="button savedraft pressed" label="save" title="savemessage" />
<span class="spacer"></span>
<roundcube:if condition="config:enable_spellcheck" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="button spellcheck disabled" classAct="button spellcheck" classSel="button spellcheck pressed" label="spellcheck" title="checkspelling" />
<span class="dropbuttontip" id="spellmenulink" onclick="UI.show_popup('spellmenu');return false"></span>
</span>
<roundcube:endif />
<roundcube:button name="addattachment" type="link" class="button attach" classAct="button attach" classSel="button attach pressed" label="attach" title="addattachment" onclick="UI.show_uploadform();return false" />
<roundcube:button command="insert-sig" type="link" class="button insertsig disabled" classAct="button insertsig" classSel="button insertsig pressed" label="signature" title="insertsignature" />
<roundcube:container name="toolbar" id="compose-toolbar" />
</div>
</div>
<div id="composeview-left">
<!-- inline address book -->
<div id="compose-contacts" class="uibox listbox">
<h2 class="boxtitle"><roundcube:label name="contacts" /></h2>
<div id="composequicksearch">
<div class="searchbox">
<roundcube:object name="searchform" id="contactsearchbox" />
<a id="searchmenulink" class="iconbutton searchoptions"> </a>
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
</div>
<roundcube:object name="addressbooks" id="directorylist" class="listing" />
<div class="scroller withfooter">
<roundcube:object name="addresslist" id="contacts-table" class="listing" noheader="true" />
</div>
<div class="boxfooter">
<roundcube:button command="add-recipient" prop="to" type="link" title="to" class="listbutton addto disabled" classAct="listbutton addto" innerClass="inner" content="To+" /><roundcube:button command="add-recipient" prop="cc" type="link" title="cc" class="listbutton addcc disabled" classAct="listbutton addcc" innerClass="inner" content="Cc+" /><roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc" class="listbutton addbcc disabled" classAct="listbutton addbcc" innerClass="inner" content="Bcc+" />
</div>
<div class="boxpagenav">
<roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" content="|&amp;lt;" />
<roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" content="&amp;lt;" />
<roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" content="&amp;gt;" />
<roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" content="&amp;gt;|" />
</div>
</div>
</div>
<div id="composeview-right">
-<form name="form" action="./" method="post" id="compose-content" class="uibox">
+<roundcube:form name="form" method="post" id="compose-content" class="uibox">
<!-- message headers -->
<div id="composeheaders">
<a href="#options" id="composeoptionstoggle" class="moreheaderstoggle"><span class="iconlink" title="<roundcube:label name='options' />"></span></a>
<table class="headers-table compose-headers">
<tbody>
<tr>
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
<td class="editfield">
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
<a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit"><roundcube:label name="editidents" /></a>
</td>
</tr><tr>
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="2" /></td>
</tr><tr id="compose-cc">
<td class="title top">
<label for="_cc"><roundcube:label name="cc" /></label>
<a href="#cc" onclick="return UI.hide_header_row('cc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" />x</a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="3" /></td>
</tr><tr id="compose-bcc">
<td class="title top">
<label for="_bcc"><roundcube:label name="bcc" /></label>
<a href="#bcc" onclick="return UI.hide_header_row('bcc');" class="iconbutton cancel" title="<roundcube:label name='delete' />" />x</a>
</td>
<td colspan="2" class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="4" /></td>
</tr><tr id="compose-replyto">
<td class="title top">
<label for="_replyto"><roundcube:label name="replyto" /></label>
<a href="#replyto" onclick="return UI.hide_header_row('replyto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" />x</a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="replyto" form="form" id="_replyto" size="70" tabindex="5" /></td>
</tr><tr id="compose-followupto">
<td class="title top">
<label for="_followupto"><roundcube:label name="followupto" /></label>
<a href="#followupto" onclick="return UI.hide_header_row('followupto');" class="iconbutton cancel" title="<roundcube:label name='delete' />" />x</a>
</td>
<td class="editfield"><roundcube:object name="composeHeaders" part="followupto" form="form" id="_followupto" size="70" tabindex="7" /></td>
</tr><tr>
<td></td>
<td class="formlinks">
<a href="#cc" onclick="return UI.show_header_row('cc')" id="cc-link" class="iconlink add"><roundcube:label name="addcc" /></a>
<a href="#bcc" onclick="return UI.show_header_row('bcc')" id="bcc-link" class="iconlink add"><roundcube:label name="addbcc" /></a>
<a href="#reply-to" onclick="return UI.show_header_row('replyto')" id="replyto-link" class="iconlink add"><roundcube:label name="addreplyto" /></a>
<a href="#followup-to" onclick="return UI.show_header_row('followupto')" id="followupto-link" class="iconlink add"><roundcube:label name="addfollowupto" /></a>
</td>
</tr><tr>
<td class="title"><label for="compose-subject"><roundcube:label name="subject" /></label></td>
<td class="editfield"><roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="8" /></td>
</tr>
</tbody>
</table>
<div id="composebuttons" class="pagenav formbuttons">
<roundcube:button command="extwin" type="link" class="button extwin" classSel="button extwin pressed" innerClass="inner" title="openinextwin" content="[]" condition="!env:extwin" />
</div>
<!-- (collapsable) message options -->
<div id="composeoptions">
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<span class="composeoption">
<label><roundcube:label name="editortype" />
<roundcube:object name="editorSelector" editorid="composebody" tabindex="14" /></label>
</span>
<roundcube:endif />
<span class="composeoption">
<label for="rcmcomposepriority"><roundcube:label name="priority" />
<roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></label>
</span>
<span class="composeoption">
<label><roundcube:object name="receiptCheckBox" form="form" id="rcmcomposereceipt" /> <roundcube:label name="returnreceipt" /></label>
</span>
<span class="composeoption">
<label><roundcube:object name="dsnCheckBox" form="form" id="rcmcomposedsn" /> <roundcube:label name="dsn" /></label>
</span>
<roundcube:if condition="!config:no_save_sent_messages" />
<span class="composeoption">
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" /></label>
</span>
<roundcube:endif />
<roundcube:container name="composeoptions" id="composeoptions" />
</div>
</div>
<!-- message compose body -->
<div id="composeview-bottom">
<div id="composebodycontainer">
<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="9" />
</div>
<div id="compose-attachments" class="rightcol">
<div style="text-align:center; margin-bottom:20px">
<roundcube:button name="addattachment" type="input" class="button" classSel="button pressed" label="addattachment" onclick="UI.show_uploadform();return false" />
</div>
<roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" />
<roundcube:object name="fileDropArea" id="compose-attachments" />
</div>
<!--
<div id="composeformbuttons" class="footerleft formbuttons floating">
<roundcube:button type="input" command="send" class="button mainaction" label="sendmessage" tabindex="11" />
<roundcube:button type="input" command="savedraft" class="button" label="savemessage" tabindex="12" />
<roundcube:button type="input" command="list" class="button" label="cancel" tabindex="13" />
</div>
-->
</div>
</form>
<div id="mailview-bottom" class="uibox">
<roundcube:object name="message" id="message" class="statusbar" />
</div>
</div><!-- end mailview-right -->
</div><!-- end mainscreen -->
<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='addattachment' />">
<roundcube:object name="composeAttachmentForm" id="uploadform" attachmentFieldSize="40" buttons="no" />
<div class="formbuttons">
<roundcube:button command="send-attachment" type="input" class="button mainaction" label="upload" />
<roundcube:button name="close" type="input" class="button" label="cancel" onclick="UI.show_uploadform()" />
</div>
</div>
<div id="spellmenu" class="popupmenu"></div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>
diff --git a/skins/larry/templates/contactedit.html b/skins/larry/templates/contactedit.html
index f84936635..3467ebe8e 100644
--- a/skins/larry/templates/contactedit.html
+++ b/skins/larry/templates/contactedit.html
@@ -1,56 +1,56 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="iframe">
<h1 class="boxtitle">
<roundcube:if condition="env:action=='add'" /><roundcube:label name="addcontact" />
<roundcube:else /><roundcube:label name="editcontact" />
<roundcube:endif /></h1>
-<form name="editform" method="post" action="./" id="contact-details" class="boxcontent">
+<roundcube:form name="editform" method="post" id="contact-details" class="boxcontent">
<roundcube:if condition="strlen(env:sourcename)" />
<div id="sourcename"><roundcube:label name="addressbook" />: <roundcube:var name="env:sourcename" condition="env:action!='add'" /><roundcube:object name="sourceselector" id="sourceselect" condition="env:action=='add'" /></div>
<roundcube:endif />
<div id="contactphoto">
<roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" />
<roundcube:if condition="env:photocol" />
<roundcube:object name="fileDropArea" id="contactpic" />
<div class="formlinks">
<roundcube:button command="upload-photo" id="uploadformlink" type="link" label="replacephoto" class="iconlink upload disabled" classAct="iconlink upload active" onclick="UI.show_uploadform();return false" condition="env:photocol" /><br/>
<roundcube:button command="delete-photo" type="link" label="delete" class="iconlink delete disabled" classAct="iconlink delete active" condition="env:photocol" />
</div>
<roundcube:endif />
</div>
<roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" />
<br style="clear:both" />
<div id="contacttabs" class="tabbed">
<roundcube:object name="contacteditform" size="40" textareacols="60" form="editform" />
</div>
</form>
<div class="footerleft formbuttons">
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
<roundcube:button command="show" type="input" class="button" label="cancel" condition="env:action=='edit'" />
<roundcube:button name="cancel" type="input" class="button" label="cancel" onclick="history.back()" condition="env:action=='add'" />
</div>
<div id="upload-dialog" class="propform popupdialog" title="<roundcube:label name='uploadphoto' />">
<roundcube:object name="photoUploadForm" id="upload-form" size="30" buttons="no" />
<div class="formbuttons">
<roundcube:button command="upload-photo" type="input" class="button mainaction" label="upload" />
<roundcube:button name="close" type="input" class="button" label="cancel" onclick="UI.show_uploadform()" />
</div>
</div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>
diff --git a/skins/larry/templates/login.html b/skins/larry/templates/login.html
index a605eb7fe..6e56ee2fd 100644
--- a/skins/larry/templates/login.html
+++ b/skins/larry/templates/login.html
@@ -1,54 +1,54 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<meta name="Robots" content="noindex,nofollow" />
<roundcube:include file="/includes/links.html" />
</head>
<body>
<div id="login-form">
<div class="box-inner">
<roundcube:object name="logo" src="/images/roundcube_logo.png" id="logo" border="0" />
-<form name="form" action="./" method="post">
+<roundcube:form name="form" method="post">
<roundcube:object name="loginform" form="form" size="40" />
<p class="formbuttons"><input type="submit" class="button mainaction" value="<roundcube:label name='login' />" /></p>
</form>
</div>
<div class="box-bottom">
<roundcube:object name="message" id="message" />
<noscript>
<p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
</noscript>
</div>
<div id="bottomline">
<roundcube:var name="config:product_name"> <roundcube:object name="version" condition="config:display_version" />
<roundcube:if condition="config:support_url" />
&nbsp;&#9679;&nbsp; <a href="<roundcube:var name='config:support_url' />" target="_blank" class="support-link"><roundcube:label name="support" /></a>
<roundcube:endif />
<roundcube:container name="loginfooter" id="bottomline" />
</div>
</div>
<roundcube:include file="/includes/footer.html" />
<roundcube:object name="preloader" images="
/images/ajaxloader.gif
/images/buttons.png
/images/addcontact.png
/images/filetypes.png
/images/listicons.png
/images/messages.png
/images/quota.png
/images/selector.png
/images/splitter.png
/images/watermark.jpg
" />
</body>
</html>
diff --git a/tests/Selenium/bootstrap.php b/tests/Selenium/bootstrap.php
index 6611e8feb..e8b186a1e 100644
--- a/tests/Selenium/bootstrap.php
+++ b/tests/Selenium/bootstrap.php
@@ -1,185 +1,185 @@
<?php
/*
+-----------------------------------------------------------------------+
| tests/Selenium/bootstrap.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2009-2013, 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: |
| Environment initialization script for unit tests |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
if (php_sapi_name() != 'cli')
die("Not in shell mode (php-cli)");
if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/../../') . '/' );
define('TESTS_DIR', dirname(__FILE__) . '/');
if (@is_dir(TESTS_DIR . 'config')) {
- define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
+ define('RCUBE_CONFIG_DIR', TESTS_DIR . 'config');
}
require_once(INSTALL_PATH . 'program/include/iniset.php');
// Extend include path so some plugin test won't fail
$include_path = ini_get('include_path') . PATH_SEPARATOR . TESTS_DIR . '..';
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}
-$rcmail = rcube::get_instance();
+$rcmail = rcube::get_instance('test');
define('TESTS_URL', $rcmail->config->get('tests_url'));
define('TESTS_BROWSER', $rcmail->config->get('tests_browser', 'firefox'));
define('TESTS_USER', $rcmail->config->get('tests_username'));
define('TESTS_PASS', $rcmail->config->get('tests_password'));
define('TESTS_SLEEP', $rcmail->config->get('tests_sleep', 5));
PHPUnit_Extensions_Selenium2TestCase::shareSession(true);
// @TODO: remove user record from DB before running tests
// @TODO: make sure mailbox has some content (always the same) or is empty
// @TODO: plugins: enable all?
/**
* Base class for all tests in this directory
*/
class Selenium_Test extends PHPUnit_Extensions_Selenium2TestCase
{
protected function setUp()
{
// $this->rc = rcube::get_instance();
$this->setBrowser(TESTS_BROWSER);
// Set root to our index.html, for better performance
// See https://github.com/sebastianbergmann/phpunit-selenium/issues/217
$this->setBrowserUrl(TESTS_URL . '/tests/Selenium');
}
protected function login()
{
$this->go('mail');
$user_input = $this->byCssSelector('form input[name="_user"]');
$pass_input = $this->byCssSelector('form input[name="_pass"]');
$submit = $this->byCssSelector('form input[type="submit"]');
$user_input->value(TESTS_USER);
$pass_input->value(TESTS_PASS);
// submit login form
$submit->click();
// wait after successful login
sleep(TESTS_SLEEP);
}
protected function go($task = 'mail', $action = null)
{
$this->url(TESTS_URL . '/?_task=' . $task);
// wait for interface load (initial ajax requests, etc.)
sleep(TESTS_SLEEP);
if ($action) {
$this->click_button($action);
sleep(TESTS_SLEEP);
}
}
protected function get_env()
{
return $this->execute(array(
'script' => 'return rcmail.env;',
'args' => array(),
));
}
protected function get_buttons($action)
{
$buttons = $this->execute(array(
'script' => "return rcmail.buttons['$action'];",
'args' => array(),
));
if (is_array($buttons)) {
foreach ($buttons as $idx => $button) {
$buttons[$idx] = $button['id'];
}
}
return (array) $buttons;
}
protected function get_objects()
{
return $this->execute(array(
'script' => "var i,r = []; for (i in rcmail.gui_objects) r.push(i); return r;",
'args' => array(),
));
}
protected function click_button($action)
{
$buttons = $this->get_buttons($action);
$id = array_shift($buttons);
// this doesn't work for me
$this->byId($id)->click();
}
protected function ajaxResponse($action, $script = '', $button = false)
{
if (!$script && !$button) {
$script = "rcmail.command('$action')";
}
$script =
"if (!window.test_ajax_response) {
window.test_ajax_response_object = {};
function test_ajax_response(response)
{
if (response.response && response.response.action) {
window.test_ajax_response_object[response.response.action] = response.response;
}
}
rcmail.addEventListener('responsebefore', test_ajax_response);
}
window.test_ajax_response_object['$action'] = null;
$script;
";
// run request
$this->execute(array(
'script' => $script,
'args' => array(),
));
if ($button) {
$this->click_button($action);
}
// wait
sleep(TESTS_SLEEP);
// get response
$response = $this->execute(array(
'script' => "return window.test_ajax_response_object['$action'];",
'args' => array(),
));
return $response;
}
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 40659ebf0..192997d0d 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,41 +1,41 @@
<?php
/*
+-----------------------------------------------------------------------+
| tests/bootstrap.php |
| |
| This file is part of the Roundcube Webmail client |
| 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: |
| Environment initialization script for unit tests |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
if (php_sapi_name() != 'cli')
die("Not in shell mode (php-cli)");
if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
define('TESTS_DIR', dirname(__FILE__) . '/');
if (@is_dir(TESTS_DIR . 'config')) {
- define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
+ define('RCUBE_CONFIG_DIR', TESTS_DIR . 'config');
}
require_once(INSTALL_PATH . 'program/include/iniset.php');
-rcmail::get_instance()->config->set('devel_mode', false);
+rcmail::get_instance('test')->config->set('devel_mode', false);
// Extend include path so some plugin test won't fail
$include_path = ini_get('include_path') . PATH_SEPARATOR . TESTS_DIR . '..';
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 31, 4:26 AM (12 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426155
Default Alt Text
(247 KB)

Event Timeline