Page MenuHomePhorge

No OneTemporary

Size
269 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/program/actions/settings/identity_delete.php b/program/actions/settings/identity_delete.php
index 5f3aeb951..1ca2b50a4 100644
--- a/program/actions/settings/identity_delete.php
+++ b/program/actions/settings/identity_delete.php
@@ -1,51 +1,52 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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: |
| A handler for identity delete action |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_identity_delete extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
- $rcmail = rcmail::get_instance();
- $iid = rcube_utils::get_input_value('_iid', rcube_utils::INPUT_POST);
+ $rcmail = rcmail::get_instance();
+ $iid = rcube_utils::get_input_value('_iid', rcube_utils::INPUT_POST);
+ $deleted = 0;
if ($iid && preg_match('/^[0-9]+(,[0-9]+)*$/', $iid)) {
$plugin = $rcmail->plugins->exec_hook('identity_delete', ['id' => $iid]);
$deleted = !$plugin['abort'] ? $rcmail->user->delete_identity($iid) : $plugin['result'];
+ }
- if ($deleted > 0 && $deleted !== false) {
- $rcmail->output->show_message('deletedsuccessfully', 'confirmation', null, false);
- $rcmail->output->command('remove_identity', $iid);
- }
- else {
- $msg = $plugin['message'] ?: ($deleted < 0 ? 'nodeletelastidentity' : 'errorsaving');
- $rcmail->output->show_message($msg, 'error', null, false);
- }
+ if ($deleted > 0 && $deleted !== false) {
+ $rcmail->output->show_message('deletedsuccessfully', 'confirmation', null, false);
+ $rcmail->output->command('remove_identity', $iid);
+ }
+ else {
+ $msg = !empty($plugin['message']) ? $plugin['message'] : ($deleted < 0 ? 'nodeletelastidentity' : 'errorsaving');
+ $rcmail->output->show_message($msg, 'error', null, false);
}
$rcmail->output->send();
}
}
diff --git a/program/actions/settings/identity_edit.php b/program/actions/settings/identity_edit.php
index 3f9d3ca2d..4252bfeb6 100644
--- a/program/actions/settings/identity_edit.php
+++ b/program/actions/settings/identity_edit.php
@@ -1,228 +1,241 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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: |
| Show edit form for an identity record |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_identity_edit extends rcmail_action
{
protected static $mode = self::MODE_HTTP;
protected static $record;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$IDENTITIES_LEVEL = intval($rcmail->config->get('identities_level', 0));
// edit-identity
- if (($_GET['_iid'] || $_POST['_iid']) && $rcmail->action == 'edit-identity') {
+ if ((!empty($_GET['_iid']) || !empty($_POST['_iid'])) && $rcmail->action == 'edit-identity') {
$id = rcube_utils::get_input_value('_iid', rcube_utils::INPUT_GPC);
self::$record = $rcmail->user->get_identity($id);
if (!is_array(self::$record)) {
$rcmail->output->show_message('dberror', 'error');
// go to identities page
$rcmail->overwrite_action('identities');
return;
}
$rcmail->output->set_env('iid', self::$record['identity_id']);
$rcmail->output->set_env('mailvelope_main_keyring', $rcmail->config->get('mailvelope_main_keyring'));
$rcmail->output->set_env('mailvelope_keysize', $rcmail->config->get('mailvelope_keysize'));
}
// add-identity
else {
if ($IDENTITIES_LEVEL > 1) {
$rcmail->output->show_message('opnotpermitted', 'error');
// go to identities page
$rcmail->overwrite_action('identities');
return;
}
if ($IDENTITIES_LEVEL == 1) {
self::$record['email'] = $rcmail->get_user_email();
}
}
- $rcmail->output->include_script('list.js');
$rcmail->output->add_handler('identityform', [$this, 'identity_form']);
$rcmail->output->set_env('identities_level', $IDENTITIES_LEVEL);
$rcmail->output->add_label('deleteidentityconfirm', 'generate',
'encryptioncreatekey', 'openmailvelopesettings', 'encryptionprivkeysinmailvelope',
'encryptionnoprivkeysinmailvelope', 'keypaircreatesuccess');
$rcmail->output->set_pagetitle($rcmail->gettext(($rcmail->action == 'add-identity' ? 'addidentity' : 'editidentity')));
if ($rcmail->action == 'add-identity' && $rcmail->output->template_exists('identityadd')) {
$rcmail->output->send('identityadd');
}
$rcmail->output->send('identityedit');
}
public static function identity_form($attrib)
{
$rcmail = rcmail::get_instance();
$IDENTITIES_LEVEL = intval($rcmail->config->get('identities_level', 0));
// Add HTML editor script(s)
self::html_editor('identity');
// add some labels to client
$rcmail->output->add_label('noemailwarning', 'converting', 'editorwarning');
- $i_size = $attrib['size'] ?: 40;
- $t_rows = $attrib['textarearows'] ?: 6;
- $t_cols = $attrib['textareacols'] ?: 40;
+ $i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
+ $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6;
+ $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
// list of available cols
$form = [
'addressing' => [
'name' => $rcmail->gettext('settings'),
'content' => [
'name' => ['type' => 'text', 'size' => $i_size],
'email' => ['type' => 'text', 'size' => $i_size],
'organization' => ['type' => 'text', 'size' => $i_size],
'reply-to' => ['type' => 'text', 'size' => $i_size],
'bcc' => ['type' => 'text', 'size' => $i_size],
'standard' => ['type' => 'checkbox', 'label' => $rcmail->gettext('setdefault')],
]
],
'signature' => [
'name' => $rcmail->gettext('signature'),
'content' => [
'signature' => [
'type' => 'textarea',
'size' => $t_cols,
'rows' => $t_rows,
'spellcheck' => true,
'data-html-editor' => true
],
'html_signature' => [
'type' => 'checkbox',
'label' => $rcmail->gettext('htmlsignature'),
'onclick' => "return rcmail.command('toggle-editor', {id: 'rcmfd_signature', html: this.checked}, '', event)"
],
]
],
'encryption' => [
'name' => $rcmail->gettext('identityencryption'),
'attrs' => ['class' => 'identity-encryption', 'style' => 'display:none'],
'content' => html::div('identity-encryption-block', '')
]
];
// Enable TinyMCE editor
- if (self::$record['html_signature']) {
+ if (!empty(self::$record['html_signature'])) {
$form['signature']['content']['signature']['class'] = 'mce_editor';
$form['signature']['content']['signature']['is_escaped'] = true;
// Correctly handle HTML entities in HTML editor (#1488483)
self::$record['signature'] = htmlspecialchars(self::$record['signature'], ENT_NOQUOTES, RCUBE_CHARSET);
}
// hide "default" checkbox if only one identity is allowed
if ($IDENTITIES_LEVEL > 1) {
unset($form['addressing']['content']['standard']);
}
// disable some field according to access level
if ($IDENTITIES_LEVEL == 1 || $IDENTITIES_LEVEL == 3) {
$form['addressing']['content']['email']['disabled'] = true;
$form['addressing']['content']['email']['class'] = 'disabled';
}
if ($IDENTITIES_LEVEL == 4) {
foreach ($form['addressing']['content'] as $formfield => $value){
$form['addressing']['content'][$formfield]['disabled'] = true;
$form['addressing']['content'][$formfield]['class'] = 'disabled';
}
}
- self::$record['email'] = rcube_utils::idn_to_utf8(self::$record['email']);
+ if (!empty(self::$record['email'])) {
+ self::$record['email'] = rcube_utils::idn_to_utf8(self::$record['email']);
+ }
// Allow plugins to modify identity form content
$plugin = $rcmail->plugins->exec_hook('identity_form', [
'form' => $form,
'record' => self::$record
]);
$form = $plugin['form'];
self::$record = $plugin['record'];
// Set form tags and hidden fields
list($form_start, $form_end) = self::get_form_tags($attrib, 'save-identity',
- intval(self::$record['identity_id']),
- ['name' => '_iid', 'value' => self::$record['identity_id']]
+ isset(self::$record['identity_id']) ? intval(self::$record['identity_id']) : 0,
+ ['name' => '_iid', 'value' => isset(self::$record['identity_id']) ? self::$record['identity_id'] : 0]
);
unset($plugin);
unset($attrib['form'], $attrib['id']);
// return the complete edit form as table
$out = "$form_start\n";
foreach ($form as $fieldset) {
if (empty($fieldset['content'])) {
continue;
}
$content = '';
if (is_array($fieldset['content'])) {
$table = new html_table(['cols' => 2]);
foreach ($fieldset['content'] as $col => $colprop) {
$colprop['id'] = 'rcmfd_'.$col;
- $label = $colprop['label'] ?: $rcmail->gettext(str_replace('-', '', $col));
- $value = $colprop['value'] ?: rcube_output::get_edit_field($col, self::$record[$col], $colprop, $colprop['type']);
+ if (!empty($colprop['label'])) {
+ $label = $colprop['label'];
+ }
+ else {
+ $label = $rcmail->gettext(str_replace('-', '', $col));
+ }
+
+ if (!empty($colprop['value'])) {
+ $value = $colprop['value'];
+ }
+ else {
+ $val = isset(self::$record[$col]) ? self::$record[$col] : '';
+ $value = rcube_output::get_edit_field($col, $val, $colprop, $colprop['type']);
+ }
$table->add('title', html::label($colprop['id'], rcube::Q($label)));
$table->add(null, $value);
}
$content = $table->show($attrib);
}
else {
$content = $fieldset['content'];
}
$content = html::tag('legend', null, rcube::Q($fieldset['name'])) . $content;
- $out .= html::tag('fieldset', $fieldset['attrs'], $content) . "\n";
+ $out .= html::tag('fieldset', !empty($fieldset['attrs']) ? $fieldset['attrs'] : [], $content) . "\n";
}
$out .= $form_end;
// add image upload form
$max_size = self::upload_init($rcmail->config->get('identity_image_size', 64) * 1024);
$form_id = 'identityImageUpload';
$out .= '<form id="' . $form_id . '" style="display: none">'
. html::div('hint', $rcmail->gettext(['name' => 'maxuploadsize', 'vars' => ['size' => $max_size]]))
. '</form>';
$rcmail->output->add_gui_object('uploadform', $form_id);
return $out;
}
}
diff --git a/program/actions/settings/identity_save.php b/program/actions/settings/identity_save.php
index de69d6cef..19e100b66 100644
--- a/program/actions/settings/identity_save.php
+++ b/program/actions/settings/identity_save.php
@@ -1,270 +1,274 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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: |
| Save an identity record or to add a new one |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_identity_save extends rcmail_action
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$IDENTITIES_LEVEL = intval($rcmail->config->get('identities_level', 0));
$a_save_cols = ['name', 'email', 'organization', 'reply-to', 'bcc', 'standard', 'signature', 'html_signature'];
$a_boolean_cols = ['standard', 'html_signature'];
$updated = $default_id = false;
// check input
if (empty($_POST['_email']) && ($IDENTITIES_LEVEL == 0 || $IDENTITIES_LEVEL == 2)) {
$rcmail->output->show_message('noemailwarning', 'warning');
$rcmail->overwrite_action('edit-identity');
return;
}
$save_data = [];
foreach ($a_save_cols as $col) {
$fname = '_'.$col;
if (isset($_POST[$fname])) {
$save_data[$col] = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
}
}
// set "off" values for checkboxes that were not checked, and therefore
// not included in the POST body.
foreach ($a_boolean_cols as $col) {
$fname = '_' . $col;
if (!isset($_POST[$fname])) {
$save_data[$col] = 0;
}
}
// make the identity a "default" if only one identity is allowed
if ($IDENTITIES_LEVEL > 1) {
$save_data['standard'] = 1;
}
// unset email address if user has no rights to change it
if ($IDENTITIES_LEVEL == 1 || $IDENTITIES_LEVEL == 3) {
unset($save_data['email']);
}
// unset all fields except signature
else if ($IDENTITIES_LEVEL == 4) {
foreach ($save_data as $idx => $value) {
if ($idx != 'signature' && $idx != 'html_signature') {
unset($save_data[$idx]);
}
}
}
// Validate e-mail addresses
$email_checks = [rcube_utils::idn_to_ascii($save_data['email'])];
foreach (['reply-to', 'bcc'] as $item) {
- foreach (rcube_mime::decode_address_list($save_data[$item], null, false) as $rcpt) {
- $email_checks[] = rcube_utils::idn_to_ascii($rcpt['mailto']);
+ if (!empty($save_data[$item])) {
+ foreach (rcube_mime::decode_address_list($save_data[$item], null, false) as $rcpt) {
+ $email_checks[] = rcube_utils::idn_to_ascii($rcpt['mailto']);
+ }
}
}
foreach ($email_checks as $email) {
if ($email && !rcube_utils::check_email($email)) {
// show error message
$rcmail->output->show_message('emailformaterror', 'error', ['email' => rcube_utils::idn_to_utf8($email)], false);
$rcmail->overwrite_action('edit-identity');
return;
}
}
if (!empty($save_data['signature']) && !empty($save_data['html_signature'])) {
// replace uploaded images with data URIs
$save_data['signature'] = self::attach_images($save_data['signature']);
// XSS protection in HTML signature (#1489251)
$save_data['signature'] = self::wash_html($save_data['signature']);
// clear POST data of signature, we want to use safe content
// when the form is displayed again
unset($_POST['_signature']);
}
// update an existing identity
if (!empty($_POST['_iid'])) {
$iid = rcube_utils::get_input_value('_iid', rcube_utils::INPUT_POST);
if (in_array($IDENTITIES_LEVEL, [1, 3, 4])) {
// merge with old identity data, fixes #1488834
$identity = $rcmail->user->get_identity($iid);
$save_data = array_merge($identity, $save_data);
unset($save_data['changed'], $save_data['del'], $save_data['user_id'], $save_data['identity_id']);
}
$plugin = $rcmail->plugins->exec_hook('identity_update', ['id' => $iid, 'record' => $save_data]);
$save_data = $plugin['record'];
if ($save_data['email']) {
$save_data['email'] = rcube_utils::idn_to_ascii($save_data['email']);
}
if (!$plugin['abort']) {
$updated = $rcmail->user->update_identity($iid, $save_data);
}
else {
$updated = $plugin['result'];
}
if ($updated) {
$rcmail->output->show_message('successfullysaved', 'confirmation');
if (!empty($save_data['standard'])) {
$default_id = $iid;
}
// update the changed col in list
$name = $save_data['name'] . ' <' . rcube_utils::idn_to_utf8($save_data['email']) .'>';
$rcmail->output->command('parent.update_identity_row', $iid, rcube::Q(trim($name)));
}
else {
// show error message
- $rcmail->output->show_message($plugin['message'] ?: 'errorsaving', 'error', null, false);
+ $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
+ $rcmail->output->show_message($error, 'error', null, false);
$rcmail->overwrite_action('edit-identity');
return;
}
}
// insert a new identity record
else if ($IDENTITIES_LEVEL < 2) {
if ($IDENTITIES_LEVEL == 1) {
$save_data['email'] = $rcmail->get_user_email();
}
$plugin = $rcmail->plugins->exec_hook('identity_create', ['record' => $save_data]);
$save_data = $plugin['record'];
if ($save_data['email']) {
$save_data['email'] = rcube_utils::idn_to_ascii($save_data['email']);
}
if (!$plugin['abort']) {
$insert_id = $save_data['email'] ? $rcmail->user->insert_identity($save_data) : null;
}
else {
$insert_id = $plugin['result'];
}
if ($insert_id) {
$rcmail->plugins->exec_hook('identity_create_after', ['id' => $insert_id, 'record' => $save_data]);
$rcmail->output->show_message('successfullysaved', 'confirmation', null, false);
$_GET['_iid'] = $insert_id;
if (!empty($save_data['standard'])) {
$default_id = $insert_id;
}
// add a new row to the list
$name = $save_data['name'] . ' <' . rcube_utils::idn_to_utf8($save_data['email']) .'>';
$rcmail->output->command('parent.update_identity_row', $insert_id, rcube::Q(trim($name)), true);
}
else {
// show error message
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error', null, false);
$rcmail->overwrite_action('edit-identity');
return;
}
}
else {
$rcmail->output->show_message('opnotpermitted', 'error');
}
// mark all other identities as 'not-default'
- if ($default_id) {
+ if (!empty($default_id)) {
$rcmail->user->set_default($default_id);
}
// go to next step
$rcmail->overwrite_action('edit-identity');
}
/**
* Attach uploaded images into signature as data URIs
*/
public static function attach_images($html)
{
$rcmail = rcmail::get_instance();
$offset = 0;
$regexp = '/\s(poster|src)\s*=\s*[\'"]*\S+upload-display\S+file=rcmfile(\w+)[\s\'"]*/';
while (preg_match($regexp, $html, $matches, 0, $offset)) {
$file_id = $matches[2];
$data_uri = ' ';
- if ($file_id && ($file = $_SESSION['identity']['files'][$file_id])) {
+ if ($file_id && !empty($_SESSION['identity']['files'][$file_id])) {
+ $file = $_SESSION['identity']['files'][$file_id];
$file = $rcmail->plugins->exec_hook('attachment_get', $file);
$data_uri .= 'src="data:' . $file['mimetype'] . ';base64,';
$data_uri .= base64_encode($file['data'] ?: file_get_contents($file['path']));
$data_uri .= '" ';
}
$html = str_replace($matches[0], $data_uri, $html);
$offset += strlen($data_uri) - strlen($matches[0]) + 1;
}
return $html;
}
/**
* Sanity checks/cleanups on HTML body of signature
*/
public static function wash_html($html)
{
// Add header with charset spec., washtml cannot work without that
$html = '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset='.RCUBE_CHARSET.'" />'
. '</head><body>' . $html . '</body></html>';
// clean HTML with washhtml by Frederic Motte
$wash_opts = [
'show_washed' => false,
'allow_remote' => 1,
'charset' => RCUBE_CHARSET,
'html_elements' => ['body', 'link'],
'html_attribs' => ['rel', 'type'],
];
// initialize HTML washer
$washer = new rcube_washtml($wash_opts);
// Remove non-UTF8 characters (#1487813)
$html = rcube_charset::clean($html);
$html = $washer->wash($html);
// remove unwanted comments and tags (produced by washtml)
$html = preg_replace(['/<!--[^>]+-->/', '/<\/?body>/'], '', $html);
return $html;
}
}
diff --git a/program/actions/settings/index.php b/program/actions/settings/index.php
index 93ff99aca..acd258f26 100644
--- a/program/actions/settings/index.php
+++ b/program/actions/settings/index.php
@@ -1,1690 +1,1712 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality for user's settings & preferences |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_index extends rcmail_action
{
/**
* Deprecated action aliases.
*
* @var array
*/
public static $aliases = [
'rename-folder' => 'folder-rename',
'subscribe' => 'folder-subscribe',
'unsubscribe' => 'folder-unsubscribe',
'purge' => 'folder-purge',
'add-folder' => 'folder-create',
'add-identity' => 'identity-create',
'add-response' => 'response-create',
'delete-folder' => 'folder-delete',
'delete-identity' => 'identity-delete',
'delete-response' => 'response-delete',
'edit-folder' => 'folder-edit',
'edit-identity' => 'identity-edit',
'edit-prefs' => 'prefs-edit',
'edit-response' => 'response-edit',
'save-folder' => 'folder-save',
'save-identity' => 'identity-save',
'save-prefs' => 'prefs-save',
'save-response' => 'response-save',
];
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$rcmail->output->set_pagetitle($rcmail->gettext('preferences'));
// register UI objects
$rcmail->output->add_handlers([
'settingstabs' => [$this, 'settings_tabs'],
'sectionslist' => [$this, 'sections_list'],
]);
}
+ /**
+ * Render and initialize the settings sections table
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML content
+ */
public static function sections_list($attrib)
{
$rcmail = rcmail::get_instance();
// add id to message list table if not specified
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmsectionslist';
}
list($list, $cols) = self::user_prefs();
// create XHTML table
$out = self::table_output($attrib, $list, $cols, 'id');
// set client env
$rcmail->output->add_gui_object('sectionslist', $attrib['id']);
$rcmail->output->include_script('list.js');
return $out;
}
public static function user_prefs($current = null)
{
$rcmail = rcmail::get_instance();
$sections['general'] = ['id' => 'general', 'section' => $rcmail->gettext('uisettings')];
$sections['mailbox'] = ['id' => 'mailbox', 'section' => $rcmail->gettext('mailboxview')];
$sections['mailview'] = ['id' => 'mailview','section' => $rcmail->gettext('messagesdisplaying')];
$sections['compose'] = ['id' => 'compose', 'section' => $rcmail->gettext('messagescomposition')];
$sections['addressbook'] = ['id' => 'addressbook','section' => $rcmail->gettext('contacts')];
$sections['folders'] = ['id' => 'folders', 'section' => $rcmail->gettext('specialfolders')];
$sections['server'] = ['id' => 'server', 'section' => $rcmail->gettext('serversettings')];
$sections['encryption'] = ['id' => 'encryption', 'section' => $rcmail->gettext('encryption')];
// hook + define list cols
$plugin = $rcmail->plugins->exec_hook('preferences_sections_list', [
'list' => $sections,
'cols' => ['section']
]);
$sections = $plugin['list'];
$config = $rcmail->config->all();
$no_override = array_flip((array) $rcmail->config->get('dont_override'));
foreach ($sections as $idx => $sect) {
- $sections[$idx]['class'] = $sect['class'] ?: $idx;
+ $sections[$idx]['class'] = !empty($sect['class']) ? $sect['class'] : $idx;
if ($current && $sect['id'] != $current) {
continue;
}
$blocks = [];
switch ($sect['id']) {
// general
case 'general':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'skin' => ['name' => rcube::Q($rcmail->gettext('skin'))],
'browser' => ['name' => rcube::Q($rcmail->gettext('browseroptions'))],
'advanced'=> ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
// language selection
if (!isset($no_override['language'])) {
if (!$current) {
continue 2;
}
$a_lang = $rcmail->list_languages();
asort($a_lang);
$field_id = 'rcmfd_lang';
$select = new html_select([
'name' => '_language',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add(array_values($a_lang), array_keys($a_lang));
$blocks['main']['options']['language'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('language'))),
'content' => $select->show($rcmail->user->language),
];
}
// timezone selection
if (!isset($no_override['timezone'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_timezone';
$select = new html_select([
'name' => '_timezone',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('autodetect'), 'auto');
$zones = [];
foreach (DateTimeZone::listIdentifiers() as $i => $tzs) {
if ($data = self::timezone_standard_time_data($tzs)) {
$zones[$data['key']] = [$tzs, $data['offset']];
}
}
ksort($zones);
foreach ($zones as $zone) {
list($tzs, $offset) = $zone;
$select->add('(GMT ' . $offset . ') ' . self::timezone_label($tzs), $tzs);
}
$blocks['main']['options']['timezone'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('timezone'))),
'content' => $select->show((string)$config['timezone']),
];
}
// date/time formatting
if (!isset($no_override['time_format'])) {
if (!$current) {
continue 2;
}
$reftime = mktime(7,30,0);
$defaults = ['G:i', 'H:i', 'g:i a', 'h:i A'];
$formats = (array) $rcmail->config->get('time_formats', $defaults);
$field_id = 'rcmfd_time_format';
$select = new html_select([
'name' => '_time_format',
'id' => $field_id,
'class' => 'custom-select'
]);
foreach ($formats as $choice) {
$select->add(date($choice, $reftime), $choice);
}
$blocks['main']['options']['time_format'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('timeformat'))),
'content' => $select->show($rcmail->config->get('time_format')),
];
}
if (!isset($no_override['date_format'])) {
if (!$current) {
continue 2;
}
$refdate = mktime(12,30,0,7,24);
$defaults = ['Y-m-d','d-m-Y','Y/m/d','m/d/Y','d/m/Y','d.m.Y','j.n.Y'];
$formats = (array) $rcmail->config->get('date_formats', $defaults);
$field_id = 'rcmfd_date_format';
$select = new html_select([
'name' => '_date_format',
'id' => $field_id,
'class' => 'custom-select'
]);
foreach ($formats as $choice) {
$select->add(date($choice, $refdate), $choice);
}
$blocks['main']['options']['date_format'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('dateformat'))),
'content' => $select->show($config['date_format']),
];
}
// Show checkbox for toggling 'pretty dates'
if (!isset($no_override['prettydate'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_prettydate';
$input = new html_checkbox([
'name' => '_pretty_date',
'id' => $field_id,
'value' => 1
]);
$blocks['main']['options']['prettydate'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('prettydate'))),
'content' => $input->show($config['prettydate']?1:0),
];
}
// "display after delete" checkbox
if (!isset($no_override['display_next'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_displaynext';
$input = new html_checkbox([
'name' => '_display_next',
'id' => $field_id,
'value' => 1
]);
$blocks['main']['options']['display_next'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('displaynext'))),
'content' => $input->show($config['display_next']?1:0),
];
}
if (!isset($no_override['refresh_interval'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_refresh_interval';
$select = new html_select([
'name' => '_refresh_interval',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('never'), 0);
foreach ([1, 3, 5, 10, 15, 30, 60] as $min) {
if (!$config['min_refresh_interval'] || $config['min_refresh_interval'] <= $min * 60) {
$label = $rcmail->gettext(['name' => 'everynminutes', 'vars' => ['n' => $min]]);
$select->add($label, $min);
}
}
$blocks['main']['options']['refresh_interval'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('refreshinterval'))),
'content' => $select->show($config['refresh_interval']/60),
];
}
// show drop-down for available skins
if (!isset($no_override['skin'])) {
if (!$current) {
continue 2;
}
$skins = self::get_skins();
if (count($skins) > 1) {
- $field_id = 'rcmfd_skin';
- $input = new html_radiobutton(['name' => '_skin']);
- $skinnames = [];
+ sort($skins);
+
+ $field_id = 'rcmfd_skin';
+ $input = new html_radiobutton(['name' => '_skin']);
foreach ($skins as $skin) {
- $skinname = ucfirst($skin);
- $author_link = $license_link = '';
- $meta = @json_decode(@file_get_contents(INSTALL_PATH . "skins/$skin/meta.json"), true);
+ $skinname = ucfirst($skin);
+ $author_link = '';
+ $license_link = '';
+ $meta = @json_decode(@file_get_contents(INSTALL_PATH . "skins/$skin/meta.json"), true);
if (is_array($meta) && !empty($meta['name'])) {
$skinname = $meta['name'];
- $author_link = $meta['url'] ? html::a(['href' => $meta['url'], 'target' => '_blank'], rcube::Q($meta['author'])) : rcube::Q($meta['author']);
- $license_link = $meta['license-url'] ? html::a(['href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'], rcube::Q($meta['license'])) : rcube::Q($meta['license']);
+ $author_link = !empty($meta['url']) ? html::a(['href' => $meta['url'], 'target' => '_blank'], rcube::Q($meta['author'])) : rcube::Q($meta['author']);
+ $license_link = !empty($meta['license-url']) ? html::a(['href' => $meta['license-url'], 'target' => '_blank', 'tabindex' => '-1'], rcube::Q($meta['license'])) : rcube::Q($meta['license']);
}
$img = html::img([
'src' => $rcmail->output->asset_url("skins/$skin/thumbnail.png"),
'class' => 'skinthumbnail',
'alt' => $skin,
'width' => 64,
'height' => 64,
'onerror' => "this.src = rcmail.assets_path('program/resources/blank.gif'); this.onerror = null",
]);
- $skinnames[] = mb_strtolower($skinname);
$blocks['skin']['options'][$skin]['content'] = html::label(['class' => 'skinselection'],
html::span('skinitem', $input->show($config['skin'], ['value' => $skin, 'id' => $field_id.$skin])) .
html::span('skinitem', $img) .
html::span('skinitem', html::span('skinname', rcube::Q($skinname)) . html::br() .
html::span('skinauthor', $author_link ? 'by ' . $author_link : '') . html::br() .
html::span('skinlicense', $license_link ? $rcmail->gettext('license').':&nbsp;' . $license_link : ''))
);
}
- array_multisort($blocks['skin']['options'], SORT_ASC, SORT_STRING, $skinnames);
}
}
// standard_windows option decides if new windows should be
// opened as popups or standard windows (which can be handled by browsers as tabs)
if (!isset($no_override['standard_windows'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_standard_windows';
$checkbox = new html_checkbox([
'name' => '_standard_windows',
'id' => $field_id,
'value' => 1
]);
$blocks['browser']['options']['standard_windows'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('standardwindows'))),
'content' => $checkbox->show($config['standard_windows']?1:0),
];
}
if ($current) {
$product_name = $rcmail->config->get('product_name', 'Roundcube Webmail');
$rcmail->output->add_script(sprintf("%s.check_protocol_handler('%s', '#mailtoprotohandler');",
rcmail_output::JS_OBJECT_NAME, rcube::JQ($product_name)), 'docready');
}
$blocks['browser']['options']['mailtoprotohandler'] = [
'content' => html::a(['href' => '#', 'id' => 'mailtoprotohandler'],
rcube::Q($rcmail->gettext('mailtoprotohandler'))) .
html::span('mailtoprotohandler-status', ''),
];
break;
// Mailbox view (mail screen)
case 'mailbox':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'new_message' => ['name' => rcube::Q($rcmail->gettext('newmessage'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
if (!isset($no_override['layout']) && count($config['supported_layouts']) > 1) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_layout';
$select = new html_select([
'name' => '_layout',
'id' => $field_id,
'class' => 'custom-select'
]);
$layouts = [
'widescreen' => 'layoutwidescreendesc',
'desktop' => 'layoutdesktopdesc',
'list' => 'layoutlistdesc'
];
$available_layouts = array_intersect_key($layouts, array_flip($config['supported_layouts']));
foreach ($available_layouts as $val => $label) {
$select->add($rcmail->gettext($label), $val);
}
$blocks['main']['options']['layout'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('layout'))),
'content' => $select->show($config['layout'] ?: 'widescreen'),
];
}
// show config parameter for auto marking the previewed message as read
if (!isset($no_override['mail_read_time'])) {
if (!$current) {
continue 2;
}
// apply default if config option is not set at all
$config['mail_read_time'] = intval($rcmail->config->get('mail_read_time'));
$field_id = 'rcmfd_mail_read_time';
$select = new html_select([
'name' => '_mail_read_time',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('never'), -1);
$select->add($rcmail->gettext('immediately'), 0);
foreach ([5, 10, 20, 30] as $sec) {
$label = $rcmail->gettext(['name' => 'afternseconds', 'vars' => ['n' => $sec]]);
$select->add($label, $sec);
}
$blocks['main']['options']['mail_read_time'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('automarkread'))),
'content' => $select->show($config['mail_read_time']),
];
}
if (!isset($no_override['autoexpand_threads'])) {
if (!$current) {
continue 2;
}
$storage = $rcmail->get_storage();
$supported = $storage->get_capability('THREAD');
if ($supported) {
$field_id = 'rcmfd_autoexpand_threads';
$select = new html_select([
'name' => '_autoexpand_threads',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('never'), 0);
$select->add($rcmail->gettext('do_expand'), 1);
$select->add($rcmail->gettext('expand_only_unread'), 2);
$blocks['main']['options']['autoexpand_threads'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('autoexpand_threads'))),
'content' => $select->show($config['autoexpand_threads']),
];
}
}
// show page size selection
if (!isset($no_override['mail_pagesize'])) {
if (!$current) {
continue 2;
}
$size = intval($config['mail_pagesize'] ?: $config['pagesize']);
$field_id = 'rcmfd_mail_pagesize';
$input = new html_inputfield([
'name' => '_mail_pagesize',
'id' => $field_id,
'size' => 5,
'class' => 'form-control'
]);
$blocks['main']['options']['pagesize'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('pagesize'))),
'content' => $input->show($size ?: 50),
];
}
if (!isset($no_override['check_all_folders'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_check_all_folders';
$input = new html_checkbox([
'name' => '_check_all_folders',
'id' => $field_id,
'value' => 1
]);
$blocks['new_message']['options']['check_all_folders'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('checkallfolders'))),
'content' => $input->show($config['check_all_folders']?1:0),
];
}
break;
// Message viewing
case 'mailview':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
// show checkbox to open message view in new window
if (!isset($no_override['message_extwin'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_message_extwin';
$input = new html_checkbox(['name' => '_message_extwin', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['message_extwin'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('showinextwin'))),
'content' => $input->show($config['message_extwin']?1:0),
];
}
// show checkbox to show email instead of name
if (!isset($no_override['message_show_email'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_message_show_email';
$input = new html_checkbox(['name' => '_message_show_email', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['message_show_email'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('showemail'))),
'content' => $input->show($config['message_show_email']?1:0),
];
}
// show checkbox for HTML/plaintext messages
if (!isset($no_override['prefer_html'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_htmlmsg';
$input = new html_checkbox([
'name' => '_prefer_html',
'id' => $field_id,
'value' => 1,
'onchange' => "$('#rcmfd_show_images').prop('disabled', !this.checked).val(0)"
]);
$blocks['main']['options']['prefer_html'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('preferhtml'))),
'content' => $input->show($config['prefer_html']?1:0),
];
}
if (!isset($no_override['default_charset'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_default_charset';
$blocks['advanced']['options']['default_charset'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('defaultcharset'))),
'content' => $rcmail->output->charset_selector([
'id' => $field_id,
'name' => '_default_charset',
'selected' => $config['default_charset'],
'class' => 'custom-select',
])
];
}
if (!isset($no_override['show_images'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_show_images';
$input = new html_select([
'name' => '_show_images',
'id' => $field_id,
'class' => 'custom-select',
'disabled' => empty($config['prefer_html'])
]);
$input->add($rcmail->gettext('never'), 0);
$input->add($rcmail->gettext('frommycontacts'), 3);
$input->add($rcmail->gettext('fromtrustedsenders'), 1);
$input->add($rcmail->gettext('always'), 2);
$blocks['main']['options']['show_images'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('allowremoteresources'))),
'content' => $input->show(!empty($config['prefer_html']) ? $config['show_images'] : 0),
];
}
if (!isset($no_override['mdn_requests'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_mdn_requests';
$select = new html_select([
'name' => '_mdn_requests',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('askuser'), 0);
$select->add($rcmail->gettext('autosend'), 1);
$select->add($rcmail->gettext('autosendknown'), 3);
$select->add($rcmail->gettext('autosendknownignore'), 4);
$select->add($rcmail->gettext('autosendtrusted'), 5);
$select->add($rcmail->gettext('autosendtrustedignore'), 6);
$select->add($rcmail->gettext('ignorerequest'), 2);
$blocks['main']['options']['mdn_requests'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('mdnrequests'))),
'content' => $select->show($config['mdn_requests']),
];
}
if (!isset($no_override['inline_images'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_inline_images';
$input = new html_checkbox(['name' => '_inline_images', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['inline_images'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('showinlineimages'))),
'content' => $input->show($config['inline_images']?1:0),
];
}
break;
// Mail composition
case 'compose':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'sig' => ['name' => rcube::Q($rcmail->gettext('signatureoptions'))],
'spellcheck' => ['name' => rcube::Q($rcmail->gettext('spellcheckoptions'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
// show checkbox to compose messages in a new window
if (!isset($no_override['compose_extwin'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfdcompose_extwin';
$input = new html_checkbox(['name' => '_compose_extwin', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['compose_extwin'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('composeextwin'))),
'content' => $input->show($config['compose_extwin']?1:0),
];
}
if (!isset($no_override['htmleditor'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_htmleditor';
$select = new html_select([
'name' => '_htmleditor',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('never'), 0);
$select->add($rcmail->gettext('htmlonreply'), 2);
$select->add($rcmail->gettext('htmlonreplyandforward'), 3);
$select->add($rcmail->gettext('always'), 1);
$select->add($rcmail->gettext('alwaysbutplain'), 4);
$blocks['main']['options']['htmleditor'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('htmleditor'))),
'content' => $select->show(intval($config['htmleditor'])),
];
}
if (!isset($no_override['draft_autosave'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_autosave';
$select = new html_select([
'name' => '_draft_autosave',
'id' => $field_id,
'class' => 'custom-select',
'disabled' => empty($config['drafts_mbox'])
]);
$select->add($rcmail->gettext('never'), 0);
foreach ([1, 3, 5, 10] as $i => $min) {
$label = $rcmail->gettext(['name' => 'everynminutes', 'vars' => ['n' => $min]]);
$select->add($label, $min * 60);
}
$blocks['main']['options']['draft_autosave'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('autosavedraft'))),
'content' => $select->show($config['draft_autosave']),
];
}
if (!isset($no_override['mime_param_folding'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_param_folding';
$select = new html_select([
'name' => '_mime_param_folding',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('2231folding'), 0);
$select->add($rcmail->gettext('miscfolding'), 1);
$select->add($rcmail->gettext('2047folding'), 2);
$blocks['advanced']['options']['mime_param_folding'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('mimeparamfolding'))),
'content' => $select->show($config['mime_param_folding']),
];
}
if (!isset($no_override['force_7bit'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_force_7bit';
$input = new html_checkbox(['name' => '_force_7bit', 'id' => $field_id, 'value' => 1]);
$blocks['advanced']['options']['force_7bit'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('force7bit'))),
'content' => $input->show($config['force_7bit']?1:0),
];
}
if (!isset($no_override['mdn_default'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_mdn_default';
$input = new html_checkbox(['name' => '_mdn_default', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['mdn_default'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('reqmdn'))),
'content' => $input->show($config['mdn_default']?1:0),
];
}
if (!isset($no_override['dsn_default'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_dsn_default';
$input = new html_checkbox(['name' => '_dsn_default', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['dsn_default'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('reqdsn'))),
'content' => $input->show($config['dsn_default']?1:0),
];
}
if (!isset($no_override['reply_same_folder'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_reply_same_folder';
$input = new html_checkbox(['name' => '_reply_same_folder', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['reply_same_folder'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('replysamefolder'))),
'content' => $input->show($config['reply_same_folder']?1:0),
];
}
if (!isset($no_override['reply_mode'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_reply_mode';
$select = new html_select(['name' => '_reply_mode', 'id' => $field_id, 'class' => 'custom-select']);
$select->add($rcmail->gettext('replyempty'), -1);
$select->add($rcmail->gettext('replybottomposting'), 0);
$select->add($rcmail->gettext('replytopposting'), 1);
$select->add($rcmail->gettext('replytoppostingnoindent'), 2);
$blocks['main']['options']['reply_mode'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('whenreplying'))),
'content' => $select->show(intval($config['reply_mode'])),
];
}
if (!isset($no_override['spellcheck_before_send']) && $config['enable_spellcheck']) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_spellcheck_before_send';
$input = new html_checkbox([
'name' => '_spellcheck_before_send',
'id' => $field_id,
'value' => 1
]);
$blocks['spellcheck']['options']['spellcheck_before_send'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('spellcheckbeforesend'))),
'content' => $input->show($config['spellcheck_before_send']?1:0),
];
}
if ($config['enable_spellcheck']) {
if (!$current) {
continue 2;
}
foreach (['syms', 'nums', 'caps'] as $key) {
$key = 'spellcheck_ignore_' . $key;
if (!isset($no_override[$key])) {
$input = new html_checkbox(['name' => '_' . $key, 'id' => 'rcmfd_' . $key, 'value' => 1]);
$blocks['spellcheck']['options'][$key] = [
'title' => html::label('rcmfd_' . $key, rcube::Q($rcmail->gettext(str_replace('_', '', $key)))),
'content' => $input->show($config[$key]?1:0),
];
}
}
}
if (!isset($no_override['show_sig'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_show_sig';
$select = new html_select([
'name' => '_show_sig',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('never'), 0);
$select->add($rcmail->gettext('always'), 1);
$select->add($rcmail->gettext('newmessageonly'), 2);
$select->add($rcmail->gettext('replyandforwardonly'), 3);
$blocks['sig']['options']['show_sig'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('autoaddsignature'))),
'content' => $select->show($rcmail->config->get('show_sig', 1)),
];
}
if (!isset($no_override['sig_below'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_sig_below';
$input = new html_checkbox(['name' => '_sig_below', 'id' => $field_id, 'value' => 1]);
$blocks['sig']['options']['sig_below'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('sigbelow'))),
'content' => $input->show($rcmail->config->get('sig_below') ? 1 : 0),
];
}
if (!isset($no_override['strip_existing_sig'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_strip_existing_sig';
$input = new html_checkbox([
'name' => '_strip_existing_sig',
'id' => $field_id,
'value' => 1
]);
$blocks['sig']['options']['strip_existing_sig'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('replyremovesignature'))),
'content' => $input->show($config['strip_existing_sig']?1:0),
];
}
if (!isset($no_override['sig_separator'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_sig_separator';
$input = new html_checkbox(['name' => '_sig_separator', 'id' => $field_id, 'value' => 1]);
$blocks['sig']['options']['sig_separator'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('sigseparator'))),
'content' => $input->show($rcmail->config->get('sig_separator') ? 1 : 0),
];
}
if (!isset($no_override['forward_attachment'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_forward_attachment';
$select = new html_select([
'name' => '_forward_attachment',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('inline'), 0);
$select->add($rcmail->gettext('asattachment'), 1);
$blocks['main']['options']['forward_attachment'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('forwardmode'))),
'content' => $select->show(intval($config['forward_attachment'])),
];
}
if (!isset($no_override['default_font']) || !isset($no_override['default_font_size'])) {
if (!$current) {
continue 2;
}
// Default font size
$field_id = 'rcmfd_default_font_size';
$select_size = new html_select([
'name' => '_default_font_size',
'id' => $field_id,
'class' => 'custom-select'
]);
$fontsizes = ['', '8pt', '9pt', '10pt', '11pt', '12pt', '14pt', '18pt', '24pt', '36pt'];
foreach ($fontsizes as $size) {
$select_size->add($size, $size);
}
// Default font
$field_id = 'rcmfd_default_font';
$select_font = new html_select([
'name' => '_default_font',
'id' => $field_id,
'class' => 'custom-select'
]);
$select_font->add('', '');
$fonts = self::font_defs();
foreach (array_keys($fonts) as $fname) {
$select_font->add($fname, $fname);
}
$blocks['main']['options']['default_font'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('defaultfont'))),
'content' => html::div('input-group',
$select_font->show($rcmail->config->get('default_font', 1)) .
$select_size->show($rcmail->config->get('default_font_size', 1))
)
];
}
if (!isset($no_override['reply_all_mode'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_reply_all_mode';
$select = new html_select([
'name' => '_reply_all_mode',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('replyalldefault'), 0);
$select->add($rcmail->gettext('replyalllist'), 1);
$blocks['main']['options']['reply_all_mode'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('replyallmode'))),
'content' => $select->show(intval($config['reply_all_mode'])),
];
}
if (!isset($no_override['compose_save_localstorage'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_compose_save_localstorage';
$input = new html_checkbox([
'name' => '_compose_save_localstorage',
'id' => $field_id,
'value' => 1
]);
$blocks['advanced']['options']['compose_save_localstorage'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('savelocalstorage'))),
'content' => $input->show($config['compose_save_localstorage']?1:0),
];
}
break;
// Addressbook config
case 'addressbook':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'collected' => ['name' => rcube::Q($rcmail->gettext('collectedaddresses'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
if (!isset($no_override['default_addressbook'])
&& (!$current || ($books = $rcmail->get_address_sources(true, true)))
) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_default_addressbook';
$select = new html_select([
'name' => '_default_addressbook',
'id' => $field_id,
'class' => 'custom-select'
]);
if (!empty($books)) {
foreach ($books as $book) {
$select->add(html_entity_decode($book['name'], ENT_COMPAT, 'UTF-8'), $book['id']);
}
}
$blocks['main']['options']['default_addressbook'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('defaultabook'))),
'content' => $select->show($config['default_addressbook']),
];
}
// show addressbook listing mode selection
if (!isset($no_override['addressbook_name_listing'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_addressbook_name_listing';
$select = new html_select([
'name' => '_addressbook_name_listing',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('name'), 0);
$select->add($rcmail->gettext('firstname') . ' ' . $rcmail->gettext('surname'), 1);
$select->add($rcmail->gettext('surname') . ' ' . $rcmail->gettext('firstname'), 2);
$select->add($rcmail->gettext('surname') . ', ' . $rcmail->gettext('firstname'), 3);
$blocks['main']['options']['list_name_listing'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('listnamedisplay'))),
'content' => $select->show($config['addressbook_name_listing']),
];
}
// show addressbook sort column
if (!isset($no_override['addressbook_sort_col'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_addressbook_sort_col';
$select = new html_select([
'name' => '_addressbook_sort_col',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('name'), 'name');
$select->add($rcmail->gettext('firstname'), 'firstname');
$select->add($rcmail->gettext('surname'), 'surname');
$blocks['main']['options']['sort_col'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('listsorting'))),
'content' => $select->show($config['addressbook_sort_col']),
];
}
// show addressbook page size selection
if (!isset($no_override['addressbook_pagesize'])) {
if (!$current) {
continue 2;
}
$size = intval($config['addressbook_pagesize'] ?: $config['pagesize']);
$field_id = 'rcmfd_addressbook_pagesize';
$input = new html_inputfield([
'name' => '_addressbook_pagesize',
'id' => $field_id,
'size' => 5,
'class' => 'form-control'
]);
$blocks['main']['options']['pagesize'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('pagesize'))),
'content' => $input->show($size ?: 50),
];
}
if (!isset($no_override['contact_form_mode'])) {
if (!$current) {
continue 2;
}
$mode = $config['contact_form_mode'] == 'business' ? 'business' : 'private';
$field_id = 'rcmfd_contact_form_mode';
$select = new html_select([
'name' => '_contact_form_mode',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('privatemode'), 'private');
$select->add($rcmail->gettext('businessmode'), 'business');
$blocks['main']['options']['contact_form_mode'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('contactformmode'))),
'content' => $select->show($mode),
];
}
if (!isset($no_override['autocomplete_single'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_autocomplete_single';
$checkbox = new html_checkbox(['name' => '_autocomplete_single', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['autocomplete_single'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('autocompletesingle'))),
'content' => $checkbox->show($config['autocomplete_single']?1:0),
];
}
if (!isset($no_override['collected_recipients'])) {
if (!$current) {
continue 2;
}
if (!isset($books)) {
$books = $rcmail->get_address_sources(true, true);
}
$field_id = 'rcmfd_collected_recipients';
$select = new html_select([
'name' => '_collected_recipients',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add('---', '');
$select->add($rcmail->gettext('collectedrecipients'), (string) rcube_addressbook::TYPE_RECIPIENT);
foreach ($books as $book) {
$select->add(html_entity_decode($book['name'], ENT_COMPAT, 'UTF-8'), $book['id']);
}
$selected = $config['collected_recipients'];
if (is_bool($selected)) {
$selected = $selected ? rcube_addressbook::TYPE_RECIPIENT : '';
}
$blocks['collected']['options']['collected_recipients'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('collectedrecipientsopt'))),
'content' => $select->show((string) $selected),
];
}
if (!isset($no_override['collected_senders'])) {
if (!$current) {
continue 2;
}
if (!isset($books)) {
$books = $rcmail->get_address_sources(true, true);
}
$field_id = 'rcmfd_collected_senders';
$select = new html_select([
'name' => '_collected_senders',
'id' => $field_id,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('trustedsenders'), (string) rcube_addressbook::TYPE_TRUSTED_SENDER);
foreach ($books as $book) {
$select->add(html_entity_decode($book['name'], ENT_COMPAT, 'UTF-8'), $book['id']);
}
$selected = $config['collected_senders'];
if (is_bool($selected)) {
$selected = $selected ? rcube_addressbook::TYPE_TRUSTED_SENDER : '';
}
$blocks['collected']['options']['collected_senders'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('collectedsendersopt'))),
'content' => $select->show((string) $selected),
];
}
break;
// Special IMAP folders
case 'folders':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
if (!isset($no_override['show_real_foldernames'])) {
if (!$current) {
continue 2;
}
$field_id = 'show_real_foldernames';
$input = new html_checkbox(['name' => '_show_real_foldernames', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['show_real_foldernames'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('show_real_foldernames'))),
'content' => $input->show($config['show_real_foldernames']?1:0),
];
}
// Configure special folders
$set = ['drafts_mbox', 'sent_mbox', 'junk_mbox', 'trash_mbox'];
if ($current && count(array_intersect($no_override, $set)) < 4) {
$select = self::folder_selector([
'noselection' => '---',
'realnames' => true,
'maxlength' => 30,
'folder_filter' => 'mail',
'folder_rights' => 'w',
'class' => 'custom-select',
]);
// #1486114, #1488279, #1489219
$onchange = "if ($(this).val() == 'INBOX') $(this).val('')";
}
else {
$onchange = null;
$select = new html_select();
}
if (!isset($no_override['drafts_mbox'])) {
if (!$current) {
continue 2;
}
$attrs = ['id' => '_drafts_mbox', 'name' => '_drafts_mbox', 'onchange' => $onchange];
$blocks['main']['options']['drafts_mbox'] = [
'title' => html::label($attrs['id'], rcube::Q($rcmail->gettext('drafts'))),
'content' => $select->show($config['drafts_mbox'], $attrs),
];
}
if (!isset($no_override['sent_mbox'])) {
if (!$current) {
continue 2;
}
$attrs = ['id' => '_sent_mbox', 'name' => '_sent_mbox', 'onchange' => ''];
$blocks['main']['options']['sent_mbox'] = [
'title' => html::label($attrs['id'], rcube::Q($rcmail->gettext('sent'))),
'content' => $select->show($config['sent_mbox'], $attrs),
];
}
if (!isset($no_override['junk_mbox'])) {
if (!$current) {
continue 2;
}
$attrs = ['id' => '_junk_mbox', 'name' => '_junk_mbox', 'onchange' => $onchange];
$blocks['main']['options']['junk_mbox'] = [
'title' => html::label($attrs['id'], rcube::Q($rcmail->gettext('junk'))),
'content' => $select->show($config['junk_mbox'], $attrs),
];
}
if (!isset($no_override['trash_mbox'])) {
if (!$current) {
continue 2;
}
$attrs = ['id' => '_trash_mbox', 'name' => '_trash_mbox', 'onchange' => $onchange];
$blocks['main']['options']['trash_mbox'] = [
'title' => html::label($attrs['id'], rcube::Q($rcmail->gettext('trash'))),
'content' => $select->show($config['trash_mbox'], $attrs),
];
}
break;
// Server settings
case 'server':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'maintenance' => ['name' => rcube::Q($rcmail->gettext('maintenance'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
if (!isset($no_override['read_when_deleted'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_read_deleted';
$input = new html_checkbox(['name' => '_read_when_deleted', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['read_when_deleted'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('readwhendeleted'))),
'content' => $input->show($config['read_when_deleted']?1:0),
];
}
if (!isset($no_override['flag_for_deletion'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_flag_for_deletion';
$input = new html_checkbox(['name' => '_flag_for_deletion', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['flag_for_deletion'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('flagfordeletion'))),
'content' => $input->show($config['flag_for_deletion']?1:0),
];
}
// don't show deleted messages
if (!isset($no_override['skip_deleted'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_skip_deleted';
$input = new html_checkbox(['name' => '_skip_deleted', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['skip_deleted'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('skipdeleted'))),
'content' => $input->show($config['skip_deleted']?1:0),
];
}
if (!isset($no_override['delete_junk'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_delete_junk';
$input = new html_checkbox(['name' => '_delete_junk', 'id' => $field_id, 'value' => 1]);
$blocks['main']['options']['delete_junk'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('deletejunk'))),
'content' => $input->show($config['delete_junk']?1:0),
];
}
// Trash purging on logout
if (!isset($no_override['logout_purge'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_logout_purge';
$input = new html_checkbox(['name' => '_logout_purge', 'id' => $field_id, 'value' => 1]);
$blocks['maintenance']['options']['logout_purge'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('logoutclear'))),
'content' => $input->show($config['logout_purge']?1:0),
];
}
// INBOX compacting on logout
if (!isset($no_override['logout_expunge'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_logout_expunge';
$input = new html_checkbox(['name' => '_logout_expunge', 'id' => $field_id, 'value' => 1]);
$blocks['maintenance']['options']['logout_expunge'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('logoutcompact'))),
'content' => $input->show($config['logout_expunge']?1:0),
];
}
break;
// Server settings
case 'encryption':
$blocks = [
'main' => ['name' => rcube::Q($rcmail->gettext('mainoptions'))],
'mailvelope' => ['name' => rcube::Q($rcmail->gettext('mailvelopeoptions'))],
'advanced' => ['name' => rcube::Q($rcmail->gettext('advancedoptions'))],
];
if (!isset($no_override['mailvelope_main_keyring'])) {
if (!$current) {
continue 2;
}
$field_id = 'rcmfd_mailvelope_main_keyring';
$input = new html_checkbox(['name' => '_mailvelope_main_keyring', 'id' => $field_id, 'value' => 1]);
$blocks['mailvelope']['options']['mailvelope_status'] = [
'content' => html::div(
['style' => 'display:none', 'class' => 'boxwarning', 'id' => 'mailvelope-warning'],
str_replace(
'Mailvelope', '<a href="https://www.mailvelope.com" target="_blank">Mailvelope</a>',
rcube::Q($rcmail->gettext('mailvelopenotfound'))
)
. html::script([], "if (!parent.mailvelope) \$('#mailvelope-warning').show()")
)
];
$blocks['mailvelope']['options']['mailvelope_main_keyring'] = [
'title' => html::label($field_id, rcube::Q($rcmail->gettext('mailvelopemainkeyring'))),
'content' => $input->show(!empty($config['mailvelope_main_keyring']) ? 1 : 0),
];
}
break;
}
$found = false;
$data = $rcmail->plugins->exec_hook('preferences_list', [
'section' => $sect['id'],
'blocks' => $blocks,
'current' => $current
]);
$advanced_prefs = (array) $rcmail->config->get('advanced_prefs');
// create output
foreach ($data['blocks'] as $key => $block) {
if (!empty($block['content']) || !empty($block['options'])) {
$found = true;
}
// move some options to the 'advanced' block as configured by admin
if ($key != 'advanced') {
foreach ($advanced_prefs as $opt) {
if ($block['options'][$opt]) {
$data['blocks']['advanced']['options'][$opt] = $block['options'][$opt];
unset($data['blocks'][$key]['options'][$opt]);
}
}
}
}
// move 'advanced' block to the end of the list
if (!empty($data['blocks']['advanced'])) {
$adv = $data['blocks']['advanced'];
unset($data['blocks']['advanced']);
$data['blocks']['advanced'] = $adv;
}
if (!$found) {
unset($sections[$idx]);
}
else {
$sections[$idx]['blocks'] = $data['blocks'];
}
// allow plugins to add a header to each section
$data = $rcmail->plugins->exec_hook('preferences_section_header',
['section' => $sect['id'], 'header' => '', 'current' => $current]);
if (!empty($data['header'])) {
$sections[$idx]['header'] = $data['header'];
}
}
return [$sections, $plugin['cols']];
}
-
+ /**
+ * Get list of installed skins
+ *
+ * @return array List of skin names
+ */
public static function get_skins()
{
$rcmail = rcmail::get_instance();
$path = RCUBE_INSTALL_PATH . 'skins';
$skins = [];
$dir = opendir($path);
$limit = (array) $rcmail->config->get('skins_allowed');
if (!$dir) {
return false;
}
while (($file = readdir($dir)) !== false) {
$filename = $path . '/' . $file;
if ($file[0] != '.'
&& (empty($limit) || in_array($file, $limit))
&& is_dir($filename) && is_readable($filename)
) {
$skins[] = $file;
}
}
closedir($dir);
return $skins;
}
/**
* Render the list of settings sections (AKA tabs)
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML content
*/
public static function settings_tabs($attrib)
{
$rcmail = rcmail::get_instance();
// add default attributes
$attrib += ['tagname' => 'span', 'idprefix' => 'settingstab', 'selclass' => 'selected'];
$default_actions = [
['action' => 'preferences', 'type' => 'link', 'label' => 'preferences', 'title' => 'editpreferences'],
['action' => 'folders', 'type' => 'link', 'label' => 'folders', 'title' => 'managefolders'],
['action' => 'identities', 'type' => 'link', 'label' => 'identities', 'title' => 'manageidentities'],
['action' => 'responses', 'type' => 'link', 'label' => 'responses', 'title' => 'manageresponses'],
];
$disabled_actions = (array) $rcmail->config->get('disabled_actions');
// get all identites from DB and define list of cols to be displayed
$plugin = $rcmail->plugins->exec_hook('settings_actions', [
'actions' => $default_actions,
'attrib' => $attrib,
]);
$selected = !empty($rcmail->action) ? $rcmail->action : 'preferences';
$attrib = $plugin['attrib'];
$tagname = $attrib['tagname'];
$tabs = [];
foreach ($plugin['actions'] as $action) {
- if (!$action['command'] && $action['action']) {
+ if (empty($action['command']) && !empty($action['action'])) {
$action['prop'] = $action['action'];
$action['command'] = 'show';
}
- else if ($action['command'] != 'show') {
+ else if (empty($action['command']) || $action['command'] != 'show') {
// Backwards compatibility, show command added in 1.4
- $action['prop'] = $action['command'];
+ $action['prop'] = !empty($action['command']) ? $action['command'] : null;
$action['command'] = 'show';
}
- $cmd = $action['prop'] ?: $action['action'];
- $id = $action['id'] ?: $cmd;
+ $cmd = !empty($action['prop']) ? $action['prop'] : $action['action'];
+ $id = !empty($action['id']) ? $action['id'] : $cmd;
if (in_array('settings.' . $cmd, $disabled_actions)) {
continue;
}
if (empty($action['href'])) {
$action['href'] = $rcmail->url(['_action' => $cmd]);
}
$button = $rcmail->output->button($action + ['type' => 'link']);
$attr = $attrib;
if (!empty($id)) {
$attr['id'] = preg_replace('/[^a-z0-9]/i', '', $attrib['idprefix'] . $id);
}
- $classnames = [$attrib['class']];
+ $classnames = [];
+ if (!empty($attrib['class'])) {
+ $classnames[] = $attrib['class'];
+ }
if (!empty($action['class'])) {
$classnames[] = $action['class'];
}
else if (!empty($cmd)) {
$classnames[] = $cmd;
}
- if ($cmd == $selected) {
+ if ($cmd == $selected && !empty($attrib['selclass'])) {
$classnames[] = $attrib['selclass'];
}
$attr['class'] = join(' ', $classnames);
$tabs[] = html::tag($tagname, $attr, $button, html::$common_attrib);
}
return join('', $tabs);
}
/**
* Localize timezone identifiers
+ *
+ * @param string $tz Timezone name
+ *
+ * @return string Localized timezone name
*/
public static function timezone_label($tz)
{
static $labels;
if ($labels === null) {
$labels = [];
$lang = $_SESSION['language'];
if ($lang && $lang != 'en_US') {
if (file_exists(RCUBE_LOCALIZATION_DIR . "$lang/timezones.inc")) {
include RCUBE_LOCALIZATION_DIR . "$lang/timezones.inc";
}
}
}
if (empty($labels)) {
return str_replace('_', ' ', $tz);
}
$tokens = explode('/', $tz);
$key = 'tz';
foreach ($tokens as $i => $token) {
$idx = strtolower($token);
$token = str_replace('_', ' ', $token);
$key .= ":$idx";
$tokens[$i] = $labels[$key] ?: $token;
}
return implode('/', $tokens);
}
/**
* Returns timezone offset in standard time
*/
public static function timezone_standard_time_data($tzname)
{
try {
$tz = new DateTimeZone($tzname);
$date = new DateTime(null, $tz);
$count = 12;
// Move back for a month (up to 12 times) until non-DST date is found
while ($count > 0 && $date->format('I')) {
$date->sub(new DateInterval('P1M'));
$count--;
}
$offset = $date->format('Z') + 45000;
$sortkey = sprintf('%06d.%s', $offset, $tzname);
return [
'key' => $sortkey,
'offset' => $date->format('P'),
];
}
catch (Exception $e) {
// ignore
}
}
}
diff --git a/program/include/rcmail_action.php b/program/include/rcmail_action.php
index 4149ccc14..826ef1dbf 100644
--- a/program/include/rcmail_action.php
+++ b/program/include/rcmail_action.php
@@ -1,1459 +1,1474 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) 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: |
| An abstract for HTTP request handlers with some helpers. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* An abstract for HTTP request handlers with some helpers.
*
* @package Webmail
*/
abstract class rcmail_action
{
const MODE_AJAX = 1;
const MODE_HTTP = 2;
/**
* Mode of operation supported by the action. Use MODE_* constants.
* By default all modes are allowed.
*
* @var int
*/
protected static $mode;
+ /**
+ * A name of a initialized common form
+ *
+ * @var string
+ */
+ protected static $edit_form;
+
/**
* Deprecated action aliases.
*
* @todo Get rid of these (but it will be a big BC break)
* @var array
*/
public static $aliases = [];
/**
* Request handler. The only abstract method.
*
* @param array $args Arguments from the previous step(s)
*/
abstract public function run($args = []);
/**
* Request sanity checks, e.g. supported request mode
*
* @return bool
*/
public function checks()
{
$rcmail = rcmail::get_instance();
if (static::$mode) {
if (!(static::$mode & self::MODE_HTTP) && empty($rcmail->output->ajax_call)) {
return false;
}
if (!(static::$mode & self::MODE_AJAX) && !empty($rcmail->output->ajax_call)) {
return false;
}
}
return true;
}
/**
* Set environment variables for specified config boolean options
*
* @param array $options List of configuration option names
*/
public static function set_env_config($options)
{
$rcmail = rcmail::get_instance();
foreach ((array) $options as $option) {
if ($rcmail->config->get($option)) {
$rcmail->output->set_env($option, true);
}
}
}
/**
* Create a HTML table based on the given data
*
* @param array $attrib Named table attributes
* @param mixed $table_data Table row data. Either a two-dimensional array
* or a valid SQL result set
* @param array $show_cols List of cols to show
* @param string $id_col Name of the identifier col
*
* @return string HTML table code
*/
public static function table_output($attrib, $table_data, $show_cols, $id_col)
{
$rcmail = rcmail::get_instance();
$table = new html_table($attrib);
// add table header
- if (!$attrib['noheader']) {
+ if (empty($attrib['noheader'])) {
foreach ($show_cols as $col) {
$table->add_header($col, rcube::Q($rcmail->gettext($col)));
}
}
if (!is_array($table_data)) {
$db = $rcmail->get_dbh();
while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
$table->add_row(array('id' => 'rcmrow' . rcube_utils::html_identifier($sql_arr[$id_col])));
// format each col
foreach ($show_cols as $col) {
$table->add($col, rcube::Q($sql_arr[$col]));
}
}
}
else {
foreach ($table_data as $row_data) {
$class = !empty($row_data['class']) ? $row_data['class'] : null;
if (!empty($attrib['rowclass'])) {
$class = trim($class . ' ' . $attrib['rowclass']);
}
$rowid = 'rcmrow' . rcube_utils::html_identifier($row_data[$id_col]);
$table->add_row(['id' => $rowid, 'class' => $class]);
// format each col
foreach ($show_cols as $col) {
$val = is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col];
$table->add($col, empty($attrib['ishtml']) ? rcube::Q($val) : $val);
}
}
}
return $table->show($attrib);
}
/**
* Return HTML for quota indicator object
*
* @param array $attrib Named parameters
*
* @return string HTML code for the quota indicator object
*/
public static function quota_display($attrib)
{
$rcmail = rcmail::get_instance();
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmquotadisplay';
}
$_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
$quota = self::quota_content($attrib);
$rcmail->output->add_gui_object('quotadisplay', $attrib['id']);
$rcmail->output->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
return html::span($attrib, '&nbsp;');
}
/**
* Return (parsed) quota information
*
* @param array $attrib Named parameters
* @param array $folder Current folder
*
* @return array Quota information
*/
public static function quota_content($attrib = null, $folder = null)
{
$rcmail = rcmail::get_instance();
$quota = $rcmail->storage->get_quota($folder);
$quota = $rcmail->plugins->exec_hook('quota', $quota);
$quota_result = (array) $quota;
$quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
$quota_result['folder'] = $folder !== null && $folder !== '' ? $folder : 'INBOX';
if (!empty($quota['total']) && $quota['total'] > 0) {
if (!isset($quota['percent'])) {
$quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
}
$title = $rcmail->gettext('quota') . ': ' . sprintf('%s / %s (%.0f%%)',
self::show_bytes($quota['used'] * 1024),
self::show_bytes($quota['total'] * 1024),
$quota_result['percent']
);
$quota_result['title'] = $title;
if (!empty($attrib['width'])) {
$quota_result['width'] = $attrib['width'];
}
if (!empty($attrib['height'])) {
$quota_result['height'] = $attrib['height'];
}
// build a table of quota types/roots info
if (($root_cnt = count($quota_result['all'])) > 1 || count($quota_result['all'][key($quota_result['all'])]) > 1) {
$table = new html_table(array('cols' => 3, 'class' => 'quota-info'));
$table->add_header(null, rcube::Q($rcmail->gettext('quotatype')));
$table->add_header(null, rcube::Q($rcmail->gettext('quotatotal')));
$table->add_header(null, rcube::Q($rcmail->gettext('quotaused')));
foreach ($quota_result['all'] as $root => $data) {
if ($root_cnt > 1 && $root) {
$table->add(['colspan' => 3, 'class' => 'root'], rcube::Q($root));
}
if ($storage = $data['storage']) {
$percent = min(100, round(($storage['used']/max(1,$storage['total']))*100));
$table->add('name', rcube::Q($rcmail->gettext('quotastorage')));
$table->add(null, self::show_bytes($storage['total'] * 1024));
$table->add(null, sprintf('%s (%.0f%%)', self::show_bytes($storage['used'] * 1024), $percent));
}
if ($message = $data['message']) {
$percent = min(100, round(($message['used']/max(1,$message['total']))*100));
$table->add('name', rcube::Q($rcmail->gettext('quotamessage')));
$table->add(null, intval($message['total']));
$table->add(null, sprintf('%d (%.0f%%)', $message['used'], $percent));
}
}
$quota_result['table'] = $table->show();
}
}
else {
$unlimited = $rcmail->config->get('quota_zero_as_unlimited');
$quota_result['title'] = $rcmail->gettext($unlimited ? 'unlimited' : 'unknown');
$quota_result['percent'] = 0;
}
// cleanup
unset($quota_result['abort']);
if (empty($quota_result['table'])) {
unset($quota_result['all']);
}
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
* @param array $params Additional parameters (type, prefix)
*/
public static function display_server_error($fallback = null, $fallback_args = null, $suffix = '', $params = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$err_code = $storage->get_error_code();
$res_code = $storage->get_response_code();
$args = [];
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 = 'erroroverquota';
}
else if ($err_code && ($err_str = $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" / "quotum would exceed" / "Over quota"
else if (stripos($err_str, 'quot') !== false && preg_match('/exceed|over/i', $err_str)) {
$error = 'erroroverquota';
}
else {
$error = 'servererrormsg';
$args = array('msg' => rcube::Q($err_str));
}
}
else if ($err_code < 0) {
$error = 'storageerror';
}
else if ($fallback) {
$error = $fallback;
$args = $fallback_args;
$params['prefix'] = false;
}
if (!empty($error)) {
if ($suffix && $rcmail->text_exists($error . $suffix)) {
$error .= $suffix;
}
$msg = $rcmail->gettext(array('name' => $error, 'vars' => $args));
if (!empty($params['prefix']) && $fallback) {
$msg = $rcmail->gettext(array('name' => $fallback, 'vars' => $fallback_args)) . ' ' . $msg;
}
$rcmail->output->show_message($msg, !empty($params['type']) ? $params['type'] : 'error');
}
}
/**
* Displays an error message on storage fatal errors
*/
public static function storage_fatal_error()
{
$rcmail = rcmail::get_instance();
$err_code = $rcmail->storage->get_error_code();
switch ($err_code) {
// Not all are really fatal, but these should catch
// connection/authentication errors the best we can
case rcube_imap_generic::ERROR_NO:
case rcube_imap_generic::ERROR_BAD:
case rcube_imap_generic::ERROR_BYE:
self::display_server_error();
}
}
/**
* Output HTML editor scripts
*
* @param string $mode Editor mode
*/
public static function html_editor($mode = '')
{
$rcmail = rcmail::get_instance();
$spellcheck = intval($rcmail->config->get('enable_spellcheck'));
$spelldict = intval($rcmail->config->get('spellcheck_dictionary'));
$disabled_plugins = [];
$disabled_buttons = [];
$extra_plugins = [];
$extra_buttons = [];
if (!$spellcheck) {
$disabled_plugins[] = 'spellchecker';
}
$hook = $rcmail->plugins->exec_hook('html_editor', [
'mode' => $mode,
'disabled_plugins' => $disabled_plugins,
'disabled_buttons' => $disabled_buttons,
'extra_plugins' => $extra_plugins,
'extra_buttons' => $extra_buttons,
]);
if (!empty($hook['abort'])) {
return;
}
$lang_codes = [$_SESSION['language']];
$assets_dir = $rcmail->config->get('assets_dir') ?: INSTALL_PATH;
$skin_path = $rcmail->output->get_skin_path();
if ($pos = strpos($_SESSION['language'], '_')) {
$lang_codes[] = substr($_SESSION['language'], 0, $pos);
}
foreach ($lang_codes as $code) {
if (file_exists("$assets_dir/program/js/tinymce/langs/$code.js")) {
$lang = $code;
break;
}
}
if (empty($lang)) {
$lang = 'en';
}
$config = [
'mode' => $mode,
'lang' => $lang,
'skin_path' => $skin_path,
'spellcheck' => $spellcheck, // deprecated
'spelldict' => $spelldict,
'content_css' => 'program/resources/tinymce/content.css',
'disabled_plugins' => $hook['disabled_plugins'],
'disabled_buttons' => $hook['disabled_buttons'],
'extra_plugins' => $hook['extra_plugins'],
'extra_buttons' => $hook['extra_buttons'],
];
if ($path = $rcmail->config->get('editor_css_location')) {
if ($path = $rcmail->find_asset($skin_path . $path)) {
$config['content_css'] = $path;
}
}
$font_family = $rcmail->output->get_env('default_font');
$font_size = $rcmail->output->get_env('default_font_size');
$style = [];
if ($font_family) {
$style[] = "font-family: $font_family;";
}
if ($font_size) {
$style[] = "font-size: $font_size;";
}
if (!empty($style)) {
$config['content_style'] = "body {" . implode(' ', $style) . "}";
}
$rcmail->output->set_env('editor_config', $config);
$rcmail->output->add_label('selectimage', 'addimage', 'selectmedia', 'addmedia', 'close');
if ($path = $rcmail->config->get('media_browser_css_location', 'program/resources/tinymce/browser.css')) {
if ($path != 'none' && ($path = $rcmail->find_asset($path))) {
$rcmail->output->include_css($path);
}
}
$rcmail->output->include_script('tinymce/tinymce.min.js');
$rcmail->output->include_script('editor.js');
}
/**
* File upload progress handler.
*
* @deprecated We're using HTML5 upload progress
*/
public static function upload_progress()
{
// NOOP
rcmail::get_instance()->output->send();
}
/**
* Initializes file uploading interface.
*
* @param int $max_size Optional maximum file size in bytes
*
* @return string Human-readable file size limit
*/
public static function upload_init($max_size = null)
{
$rcmail = rcmail::get_instance();
// find max filesize value
$max_filesize = rcube_utils::max_upload_size();
if ($max_size && $max_size < $max_filesize) {
$max_filesize = $max_size;
}
$max_filesize_txt = self::show_bytes($max_filesize);
$rcmail->output->set_env('max_filesize', $max_filesize);
$rcmail->output->set_env('filesizeerror', $rcmail->gettext(array(
'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize_txt))));
if ($max_filecount = ini_get('max_file_uploads')) {
$rcmail->output->set_env('max_filecount', $max_filecount);
$rcmail->output->set_env('filecounterror', $rcmail->gettext(array(
'name' => 'filecounterror', 'vars' => array('count' => $max_filecount))));
}
$rcmail->output->add_label('uploadprogress', 'GB', 'MB', 'KB', 'B');
return $max_filesize_txt;
}
/**
* Upload form object
*
* @param array $attrib Object attributes
* @param string $name Form object name
* @param string $action Form action name
* @param array $input_attr File input attributes
* @param int $max_size Maximum upload size
*
* @return string HTML output
*/
public static function upload_form($attrib, $name, $action, $input_attr = [], $max_size = null)
{
$rcmail = rcmail::get_instance();
// Get filesize, enable upload progress bar
$max_filesize = self::upload_init($max_size);
$hint = html::div('hint', $rcmail->gettext(['name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]));
if (!empty($attrib['mode']) && $attrib['mode'] == 'hint') {
return $hint;
}
// set defaults
$attrib += ['id' => 'rcmUploadbox', 'buttons' => 'yes'];
$event = rcmail_output::JS_OBJECT_NAME . ".command('$action', this.form)";
$form_id = $attrib['id'] . 'Frm';
// Default attributes of file input and form
$input_attr += [
'id' => $attrib['id'] . 'Input',
'type' => 'file',
'name' => '_attachments[]',
'class' => 'form-control',
];
$form_attr = [
'id' => $form_id,
'name' => $name,
'method' => 'post',
'enctype' => 'multipart/form-data'
];
if (!empty($attrib['mode']) && $attrib['mode'] == 'smart') {
unset($attrib['buttons']);
$form_attr['class'] = 'smart-upload';
$input_attr = array_merge($input_attr, [
// #5854: Chrome does not execute onchange when selecting the same file.
// To fix this we reset the input using null value.
'onchange' => "$event; this.value=null",
'class' => 'smart-upload',
'tabindex' => '-1',
]);
}
$input = new html_inputfield($input_attr);
$content = $attrib['prefix'] . $input->show();
if (empty($attrib['mode']) || $attrib['mode'] != 'smart') {
$content = html::div(null, $content . $hint);
}
if (self::get_bool_attr($attrib, 'buttons')) {
$button = new html_inputfield(['type' => 'button']);
$content .= html::div('buttons',
$button->show($rcmail->gettext('close'), ['class' => 'button', 'onclick' => "$('#{$attrib['id']}').hide()"])
. ' ' .
$button->show($rcmail->gettext('upload'), ['class' => 'button mainaction', 'onclick' => $event])
);
}
$rcmail->output->add_gui_object($name, $form_id);
return html::div($attrib, $rcmail->output->form_tag($form_attr, $content));
}
/**
* Common file upload error handler
*
* @param int $php_error PHP error from $_FILES
* @param array $attachment Attachment data from attachment_upload hook
* @param string $add_error Additional error label (highest prio)
*/
public static function upload_error($php_error, $attachment = null, $add_error = null)
{
$rcmail = rcmail::get_instance();
if ($add_error) {
$msg = $rcmail->gettext($add_error);
}
else if ($attachment && !empty($attachment['error'])) {
$msg = $attachment['error'];
}
else if ($php_error == UPLOAD_ERR_INI_SIZE || $php_error == UPLOAD_ERR_FORM_SIZE) {
$post_size = self::show_bytes(rcube_utils::max_upload_size());
$msg = $rcmail->gettext(['name' => 'filesizeerror', 'vars' => ['size' => $post_size]]);
}
else {
$msg = $rcmail->gettext('fileuploaderror');
}
$rcmail->output->command('display_message', $msg, 'error');
}
/**
* Common POST file upload error handler
*
* @return bool True if it was a POST request, False otherwise
*/
public static function upload_failure()
{
if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] != 'POST') {
return false;
}
$rcmail = rcmail::get_instance();
// if filesize exceeds post_max_size then $_FILES array is empty,
// show filesizeerror instead of fileuploaderror
if ($maxsize = ini_get('post_max_size')) {
$msg = $rcmail->gettext([
'name' => 'filesizeerror',
'vars' => ['size' => self::show_bytes(parse_bytes($maxsize))]
]);
}
else {
$msg = $rcmail->gettext('fileuploaderror');
}
$rcmail->output->command('display_message', $msg, 'error');
return true;
}
/**
* Outputs uploaded file content (with image thumbnails support
*
* @param array $file Upload file data
*/
public static function display_uploaded_file($file)
{
if (empty($file)) {
return;
}
$rcmail = rcmail::get_instance();
$file = $rcmail->plugins->exec_hook('attachment_display', $file);
if ($file['status']) {
if (empty($file['size'])) {
$file['size'] = $file['data'] ? strlen($file['data']) : @filesize($file['path']);
}
// generate image thumbnail for file browser in HTML editor
if (!empty($_GET['_thumbnail'])) {
$thumbnail_size = 80;
$mimetype = $file['mimetype'];
$file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size'];
$thumb_name = 'thumb' . md5($file_ident . ':' . $rcmail->user->ID . ':' . $thumbnail_size);
$cache_file = rcube_utils::temp_filename($thumb_name, false, false);
// render thumbnail image if not done yet
if (!is_file($cache_file)) {
if (!$file['path']) {
$orig_name = $filename = $cache_file . '.tmp';
file_put_contents($orig_name, $file['data']);
}
else {
$filename = $file['path'];
}
$image = new rcube_image($filename);
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
$mimetype = 'image/' . $imgtype;
if (!empty($orig_name)) {
unlink($orig_name);
}
}
}
if (is_file($cache_file)) {
// cache for 1h
$rcmail->output->future_expire_header(3600);
header('Content-Type: ' . $mimetype);
header('Content-Length: ' . filesize($cache_file));
readfile($cache_file);
exit;
}
}
header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);
if ($file['data']) {
echo $file['data'];
}
else if ($file['path']) {
readfile($file['path']);
}
}
}
/**
* Initializes client-side autocompletion.
*/
public static function autocomplete_init()
{
static $init;
if ($init) {
return;
}
$init = 1;
$rcmail = rcmail::get_instance();
if (($threads = (int) $rcmail->config->get('autocomplete_threads')) > 0) {
$book_types = (array) $rcmail->config->get('autocomplete_addressbooks', 'sql');
if (count($book_types) > 1) {
$rcmail->output->set_env('autocomplete_threads', $threads);
$rcmail->output->set_env('autocomplete_sources', $book_types);
}
}
$rcmail->output->set_env('autocomplete_max', (int) $rcmail->config->get('autocomplete_max', 15));
$rcmail->output->set_env('autocomplete_min_length', $rcmail->config->get('autocomplete_min_length'));
$rcmail->output->add_label('autocompletechars', 'autocompletemore');
}
/**
* Returns supported font-family specifications
*
* @param string $font Font name
*
* @return string|array Font-family specification array or string (if $font is used)
*/
public static function font_defs($font = null)
{
$fonts = [
'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 !empty($fonts[$font]) ? $fonts[$font] : null;
}
return $fonts;
}
/**
* Create a human readable string for a number of bytes
*
* @param int $bytes Number of bytes
* @param string &$unit Size unit
*
* @return string Byte string
*/
public static function show_bytes($bytes, &$unit = null)
{
$rcmail = rcmail::get_instance();
// Plugins may want to display different units
$plugin = $rcmail->plugins->exec_hook('show_bytes', ['bytes' => $bytes, 'unit' => null]);
$unit = $plugin['unit'];
if (isset($plugin['result'])) {
return $plugin['result'];
}
if ($bytes >= 1073741824) {
$unit = 'GB';
$gb = $bytes/1073741824;
$str = sprintf($gb >= 10 ? "%d " : "%.1f ", $gb) . $rcmail->gettext($unit);
}
else if ($bytes >= 1048576) {
$unit = 'MB';
$mb = $bytes/1048576;
$str = sprintf($mb >= 10 ? "%d " : "%.1f ", $mb) . $rcmail->gettext($unit);
}
else if ($bytes >= 1024) {
$unit = 'KB';
$str = sprintf("%d ", round($bytes/1024)) . $rcmail->gettext($unit);
}
else {
$unit = 'B';
$str = sprintf('%d ', $bytes) . $rcmail->gettext($unit);
}
return $str;
}
/**
* Returns real size (calculated) of the message part
*
* @param rcube_message_part $part Message part
*
* @return string Part size (and unit)
*/
public static function message_part_size($part)
{
if (isset($part->d_parameters['size'])) {
$size = self::show_bytes((int) $part->d_parameters['size']);
}
else {
$size = $part->size;
if ($size === 0) {
$part->exact_size = true;
}
if ($part->encoding == 'base64') {
$size = $size / 1.33;
}
$size = self::show_bytes($size);
}
if (!$part->exact_size) {
$size = '~' . $size;
}
return $size;
}
/**
* Returns message UID(s) and IMAP folder(s) from GET/POST data
*
* @param string $uids UID value to decode
* @param string $mbox Default mailbox value (if not encoded in UIDs)
* @param bool $is_multifolder Will be set to True if multi-folder request
* @param int $mode Request mode. Default: rcube_utils::INPUT_GPC.
*
* @return array List of message UIDs per folder
*/
public static function get_uids($uids = null, $mbox = null, &$is_multifolder = false, $mode = null)
{
// message UID (or comma-separated list of IDs) is provided in
// the form of <ID>-<MBOX>[,<ID>-<MBOX>]*
$_uid = $uids ?: rcube_utils::get_input_value('_uid', $mode ?: rcube_utils::INPUT_GPC);
$_mbox = $mbox ?: (string) rcube_utils::get_input_value('_mbox', $mode ?: rcube_utils::INPUT_GPC);
// already a hash array
if (is_array($_uid) && !isset($_uid[0])) {
return $_uid;
}
$result = [];
// special case: *
if ($_uid == '*' && is_object($_SESSION['search'][1]) && $_SESSION['search'][1]->multi) {
$is_multifolder = true;
// extract the full list of UIDs per folder from the search set
foreach ($_SESSION['search'][1]->sets as $subset) {
$mbox = $subset->get_parameters('MAILBOX');
$result[$mbox] = $subset->get();
}
}
else {
if (is_string($_uid)) {
$_uid = explode(',', $_uid);
}
// create a per-folder UIDs array
foreach ((array) $_uid as $uid) {
$tokens = explode('-', $uid, 2);
$uid = $tokens[0];
if (!isset($tokens[1]) || !strlen($tokens[1])) {
$mbox = $_mbox;
}
else {
$mbox = $tokens[1];
$is_multifolder = true;
}
if ($uid == '*') {
$result[$mbox] = $uid;
}
else if (preg_match('/^[0-9:.]+$/', $uid)) {
$result[$mbox][] = $uid;
}
}
}
return $result;
}
/**
* Get resource file content (with assets_dir support)
*
* @param string $name File name
*
* @return string File content
*/
public static function get_resource_content($name)
{
if (!strpos($name, '/')) {
$name = "program/resources/$name";
}
$assets_dir = rcmail::get_instance()->config->get('assets_dir');
if ($assets_dir) {
$path = slashify($assets_dir) . $name;
if (@file_exists($path)) {
$name = $path;
}
}
return file_get_contents($name, false);
}
- public static function get_form_tags($attrib, $action, $id = null, $hidden = null)
+ /**
+ * Prepare a common edit form.
+ *
+ * @param array $attrib Form attributes
+ * @param string $action Action name
+ * @param string $id An extra index for the form key
+ * @param array $hidden Additional hidden fields
+ *
+ * @return array Start and end tags, Empty if the farm was initialized before
+ */
+ public static function get_form_tags($attrib, $action, $id = null, $hidden = [])
{
- static $edit_form;
-
$rcmail = rcmail::get_instance();
$form_start = $form_end = '';
- if (empty($edit_form)) {
+ if (empty(self::$edit_form)) {
$request_key = $action . (isset($id) ? '.'.$id : '');
$form_start = $rcmail->output->request_form([
'name' => 'form',
'method' => 'post',
'task' => $rcmail->task,
'action' => $action,
'request' => $request_key,
'noclose' => true
] + $attrib
);
- if (is_array($hidden)) {
+ if (!empty($hidden) && is_array($hidden)) {
$hiddenfields = new html_hiddenfield($hidden);
$form_start .= $hiddenfields->show();
}
$form_end = empty($attrib['form']) ? '</form>' : '';
- $edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form';
+ self::$edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form';
- $rcmail->output->add_gui_object('editform', $edit_form);
+ $rcmail->output->add_gui_object('editform', self::$edit_form);
}
return array($form_start, $form_end);
}
/**
* Return folders list in HTML
*
* @param array $attrib Named parameters
*
* @return string HTML code for the gui object
*/
public static function folder_list($attrib)
{
static $a_mailboxes;
$attrib += ['maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)'];
$type = !empty($attrib['type']) ? $attrib['type'] : 'ul';
unset($attrib['type']);
if ($type == 'ul' && empty($attrib['id'])) {
$attrib['id'] = 'rcmboxlist';
}
if (empty($attrib['folder_name'])) {
$attrib['folder_name'] = '*';
}
// get current folder
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox_name = $storage->get_folder();
$delimiter = $storage->get_hierarchy_delimiter();
// build the folders tree
if (empty($a_mailboxes)) {
// get mailbox list
$a_mailboxes = array();
$a_folders = $storage->list_folders_subscribed(
'',
$attrib['folder_name'],
isset($attrib['folder_filter']) ? $attrib['folder_filter'] : null
);
foreach ($a_folders as $folder) {
self::build_folder_tree($a_mailboxes, $folder, $delimiter);
}
}
// allow plugins to alter the folder tree or to localize folder names
$hook = $rcmail->plugins->exec_hook('render_mailboxlist', [
'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 (!empty($attrib['noselection'])) {
$select->add(html::quote($rcmail->gettext($attrib['noselection'])), '');
}
$maxlength = isset($attrib['maxlength']) ? $attrib['maxlength'] : null;
$realnames = isset($attrib['realnames']) ? $attrib['realnames'] : null;
$default = isset($attrib['default']) ? $attrib['default'] : null;
self::render_folder_tree_select($a_mailboxes, $mbox_name, $maxlength, $select, $realnames);
$out = $select->show($default);
}
else {
$out = '';
$js_mailboxlist = [];
$tree = self::render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib);
if ($type != 'js') {
$out = html::tag('ul', $attrib, $tree, html::$common_attrib);
$rcmail->output->include_script('treelist.js');
$rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
$rcmail->output->set_env('unreadwrap', isset($attrib['unreadwrap']) ? $attrib['unreadwrap'] : false);
$rcmail->output->set_env('collapsed_folders', (string) $rcmail->config->get('collapsed_folders'));
}
$rcmail->output->set_env('mailboxes', $js_mailboxlist);
// we can't use object keys in javascript because they are unordered
// we need sorted folders list for folder-selector widget
$rcmail->output->set_env('mailboxes_list', array_keys($js_mailboxlist));
}
// add some labels to client
$rcmail->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
return $out;
}
/**
* Return folders list as html_select object
*
* @param array $p Named parameters
*
* @return html_select HTML drop-down object
*/
public static function folder_selector($p = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$realnames = $rcmail->config->get('show_real_foldernames');
$p += ['maxlength' => 100, 'realnames' => $realnames, 'is_escaped' => true];
$a_mailboxes = [];
if (empty($p['folder_name'])) {
$p['folder_name'] = '*';
}
$f_filter = isset($p['folder_filter']) ? $p['folder_filter'] : null;
$f_rights = isset($p['folder_rights']) ? $p['folder_rights'] : null;
if ($p['unsubscribed']) {
$list = $storage->list_folders('', $p['folder_name'], $f_filter, $f_rights);
}
else {
$list = $storage->list_folders_subscribed('', $p['folder_name'], $f_filter, $f_rights);
}
$delimiter = $storage->get_hierarchy_delimiter();
if (!empty($p['exceptions'])) {
$list = array_diff($list, (array) $p['exceptions']);
}
if (!empty($p['additional'])) {
foreach ($p['additional'] as $add_folder) {
$add_items = explode($delimiter, $add_folder);
$folder = '';
while (count($add_items)) {
$folder .= array_shift($add_items);
// @TODO: sorting
if (!in_array($folder, $list)) {
$list[] = $folder;
}
$folder .= $delimiter;
}
}
}
foreach ($list as $folder) {
self::build_folder_tree($a_mailboxes, $folder, $delimiter);
}
// allow plugins to alter the folder tree or to localize folder names
$hook = $rcmail->plugins->exec_hook('render_folder_selector', [
'list' => $a_mailboxes,
'delimiter' => $delimiter,
'attribs' => $p,
]);
$a_mailboxes = $hook['list'];
$p = $hook['attribs'];
$select = new html_select($p);
if ($p['noselection']) {
$select->add(html::quote($p['noselection']), '');
}
self::render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
return $select;
}
/**
* Create a hierarchical array of the mailbox list
*/
protected static function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
// Handle namespace prefix
$prefix = '';
if (!$path) {
$n_folder = $folder;
$folder = $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] = [
'id' => $path,
'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
'virtual' => $virtual,
'folders' => []
];
}
else {
$arrFolders[$currentFolder]['virtual'] = $virtual;
}
if (strlen($subFolders)) {
self::build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
}
/**
* Return html for a structured list &lt;ul&gt; for the mailbox tree
*/
protected static function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $storage->get_cache('messagecount');
$collapsed = $rcmail->config->get('collapsed_folders');
$realnames = $rcmail->config->get('show_real_foldernames');
$out = '';
foreach ($arrFolders as $folder) {
$title = null;
$folder_class = self::folder_classname($folder['id']);
$is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
$unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
if ($folder_class && !$realnames && $rcmail->text_exists($folder_class)) {
$foldername = $rcmail->gettext($folder_class);
}
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength > 1) {
$fname = abbreviate_string($foldername, $maxlength);
if ($fname != $foldername) {
$title = $foldername;
}
$foldername = $fname;
}
}
// make folder name safe for ids and class names
$folder_id = rcube_utils::html_identifier($folder['id'], true);
$classes = ['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 = rcube::JQ($folder['id']);
$html_name = rcube::Q($foldername) . ($unread ? html::span('unreadcount skip-content', sprintf($attrib['unreadwrap'], $unread)) : '');
$link_attrib = $folder['virtual'] ? [] : [
'href' => $rcmail->url(['_mbox' => $folder['id']]),
'onclick' => sprintf("return %s.command('list','%s',this,event)", rcmail_output::JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
];
$out .= html::tag('li', [
'id' => "rcmli" . $folder_id,
'class' => implode(' ', $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']] = [
'id' => $folder['id'],
'name' => $foldername,
'virtual' => $folder['virtual'],
];
if (!empty($folder_class)) {
$jslist[$folder['id']]['class'] = $folder_class;
}
if (!empty($folder['folders'])) {
$out .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
self::render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
}
$out .= "</li>\n";
}
return $out;
}
/**
* Return html for a flat list <select> for the mailbox tree
*/
protected static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
{
$out = '';
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
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 = $storage->folder_attributes($folder['id']);
if ($attrs && in_array_nocase('\\Noinferiors', $attrs)) {
continue;
}
}
if (!$realnames && ($folder_class = self::folder_classname($folder['id'])) && $rcmail->text_exists($folder_class)) {
$foldername = $rcmail->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 .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
$select, $realnames, $nestLevel+1, $opts);
}
}
return $out;
}
/**
* Returns class name for the given folder if it is a special folder
* (including shared/other users namespace roots).
*
* @param string $folder_id IMAP Folder name
*
* @return string|null CSS class name
*/
public static function folder_classname($folder_id)
{
static $classes;
if ($classes === null) {
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$classes = ['INBOX' => 'inbox'];
// for these mailboxes we have css classes
foreach (['sent', 'drafts', 'trash', 'junk'] as $type) {
if (($mbox = $rcmail->config->get($type . '_mbox')) && !isset($classes[$mbox])) {
$classes[$mbox] = $type;
}
}
// add classes for shared/other user namespace roots
foreach (['other', 'shared'] as $ns_name) {
if ($ns = $storage->get_namespace($ns_name)) {
foreach ($ns as $root) {
$root = substr($root[0], 0, -1);
if (strlen($root) && !isset($classes[$root])) {
$classes[$root] = "ns-$ns_name";
}
}
}
}
}
return !empty($classes[$folder_id]) ? $classes[$folder_id] : null;
}
/**
* 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
* @param bool $path_remove Remove the path
*
* @return string Localized folder name in UTF-8 encoding
*/
public static function localize_foldername($name, $with_path = false, $path_remove = false)
{
$rcmail = rcmail::get_instance();
$realnames = $rcmail->config->get('show_real_foldernames');
if (!$realnames && ($folder_class = self::folder_classname($name)) && $rcmail->text_exists($folder_class)) {
return $rcmail->gettext($folder_class);
}
$storage = $rcmail->get_storage();
$delimiter = $storage->get_hierarchy_delimiter();
// Remove the path
if ($path_remove) {
if (strpos($name, $delimiter)) {
$path = explode($delimiter, $name);
$name = array_pop($path);
}
}
// try to localize path of the folder
else if ($with_path && !$realnames) {
$path = explode($delimiter, $name);
$count = count($path);
if ($count > 1) {
for ($i = 1; $i < $count; $i++) {
$folder = implode($delimiter, array_slice($path, 0, -$i));
$folder_class = self::folder_classname($folder);
if ($folder_class && $rcmail->text_exists($folder_class)) {
$name = implode($delimiter, array_slice($path, $count - $i));
$name = rcube_charset::convert($name, 'UTF7-IMAP');
return $rcmail->gettext($folder_class) . $delimiter . $name;
}
}
}
}
return rcube_charset::convert($name, 'UTF7-IMAP');
}
/**
* Localize folder path
*/
public static function localize_folderpath($path)
{
$rcmail = rcmail::get_instance();
$protect_folders = $rcmail->config->get('protect_default_folders');
$delimiter = $rcmail->storage->get_hierarchy_delimiter();
$path = explode($delimiter, $path);
$result = [];
foreach ($path as $idx => $dir) {
$directory = implode($delimiter, array_slice($path, 0, $idx+1));
if ($protect_folders && $rcmail->storage->is_special_folder($directory)) {
unset($result);
$result[] = self::localize_foldername($directory);
}
else {
$result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
}
}
return implode($delimiter, $result);
}
/**
* Gets a value of a boolean attribute from template object attributes
*
* @param array $attributes Template object attributes
* @param string $name Attribute name
*/
public static function get_bool_attr($attributes, $name)
{
if (!isset($attributes[$name])) {
return false;
}
return rcube_utils::get_boolean($attributes[$name]);
}
}
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index 5fcb2c4c9..d9ca390b8 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -1,2633 +1,2634 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) 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 Webmail
* @subpackage View
*/
class rcmail_output_html extends rcmail_output
{
public $type = 'html';
protected $message;
protected $template_name;
protected $objects = array();
protected $js_env = array();
protected $js_labels = array();
protected $js_commands = array();
protected $skin_paths = array();
protected $skin_name = '';
protected $scripts_path = '';
protected $script_files = array();
protected $css_files = array();
protected $scripts = array();
protected $meta_tags = array();
protected $link_tags = array('shortcut icon' => '');
protected $header = '';
protected $footer = '';
protected $body = '';
protected $base_path = '';
protected $assets_path;
protected $assets_dir = RCUBE_INSTALL_PATH;
protected $devel_mode = false;
protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
// deprecated names of templates used before 0.5
protected $deprecated_templates = array(
'contact' => 'showcontact',
'contactadd' => 'addcontact',
'contactedit' => 'editcontact',
'identityedit' => 'editidentity',
'messageprint' => 'printmessage',
);
// deprecated names of template objects used before 1.4
protected $deprecated_template_objects = array(
'addressframe' => 'contentframe',
'messagecontentframe' => 'contentframe',
'prefsframe' => 'contentframe',
'folderframe' => 'contentframe',
'identityframe' => 'contentframe',
'responseframe' => 'contentframe',
'keyframe' => 'contentframe',
'filterframe' => 'contentframe',
);
/**
* Constructor
*/
public function __construct($task = null, $framed = false)
{
parent::__construct();
$this->devel_mode = $this->config->get('devel_mode');
$this->set_env('task', $task);
$this->set_env('standard_windows', (bool) $this->config->get('standard_windows'));
$this->set_env('locale', !empty($_SESSION['language']) ? $_SESSION['language'] : 'en_US');
$this->set_env('devel_mode', $this->devel_mode);
// Version number e.g. 1.4.2 will be 10402
$version = explode('.', preg_replace('/[^0-9.].*/', '', RCMAIL_VERSION));
$this->set_env('rcversion', $version[0] * 10000 + $version[1] * 100 + (isset($version[2]) ? $version[2] : 0));
// 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));
// Easy way to change skin via GET argument, for developers
if ($this->devel_mode && !empty($_GET['skin']) && preg_match('/^[a-z0-9-_]+$/i', $_GET['skin'])) {
if ($this->check_skin($_GET['skin'])) {
$this->set_skin($_GET['skin']);
$this->app->user->save_prefs(array('skin' => $_GET['skin']));
}
}
// load and setup the skin
$this->set_skin($this->config->get('skin'));
$this->set_assets_path($this->config->get('assets_path'), $this->config->get('assets_dir'));
if (!empty($_REQUEST['_extwin']))
$this->set_env('extwin', 1);
if ($this->framed || $framed)
$this->set_env('framed', 1);
$lic = <<<EOF
/*
@licstart The following is the entire license notice for the
JavaScript code in this page.
Copyright (C) The Roundcube Dev Team
The JavaScript code in this page is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
The code is distributed WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU GPL for more details.
@licend The above is the entire license notice
for the JavaScript code in this page.
*/
EOF;
// add common javascripts
$this->add_script($lic, 'head_top');
$this->add_script('var '.self::JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
// don't wait for page onload. Call init at the bottom of the page (delayed)
$this->add_script(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 blankpage (watermark) url
$blankpage = $this->config->get('blankpage_url', '/watermark.html');
$this->set_env('blankpage', $blankpage);
}
/**
* Set environment variable
*
* @param string $name Property name
* @param mixed $value Property value
* @param boolean $addtojs 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;
}
}
/**
* Parse and set assets path
*
* @param string $path Assets path URL (relative or absolute)
* @param string $fs_dif Assets path in filesystem
*/
public function set_assets_path($path, $fs_dir = null)
{
if (empty($path)) {
return;
}
$path = rtrim($path, '/') . '/';
// handle relative assets path
if (!preg_match('|^https?://|', $path) && $path[0] != '/') {
// save the path to search for asset files later
$this->assets_dir = $path;
$base = preg_replace('/[?#&].*$/', '', $_SERVER['REQUEST_URI']);
$base = rtrim($base, '/');
// remove url token if exists
if ($len = intval($this->config->get('use_secure_urls'))) {
$_base = explode('/', $base);
$last = count($_base) - 1;
$length = $len > 1 ? $len : 16; // as in rcube::get_secure_url_token()
// we can't use real token here because it
// does not exists in unauthenticated state,
// hope this will not produce false-positive matches
if ($last > -1 && preg_match('/^[a-f0-9]{' . $length . '}$/', $_base[$last])) {
$path = '../' . $path;
}
}
}
// set filesystem path for assets
if ($fs_dir) {
if ($fs_dir[0] != '/') {
$fs_dir = realpath(RCUBE_INSTALL_PATH . $fs_dir);
}
// ensure the path ends with a slash
$this->assets_dir = rtrim($fs_dir, '/') . '/';
}
$this->assets_path = $path;
$this->set_env('assets_path', $path);
}
/**
* Getter for the current page title
*
* @param bool $full Prepend title with product/user name
*
* @return string The page title
*/
protected function get_pagetitle($full = true)
{
if (!empty($this->pagetitle)) {
$title = $this->pagetitle;
}
else if (isset($this->env['task'])) {
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']);
}
}
else {
$title = '';
}
if ($full) {
if ($this->devel_mode && !empty($_SESSION['username'])) {
$title = $_SESSION['username'] . ' :: ' . $title;
}
else if ($prod_name = $this->config->get('product_name')) {
$title = $prod_name . ' :: ' . $title;
}
}
return $title;
}
/**
* Getter for the current skin path property
*/
public function get_skin_path()
{
return $this->skin_paths[0];
}
/**
* Set skin
*
* @param string $skin Skin name
*/
public function set_skin($skin)
{
if (!$this->check_skin($skin)) {
// If the skin does not exist (could be removed or invalid),
// fallback to the skin set in the system configuration (#7271)
$skin = $this->config->system_skin;
}
$skin_path = 'skins/' . $skin;
$this->config->set('skin_path', $skin_path);
$this->base_path = $skin_path;
// register skin path(s)
$this->skin_paths = array();
$this->skins = array();
$this->load_skin($skin_path);
$this->skin_name = $skin;
$this->set_env('skin', $skin);
}
/**
* Check skin validity/existence
*
* @param string $skin Skin name
*
* @return bool True if the skin exist and is readable, False otherwise
*/
public function check_skin($skin)
{
// Sanity check to prevent from path traversal vulnerability (#1490620)
if (strpos($skin, '/') !== false || strpos($skin, "\\") !== false) {
rcube::raise_error(array(
'file' => __FILE__,
'line' => __LINE__,
'message' => 'Invalid skin name'
), true, false);
return false;
}
$skins_allowed = $this->config->get('skins_allowed');
if (!empty($skins_allowed) && !in_array($skin, (array) $skins_allowed)) {
return false;
}
$path = RCUBE_INSTALL_PATH . 'skins/';
return !empty($skin) && is_dir($path . $skin) && is_readable($path . $skin);
}
/**
* 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 dependencies
$meta = @file_get_contents(RCUBE_INSTALL_PATH . $skin_path . '/meta.json');
$meta = @json_decode($meta, true);
$meta['path'] = $skin_path;
$path_elements = explode('/', $skin_path);
$skin_id = end($path_elements);
if (empty($meta['name'])) {
$meta['name'] = $skin_id;
}
$this->skins[$skin_id] = $meta;
// Keep skin config for ajax requests (#6613)
$_SESSION['skin_config'] = array();
if (!empty($meta['extends'])) {
$path = RCUBE_INSTALL_PATH . 'skins/';
if (is_dir($path . $meta['extends']) && is_readable($path . $meta['extends'])) {
$_SESSION['skin_config'] = $this->load_skin('skins/' . $meta['extends']);
}
}
if (!empty($meta['config'])) {
foreach ($meta['config'] as $key => $value) {
$this->config->set($key, $value, true);
$_SESSION['skin_config'][$key] = $value;
}
$value = array_merge((array) $this->config->get('dont_override'), array_keys($meta['config']));
$this->config->set('dont_override', $value, true);
}
if (!empty($meta['localization'])) {
$locdir = $meta['localization'] === true ? 'localization' : $meta['localization'];
if ($texts = $this->app->read_localization(RCUBE_INSTALL_PATH . $skin_path . '/' . $locdir)) {
$this->app->load_language($_SESSION['language'], $texts);
}
}
// Use array_merge() here to allow for global default and extended skins
if (!empty($meta['meta'])) {
$this->meta_tags = array_merge($this->meta_tags, (array) $meta['meta']);
}
if (!empty($meta['links'])) {
$this->link_tags = array_merge($this->link_tags, (array) $meta['links']);
}
return $_SESSION['skin_config'];
}
/**
* Check if a specific template exists
*
* @param string $name 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]))
+ if (
+ (is_file($filename) && is_readable($filename))
+ || (!empty($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 File name/path to resolve (starting with /)
* @param string &$skin_path Reference to the base path of the matching skin
* @param string $add_path Additional path to search in
* @param bool $minified Fallback to a minified version of the file
*
* @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, $minified = false)
{
$skin_paths = $this->skin_paths;
if ($add_path) {
array_unshift($skin_paths, $add_path);
$skin_paths = array_unique($skin_paths);
}
if ($skin_path = $this->find_file_path($file, $skin_paths)) {
return $skin_path . $file;
}
if ($minified && preg_match('/(?<!\.min)\.(js|css)$/', $file)) {
$file = preg_replace('/\.(js|css)$/', '.min.\\1', $file);
if ($skin_path = $this->find_file_path($file, $skin_paths)) {
return $skin_path . $file;
}
}
return false;
}
/**
* Find path of the asset file
*/
protected function find_file_path($file, $skin_paths)
{
foreach ($skin_paths as $skin_path) {
if ($this->assets_dir != RCUBE_INSTALL_PATH) {
if (realpath($this->assets_dir . $skin_path . $file)) {
return $skin_path;
}
}
if (realpath(RCUBE_INSTALL_PATH . $skin_path . $file)) {
return $skin_path;
}
}
}
/**
* Register a GUI object to the client script
*
* @param string $obj Object name
* @param string $id Object ID
*/
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(array('rcube','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)
{
$framed = $this->framed;
$task = isset($this->env['task']) ? $this->env['task'] : '';
$env = $all ? null : array_intersect_key($this->env, array('extwin' => 1, 'framed' => 1));
// keep jQuery-UI files
$css_files = $script_files = array();
foreach ($this->css_files as $file) {
if (strpos($file, 'plugins/jqueryui') === 0) {
$css_files[] = $file;
}
}
foreach ($this->script_files as $position => $files) {
foreach ($files as $file) {
if (strpos($file, 'plugins/jqueryui') === 0) {
$script_files[$position][] = $file;
}
}
}
parent::reset();
// let some env variables survive
$this->env = $this->js_env = $env;
$this->framed = $framed || !empty($this->env['framed']);
$this->js_labels = array();
$this->js_commands = array();
$this->scripts = array();
$this->header = '';
$this->footer = '';
$this->body = '';
$this->css_files = array();
$this->script_files = array();
// load defaults
if (!$all) {
$this->__construct();
}
// Note: we merge jQuery-UI scripts after jQuery...
$this->css_files = array_merge($this->css_files, $css_files);
$this->script_files = array_merge_recursive($this->script_files, $script_files);
$this->set_env('orig_task', $task);
}
/**
* 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
* @param bool $secure Redirect to secure location (see rcmail::url())
*/
public function redirect($p = array(), $delay = 1, $secure = false)
{
if (!empty($this->env['extwin'])) {
$p['extwin'] = 1;
}
$location = $this->app->url($p, false, false, $secure);
$this->header('Location: ' . $location);
exit;
}
/**
* Send the request output to the client.
* This will either parse a skin template.
*
* @param string $templ Template name
* @param boolean $exit 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 = '')
{
if (!empty($this->script_files)) {
$this->set_env('request_token', $this->app->get_request_token());
}
// Fix assets path on blankpage
if (!empty($this->js_env['blankpage'])) {
$this->js_env['blankpage'] = $this->asset_url($this->abs_url($this->js_env['blankpage'], true));
}
$commands = $this->get_js_commands($framed);
// if all js commands go to parent window we can ignore all
// script files and skip rcube_webmail initialization (#1489792)
// but not on error pages where skins may need jQuery, etc.
if ($framed && empty($this->js_env['server_error'])) {
$this->scripts = array();
$this->script_files = array();
$this->header = '';
$this->footer = '';
}
// write all javascript commands
if (!empty($commands)) {
$this->add_script($commands, 'head_top');
}
$this->page_headers();
// call super method
$this->_write($template);
}
/**
* Send common page headers
* For now it only (re)sets X-Frame-Options when needed
*/
public function page_headers()
{
if (headers_sent()) {
return;
}
// allow (legal) iframe content to be loaded
$framed = $this->framed || !empty($this->env['framed']);
if ($framed && ($xopt = $this->app->config->get('x_frame_options', 'sameorigin'))) {
if (strtolower($xopt) === 'deny') {
$this->header('X-Frame-Options: sameorigin', true);
}
}
}
/**
* Parse a specific skin template and deliver to stdout (or return)
*
* @param string $name Template name
* @param boolean $exit Exit script
* @param boolean $write 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;
$skin_dir = '';
$plugin_skin_paths = array();
$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
foreach ($this->skin_paths as $skin_path) {
$plugin_skin_paths[] = $this->app->plugins->url . $plugin . '/' . $skin_path;
}
// prepend 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) {
// when requesting a plugin template ignore global skin path(s)
if ($plugin && strpos($skin_path, $this->app->plugins->url) !== 0) {
continue;
}
$path = RCUBE_INSTALL_PATH . "$skin_path/templates/$name.html";
// fallback to deprecated template names
if (!is_readable($path) && ($dname = $this->deprecated_templates[$realname])) {
$path = RCUBE_INSTALL_PATH . "$skin_path/templates/$dname.html";
if (is_readable($path)) {
rcube::raise_error(array(
'code' => 502, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Using deprecated template '$dname' in $skin_path/templates. Please rename to '$realname'"
), true, false);
}
}
if (is_readable($path)) {
$this->config->set('skin_path', $skin_path);
// set base_path to core skin directory (not plugin's skin)
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path);
$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' => 404,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => 'Error loading template for '.$realname
), true, $write);
$this->skin_paths = array_slice($this->skin_paths, count($plugin_skin_paths));
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, 'write' => $write));
// save some memory
$output = $hook['content'];
unset($hook['content']);
// remove plugin skin paths from current context
$this->skin_paths = array_slice($this->skin_paths, count($plugin_skin_paths));
if (!$write) {
return $this->postrender($output);
}
$this->write(trim($output));
if ($exit) {
exit;
}
}
/**
* Return executable javascript code for all registered commands
*/
protected function get_js_commands(&$framed = null)
{
$out = '';
$parent_commands = 0;
$parent_prefix = '';
$top_commands = array();
// these should be always on top,
// e.g. hide_message() below depends on env.framed
if (!$this->framed && !empty($this->js_env)) {
$top_commands[] = array('set_env', $this->js_env);
}
if (!empty($this->js_labels)) {
$top_commands[] = array('add_label', $this->js_labels);
}
// unlock interface after iframe load
$unlock = isset($_REQUEST['_unlock']) ? preg_replace('/[^a-z0-9]/i', '', $_REQUEST['_unlock']) : 0;
if ($this->framed) {
$top_commands[] = array('iframe_loaded', $unlock);
}
else if ($unlock) {
$top_commands[] = array('hide_message', $unlock);
}
$commands = array_merge($top_commands, $this->js_commands);
foreach ($commands as $i => $args) {
$method = array_shift($args);
$parent = $this->framed || preg_match('/^parent\./', $method);
foreach ($args as $i => $arg) {
$args[$i] = self::json_serialize($arg, $this->devel_mode);
}
if ($parent) {
$parent_commands++;
$method = preg_replace('/^parent\./', '', $method);
$parent_prefix = 'if (window.parent && parent.' . self::JS_OBJECT_NAME . ') parent.';
$method = $parent_prefix . self::JS_OBJECT_NAME . '.' . $method;
}
else {
$method = self::JS_OBJECT_NAME . '.' . $method;
}
$out .= sprintf("%s(%s);\n", $method, implode(',', $args));
}
$framed = $parent_prefix && $parent_commands == count($commands);
// make the output more compact if all commands go to parent window
if ($framed) {
$out = "if (window.parent && parent." . self::JS_OBJECT_NAME . ") {\n"
. str_replace($parent_prefix, "\tparent.", $out)
. "}\n";
}
return $out;
}
/**
* Make URLs starting with a slash point to skin directory
*
* @param string $str Input string
* @param bool $search_path True if URL should be resolved using the current skin path stack
*
* @return string URL
*/
public function abs_url($str, $search_path = false)
{
if ($str[0] == '/') {
if ($search_path && ($file_url = $this->get_skin_file($str))) {
return $file_url;
}
return $this->base_path . $str;
}
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)
{
$args = [
'code' => $code,
'message' => $message,
];
$page = new rcmail_action_utils_error;
$page->run($args);
}
/**
* Modify path by adding URL prefix if configured
*
* @param string $path Asset path
* @param bool $abs_url Pass to self::abs_url() first
*
* @return string Asset path
*/
public function asset_url($path, $abs_url = false)
{
// iframe content can't be in a different domain
// @TODO: check if assests are on a different domain
if ($abs_url) {
$path = $this->abs_url($path, true);
}
if (!$this->assets_path || in_array($path[0], array('?', '/', '.')) || strpos($path, '://')) {
return $path;
}
return $this->assets_path . $path;
}
/***** 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 function 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 cache busters)
*/
protected function fix_paths($output)
{
return preg_replace_callback(
'!(src|href|background|data-src-[a-z]+)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
array($this, 'file_callback'), $output);
}
/**
* Callback function for preg_replace_callback in fix_paths()
*
* @return string Parsed string
*/
protected function file_callback($matches)
{
$file = $matches[3];
$file = preg_replace('!^/this/!', '/', $file);
// correct absolute paths
if ($file[0] == '/') {
$this->get_skin_file($file, $skin_path, $this->base_path);
$file = ($skin_path ?: $this->base_path) . $file;
}
// add file modification timestamp
if (preg_match('/\.(js|css|less|ico|png|svg|jpeg)$/', $file)) {
$file = $this->file_mod($file);
}
return $matches[1] . '=' . $matches[2] . $file . $matches[4];
}
/**
* Correct paths of asset files according to assets_path
*/
protected function fix_assets_paths($output)
{
return preg_replace_callback(
'!(src|href|background)=(["\']?)([a-z0-9/_.?=-]+)(["\'\s>])!i',
array($this, 'assets_callback'), $output);
}
/**
* Callback function for preg_replace_callback in fix_assets_paths()
*
* @return string Parsed string
*/
protected function assets_callback($matches)
{
$file = $this->asset_url($matches[3]);
return $matches[1] . '=' . $matches[2] . $file . $matches[4];
}
/**
* Modify file by adding mtime indicator
*/
protected function file_mod($file)
{
$fs = false;
$ext = substr($file, strrpos($file, '.') + 1);
// use minified file if exists (not in development mode)
if (!$this->devel_mode && !preg_match('/\.min\.' . $ext . '$/', $file)) {
$minified_file = substr($file, 0, strlen($ext) * -1) . 'min.' . $ext;
if ($fs = @filemtime($this->assets_dir . $minified_file)) {
return $minified_file . '?s=' . $fs;
}
}
if ($fs = @filemtime($this->assets_dir . $file)) {
$file .= '?s=' . $fs;
}
return $file;
}
/**
* Public wrapper to dipp into template parsing.
*
* @param string $input Template content
*
* @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);
$input = $this->postrender($input);
return $input;
}
/**
* Parse for conditional tags
*/
protected function parse_conditions($input)
{
while (preg_match('/<roundcube:if\s+[^>]+>(((?!<roundcube:(if|endif)).)*)<roundcube:endif[^>]*>/is', $input, $conditions)) {
$result = $this->eval_condition($conditions[0]);
$input = str_replace($conditions[0], $result, $input);
}
return $input;
}
/**
* Process & evaluate conditional tags
*/
protected function eval_condition($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->eval_condition($matches[3]);
}
$attrib = html::parse_attrib_string($matches[2]);
if (isset($attrib['condition'])) {
$condmet = $this->check_condition($attrib['condition']);
$condparts = preg_split('/<roundcube:((elseif|else|endif)[^>]*)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
if ($condmet) {
$result = $condparts[0];
if ($condparts[2] != 'endif') {
$result .= preg_replace('/.*<roundcube:endif[^>]*>\n?/Uis', '', $condparts[3], 1);
}
else {
$result .= $condparts[3];
}
}
else {
$result = "<roundcube:$condparts[1]>" . $condparts[3];
}
return $matches[0] . $this->eval_condition($result);
}
rcube::raise_error(array(
'code' => 500, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Unable to parse conditional tag " . $matches[2]
), true, false);
}
return $input;
}
/**
* Determines if a given condition is met
*
* @param string $condition Condition statement
*
* @return boolean True if condition is met, False if not
* @todo Extend this to allow real conditions, not just "set"
*/
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') {
+ if (!empty($attrib['method']) && 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 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(
"(isset(\$_SESSION['\\1']) ? \$_SESSION['\\1'] : null)",
"\$this->app->config->get('\\1',rcube_utils::get_boolean('\\3'))",
"(isset(\$this->env['\\1']) ? \$this->env['\\1'] : null)",
"rcube_utils::get_input_value('\\1', rcube_utils::INPUT_GPC)",
"(isset(\$_COOKIE['\\1']) ? \$_COOKIE['\\1'] : null)",
"(isset(\$this->browser->{'\\1'}) ? \$this->browser->{'\\1'} : null)",
"'{$this->template_name}'",
),
$expression
);
// Note: We used create_function() before but it's deprecated in PHP 7.2
// and really it was just a wrapper on eval().
return eval("return ($expression);");
}
/**
* Parse variable strings
*
* @param string $type Variable type (env, config etc)
* @param string $name Variable name
*
* @return mixed Variable value
*/
protected function parse_variable($type, $name)
{
$value = '';
switch ($type) {
case 'env':
$value = isset($this->env[$name]) ? $this->env[$name] : null;
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], ENT_COMPAT | ENT_HTML401, RCUBE_CHARSET);
break;
case 'browser':
$value = $this->browser->{$name};
break;
}
return $value;
}
/**
* Search for special tags in input and replace them
* with the appropriate content
*
* @param string $input 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 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 '';
}
// localize title and summary attributes
if ($command != 'button' && !empty($attrib['title']) && $this->app->text_exists($attrib['title'])) {
$attrib['title'] = $this->app->gettext($attrib['title']);
}
if ($command != 'button' && !empty($attrib['summary']) && $this->app->text_exists($attrib['summary'])) {
$attrib['summary'] = $this->app->gettext($attrib['summary']);
}
// execute command
switch ($command) {
// return a button
case 'button':
if (!empty($attrib['name']) || !empty($attrib['command'])) {
return $this->button($attrib);
}
break;
// frame
case 'frame':
return $this->frame($attrib);
break;
// show a label
case 'label':
if (!empty($attrib['expression'])) {
$attrib['name'] = $this->eval_expression($attrib['expression']);
}
if (!empty($attrib['name']) || !empty($attrib['command'])) {
$vars = $attrib + array('product' => $this->config->get('product_name'));
unset($vars['name'], $vars['command']);
$label = $this->app->gettext($attrib + array('vars' => $vars));
$quoting = null;
if (!empty($attrib['quoting'])) {
$quoting = strtolower($attrib['quoting']);
}
else if (isset($attrib['html'])) {
$quoting = rcube_utils::get_boolean((string) $attrib['html']) ? 'no' : '';
}
// 'noshow' can be used in skins to define new labels
if (!empty($attrib['noshow'])) {
return '';
}
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;
case 'add_label':
$this->add_label($attrib['name']);
break;
// include a file
case 'include':
if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
break;
}
if ($attrib['file'][0] != '/') {
$attrib['file'] = '/templates/' . $attrib['file'];
}
$old_base_path = $this->base_path;
$include = '';
$attr_skin_path = !empty($attrib['skinpath']) ? $attrib['skinpath'] : null;
if (!empty($attrib['skin_path'])) {
$attr_skin_path = $attrib['skin_path'];
}
if ($path = $this->get_skin_file($attrib['file'], $skin_path, $attr_skin_path)) {
// set base_path to core skin directory (not plugin's skin)
$this->base_path = preg_replace('!plugins/\w+/!', '', $skin_path);
$path = realpath(RCUBE_INSTALL_PATH . $path);
}
if (is_readable($path)) {
$allow_php = $this->config->get('skin_include_php');
$include = $allow_php ? $this->include_php($path) : file_get_contents($path);
$include = $this->parse_conditions($include);
$include = $this->parse_xml($include);
$include = $this->fix_paths($include);
}
$this->base_path = $old_base_path;
return $include;
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 = '';
$handler = null;
// correct deprecated object names
if (!empty($this->deprecated_template_objects[$object])) {
$object = $this->deprecated_template_objects[$object];
}
if (!empty($this->object_handlers[$object])) {
$handler = $this->object_handlers[$object];
}
// execute object handler function
if (is_callable($handler)) {
$this->prepare_object_attribs($attrib);
// We assume that objects with src attribute are internal (in most
// cases this is a watermark frame). We need this to make sure assets_path
// is added to the internal assets paths
$external = empty($attrib['src']);
$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"')));
// 'type' attribute added in 1.4 was renamed 'logo-type' in 1.5
// check both for backwards compatibility
$logo_type = !empty($attrib['logo-type']) ? $attrib['logo-type'] : null;
$logo_match = !empty($attrib['logo-match']) ? $attrib['logo-match'] : null;
if (!empty($attrib['type']) && empty($logo_type)) {
$logo_type = $attrib['type'];
}
if (($template_logo = $this->get_template_logo($logo_type, $logo_match)) !== null) {
$attrib['src'] = $template_logo;
}
$additional_logos = array();
$logo_types = (array) $this->config->get('additional_logo_types');
foreach ($logo_types as $type) {
if (($template_logo = $this->get_template_logo($type)) !== null) {
$additional_logos[$type] = $this->abs_url($template_logo);
}
}
if (!empty($additional_logos)) {
$this->set_env('additional_logos', $additional_logos);
}
if (!empty($attrib['src'])) {
$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(false));
}
else if ($object == 'pagetitle') {
// Deprecated, <title> will be added automatically
$content = html::quote($this->get_pagetitle());
}
else if ($object == 'contentframe') {
if (empty($attrib['id'])) {
$attrib['id'] = 'rcm' . $this->env['task'] . 'frame';
}
// parse variables
if (preg_match('/^(config|env):([a-z0-9_]+)$/i', $attrib['src'], $matches)) {
$attrib['src'] = $this->parse_variable($matches[1], $matches[2]);
}
$content = $this->frame($attrib, true);
}
else if ($object == 'meta' || $object == 'links') {
if ($object == 'meta') {
$source = 'meta_tags';
$tag = 'meta';
$key = 'name';
$param = 'content';
}
else {
$source = 'link_tags';
$tag = 'link';
$key = 'rel';
$param = 'href';
}
foreach ($this->$source as $name => $vars) {
// $vars can be in many forms:
// - string
// - array('key' => 'val')
// - array(string, string)
// - array(array(), string)
// - array(array('key' => 'val'), array('key' => 'val'))
// normalise this for processing by checking for string array keys
$vars = is_array($vars) ? (count(array_filter(array_keys($vars), 'is_string')) > 0 ? array($vars) : $vars) : array($vars);
foreach ($vars as $args) {
// skip unset headers e.g. when extending a skin and removing a header defined in the parent
if ($args === false) {
continue;
}
$args = is_array($args) ? $args : array($param => $args);
// special handling for favicon
if ($object == 'links' && $name == 'shortcut icon' && empty($args[$param])) {
if ($href = $this->get_template_logo('favicon')) {
$args[$param] = $href;
}
else if ($href = $this->config->get('favicon', '/images/favicon.ico')) {
$args[$param] = $href;
}
}
$content .= html::tag($tag, array($key => $name, 'nl' => true) + $args);
}
}
}
// exec plugin hooks for this template object
$hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
if (strlen($hook['content']) && !empty($external)) {
$object_id = uniqid('TEMPLOBJECT:', true);
$this->objects[$object_id] = $hook['content'];
$hook['content'] = $object_id;
}
return $hook['content'];
// return <link> element
case 'link':
if ($attrib['condition'] && !$this->check_condition($attrib['condition'])) {
break;
}
unset($attrib['condition']);
return html::tag('link', $attrib);
// 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']);
$value = $this->parse_variable($var[0], $var[1]);
if (is_array($value)) {
$value = implode(', ', $value);
}
return html::quote($value);
case 'form':
return $this->form_tag($attrib);
}
return '';
}
/**
* Prepares template object attributes
*
* @param array &$attribs Attributes
*/
protected function prepare_object_attribs(&$attribs)
{
foreach ($attribs as $key => &$value) {
if (strpos($key, 'data-label-') === 0) {
// Localize data-label-* attributes
$value = $this->app->gettext($value);
}
elseif ($key[0] == ':') {
// Evaluate attributes with expressions and remove special character from attribute name
$attribs[substr($key, 1)] = $this->eval_expression($value);
unset($attribs[$key]);
}
}
}
/**
* Include a specific file and return it's contents
*
* @param string $file 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;
}
/**
* Put objects' content back into template output
*/
protected function postrender($output)
{
// insert objects' contents
foreach ($this->objects as $key => $val) {
$output = str_replace($key, $val, $output, $count);
if ($count) {
$this->objects[$key] = null;
}
}
// make sure all <form> tags have a valid request token
$output = preg_replace_callback('/<form\s+([^>]+)>/Ui', array($this, 'alter_form_tag'), $output);
return $output;
}
/**
* Create and register a button
*
* @param array $attrib 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;
static $disabled_actions = null;
// these commands can be called directly via url
$a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
if (empty($attrib['command']) && empty($attrib['name']) && empty($attrib['href'])) {
return '';
}
$command = !empty($attrib['command']) ? $attrib['command'] : null;
$action = $command ?: (!empty($attrib['name']) ? $attrib['name'] : null);
if (!empty($attrib['task'])) {
$command = $attrib['task'] . '.' . $command;
$element = $attrib['task'] . '.' . $action;
}
else {
$element = (!empty($this->env['task']) ? $this->env['task'] . '.' : '') . $action;
}
if ($disabled_actions === null) {
$disabled_actions = (array) $this->config->get('disabled_actions');
}
// remove buttons for disabled actions
if (in_array($element, $disabled_actions) || in_array($action, $disabled_actions)) {
return '';
}
// try to find out the button type
if (!empty($attrib['type'])) {
$attrib['type'] = strtolower($attrib['type']);
if (strpos($attrib['type'], '-menuitem')) {
$attrib['type'] = substr($attrib['type'], 0, -9);
$menuitem = true;
}
}
else if (!empty($attrib['image']) || !empty($attrib['imagepas']) || !empty($attrib['imageact'])) {
$attrib['type'] = 'image';
}
else {
$attrib['type'] = 'button';
}
if (empty($attrib['image'])) {
if (!empty($attrib['imagepas'])) {
$attrib['image'] = $attrib['imagepas'];
}
else if (!empty($attrib['imageact'])) {
$attrib['image'] = $attrib['imageact'];
}
}
if (empty($attrib['id'])) {
// ensure auto generated IDs are unique between main window and content frame
// Elastic skin duplicates buttons between the two on smaller screens (#7618)
$prefix = ($this->framed || !empty($this->env['framed'])) ? 'frm' : '';
$attrib['id'] = sprintf('rcmbtn%s%d', $prefix, $s_button_count++);
}
// get localized text for labels and titles
$domain = !empty($attrib['domain']) ? $attrib['domain'] : null;
if (!empty($attrib['title'])) {
$attrib['title'] = html::quote($this->app->gettext($attrib['title'], $domain));
}
if (!empty($attrib['label'])) {
$attrib['label'] = html::quote($this->app->gettext($attrib['label'], $domain));
}
if (!empty($attrib['alt'])) {
$attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $domain));
}
// set accessibility attributes
if (empty($attrib['role'])) {
$attrib['role'] = 'button';
}
if (!empty($attrib['class']) && !empty($attrib['classact']) || !empty($attrib['imagepas']) && !empty($attrib['imageact'])) {
if (array_key_exists('tabindex', $attrib)) {
$attrib['data-tabindex'] = $attrib['tabindex'];
}
$attrib['tabindex'] = '-1'; // disable button by default
$attrib['aria-disabled'] = 'true';
}
// set title to alt attribute for IE browsers
if ($this->browser->ie && empty($attrib['title']) && !empty($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 (!empty($attrib['command'])) {
$this->add_script(sprintf(
"%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
self::JS_OBJECT_NAME,
$command,
$attrib['id'],
$attrib['type'],
!empty($attrib['imageact']) ? $this->abs_url($attrib['imageact']) : (!empty($attrib['classact']) ? $attrib['classact'] : ''),
!empty($attrib['imagesel']) ? $this->abs_url($attrib['imagesel']) : (!empty($attrib['classsel']) ? $attrib['classsel'] : ''),
!empty($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 (!empty($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 (empty($attrib['href'])) {
$attrib['href'] = '#';
}
if (!empty($attrib['task'])) {
if (!empty($attrib['classact'])) {
$attrib['class'] = $attrib['classact'];
}
}
else if ($command && empty($attrib['onclick'])) {
$attrib['onclick'] = sprintf(
"return %s.command('%s','%s',this,event)",
self::JS_OBJECT_NAME,
$command,
!empty($attrib['prop']) ? $attrib['prop'] : ''
);
}
$out = '';
$btn_content = null;
$link_attrib = array();
// 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 (!empty($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'] : (!empty($attrib['label']) ? $attrib['label'] : $attrib['command']);
$link_attrib = array_merge(html::$common_attrib, array('href', 'onclick', 'tabindex', 'target', 'rel'));
if (!empty($attrib['innerclass'])) {
$btn_content = html::span($attrib['innerclass'], $btn_content);
}
}
else if ($attrib['type'] == 'input') {
$attrib['type'] = 'button';
if (!empty($attrib['label'])) {
$attrib['value'] = $attrib['label'];
}
if (!empty($attrib['command'])) {
$attrib['disabled'] = 'disabled';
}
$out = html::tag('input', $attrib, null, array('type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex', 'disabled'));
}
else {
if (!empty($attrib['label'])) {
$attrib['value'] = $attrib['label'];
}
if (!empty($attrib['command'])) {
$attrib['disabled'] = 'disabled';
}
$content = isset($attrib['content']) ? $attrib['content'] : $attrib['label'];
$out = html::tag('button', $attrib, $content, 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 (!empty($attrib['wrapper'])) {
$out = html::tag($attrib['wrapper'], null, $out);
}
if (!empty($menuitem)) {
$class = !empty($attrib['menuitem-class']) ? ' class="' . $attrib['menuitem-class'] . '"' : '';
$out = '<li role="menuitem"' . $class . '>' . $out . '</li>';
}
return $out;
}
/**
* Link an external script file
*
* @param string $file File URL
* @param string $position Target position [head|head_bottom|foot]
*/
public function include_script($file, $position = 'head', $add_path = true)
{
if ($add_path && !preg_match('|^https?://|i', $file) && $file[0] != '/') {
$file = $this->file_mod($this->scripts_path . $file);
}
if (!isset($this->script_files[$position]) || !is_array($this->script_files[$position])) {
$this->script_files[$position] = array();
}
if (!in_array($file, $this->script_files[$position])) {
$this->script_files[$position][] = $file;
}
}
/**
* Add inline javascript code
*
* @param string $script JS code snippet
* @param string $position Target position [head|head_top|foot|docready]
*/
public function add_script($script, $position = 'head')
{
if (!isset($this->scripts[$position])) {
$this->scripts[$position] = rtrim($script);
}
else {
$this->scripts[$position] .= "\n" . rtrim($script);
}
}
/**
* Link an external css file
*
* @param string $file 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 $output HTML output
*/
protected function _write($output = '')
{
$output = trim($output);
if (empty($output)) {
$output = html::doctype('html5') . "\n" . $this->default_template;
$is_empty = true;
}
$merge_script_files = function($output, $script) {
return $output . html::script($script);
};
$merge_scripts = function($output, $script) {
return $output . html::script(array(), $script);
};
// put docready commands into page footer
if (!empty($this->scripts['docready'])) {
$this->add_script("\$(function() {\n" . $this->scripts['docready'] . "\n});", 'foot');
}
$page_header = '';
$page_footer = '';
$meta = '';
// declare page language
if (!empty($_SESSION['language'])) {
$lang = substr($_SESSION['language'], 0, 2);
$output = preg_replace('/<html/', '<html lang="' . html::quote($lang) . '"', $output, 1);
if (!headers_sent()) {
$this->header('Content-Language: ' . $lang);
}
}
// include meta tag with charset
if (!empty($this->charset)) {
if (!headers_sent()) {
$this->header('Content-Type: text/html; charset=' . $this->charset);
}
$meta .= html::tag('meta', array(
'http-equiv' => 'content-type',
'content' => "text/html; charset={$this->charset}",
'nl' => true
));
}
// include page title (after charset specification)
$meta .= '<title>' . html::quote($this->get_pagetitle()) . "</title>\n";
$output = preg_replace('/(<head[^>]*>)\n*/i', "\\1\n{$meta}", $output, 1, $count);
if (!$count) {
$page_header .= $meta;
}
// include scripts into header/footer
if (!empty($this->script_files['head'])) {
$page_header .= array_reduce((array) $this->script_files['head'], $merge_script_files);
}
$head = $this->scripts['head_top'] . (isset($this->scripts['head']) ? $this->scripts['head'] : '');
$page_header .= array_reduce((array) $head, $merge_scripts);
$page_header .= $this->header . "\n";
if (!empty($this->script_files['head_bottom'])) {
$page_header .= array_reduce((array) $this->script_files['head_bottom'], $merge_script_files);
}
if (!empty($this->script_files['foot'])) {
$page_footer .= array_reduce((array) $this->script_files['foot'], $merge_script_files);
}
$page_footer .= $this->footer . "\n";
if (!empty($this->scripts['foot'])) {
$page_footer .= array_reduce((array) $this->scripts['foot'], $merge_scripts);
}
// 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$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>'))) {
// for Elastic: put footer content before "footer scripts"
while (($npos = strripos($output, "\n", -strlen($output) + $fpos - 1))
&& $npos != $fpos
&& ($chunk = substr($output, $npos, $fpos - $npos)) !== ''
&& (trim($chunk) === '' || preg_match('/\s*<script[^>]+><\/script>\s*/', $chunk))
) {
$fpos = $npos;
}
$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) && empty($is_empty)
&& (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
) {
$css = '';
foreach ($this->css_files as $file) {
$is_less = substr_compare($file, '.less', -5, 5, true) === 0;
$css .= html::tag('link', array(
'rel' => $is_less ? 'stylesheet/less' : 'stylesheet',
'type' => 'text/css',
'href' => $file,
'nl' => true,
));
}
$output = substr_replace($output, $css, $pos, 0);
}
$output = $this->parse_with_globals($this->fix_paths($output));
if ($this->assets_path) {
$output = $this->fix_assets_paths($output);
}
$output = $this->postrender($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 (empty($attrib['id'])) {
$attrib['id'] = 'rcmframe' . ++$idcount;
}
$attrib['name'] = $attrib['id'];
$attrib['src'] = !empty($attrib['src']) ? $this->abs_url($attrib['src'], true) : 'about:blank';
// register as 'contentframe' object
if ($is_contentframe || !empty($attrib['contentframe'])) {
$this->set_env('contentframe', !empty($attrib['contentframe']) ? $attrib['contentframe'] : $attrib['name']);
}
return html::iframe($attrib);
}
/* ************* common functions delivering gui objects ************** */
/**
* Create a form tag with the necessary hidden fields
*
* @param array $attrib Named tag parameters
* @param string $content HTML content of the form
*
* @return string HTML code for the form
*/
public function form_tag($attrib, $content = null)
{
$hidden = '';
if (!empty($this->env['extwin'])) {
$hiddenfield = new html_hiddenfield(array('name' => '_extwin', 'value' => '1'));
$hidden = $hiddenfield->show();
}
else if ($this->framed || !empty($this->env['framed'])) {
$hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$hidden = $hiddenfield->show();
}
if (!$content) {
$attrib['noclose'] = true;
}
return html::tag('form',
$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 $attrib Named tag parameters including 'action' and 'task' values
* which will be put into hidden fields
* @param string $content Form content
*
* @return string HTML code for the form
*/
public function request_form($attrib, $content = '')
{
$hidden = new html_hiddenfield();
if (!empty($attrib['task'])) {
$hidden->add(array('name' => '_task', 'value' => $attrib['task']));
}
if (!empty($attrib['action'])) {
$hidden->add(array('name' => '_action', 'value' => $attrib['action']));
}
// we already have a <form> tag
if (!empty($attrib['form'])) {
if ($this->framed || !empty($this->env['framed'])) {
$hidden->add(array('name' => '_framed', 'value' => '1'));
}
return $hidden->show() . $content;
}
unset($attrib['task'], $attrib['request']);
$attrib['action'] = './';
return $this->form_tag($attrib, $hidden->show() . $content);
}
/**
* GUI object 'username'
* Showing IMAP username of the current session
*
* @param array $attrib 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();
}
$username = rcube_utils::idn_to_utf8($username);
return html::quote($username);
}
/**
* GUI object 'loginform'
* Returns code for the webmail login form
*
* @param array $attrib 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');
$username_filter = $this->config->get('login_username_filter');
$_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';
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
// 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');
if ($username_filter && strtolower($username_filter) == 'email') {
$user_attrib['type'] = 'email';
}
$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', 'required' => 'required')
+ $attrib + $user_attrib);
$input_pass = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'required' => 'required')
+ $attrib + $pass_attrib);
$input_host = null;
$hide_host = false;
if (is_array($default_host) && count($default_host) > 1) {
$input_host = new html_select(array('name' => '_host', 'id' => 'rcmloginhost', 'class' => 'custom-select'));
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', 'class' => 'form-control')
+ $attrib + $host_attrib);
}
$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();
}
if (rcube_utils::get_boolean($attrib['submit'])) {
$button_attr = array('type' => 'submit', 'id' => 'rcmloginsubmit', 'class' => 'button mainaction submit');
$out .= html::p('formbuttons', html::tag('button', $button_attr, $this->app->gettext('login')));
}
// add oauth login button
if ($this->config->get('oauth_auth_uri') && $this->config->get('oauth_provider')) {
$link_attr = array('href' => $this->app->url(array('action' => 'oauth')), 'id' => 'rcmloginoauth', 'class' => 'button oauth ' . $this->config->get('oauth_provider'));
$out .= html::p('oauthlogin', html::a($link_attr, $this->app->gettext(array('name' => 'oauthlogin', 'vars' => array('provider' => $this->config->get('oauth_provider_name', 'OAuth'))))));
}
// 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 $attrib 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);
$images = array_map(array($this, 'asset_url'), $images);
if (empty($images) || $_REQUEST['_task'] == 'logout') {
return;
}
$this->add_script('var images = ' . self::json_serialize($images, $this->devel_mode) .';
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 $attrib Named parameters
*
* @return string HTML code for the gui object
*/
public function search_form($attrib)
{
// add some labels to client
$this->add_label('searching');
$attrib['name'] = '_q';
$attrib['class'] = trim((!empty($attrib['class']) ? $attrib['class'] : '') . ' no-bs');
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmqsearchbox';
}
if (isset($attrib['type']) && $attrib['type'] == 'search' && !$this->browser->khtml) {
unset($attrib['type'], $attrib['results']);
}
if (empty($attrib['placeholder'])) {
$attrib['placeholder'] = $this->app->gettext('searchplaceholder');
}
$label = html::label(array('for' => $attrib['id'], 'class' => 'voice'), rcube::Q($this->app->gettext('arialabelsearchterms')));
$input_q = new html_inputfield($attrib);
$out = $label . $input_q->show();
$name = 'qsearchbox';
// Support for multiple searchforms on the same page
if (isset($attrib['gui-object']) && $attrib['gui-object'] !== false && $attrib['gui-object'] !== 'false') {
$name = $attrib['gui-object'];
}
$this->add_gui_object($name, $attrib['id']);
// add form tag around text field
if (empty($attrib['form']) && empty($attrib['no-form'])) {
$out = $this->form_tag(array(
'name' => !empty($attrib['form-name']) ? $attrib['form-name'] : 'rcmqsearchform',
'onsubmit' => sprintf(
"%s.command('%s'); return false",
self::JS_OBJECT_NAME,
!empty($attrib['command']) ? $attrib['command'] : 'search'
),
// 'style' => "display:inline"
), $out);
}
if (!empty($attrib['wrapper'])) {
$options_button = '';
$ariatag = !empty($attrib['ariatag']) ? $attrib['ariatag'] : 'h2';
$domain = !empty($attrib['label-domain']) ? $attrib['label-domain'] : null;
$options = !empty($attrib['options']) ? $attrib['options'] : null;
$header_label = $this->app->gettext('arialabel' . $attrib['label'], $domain);
$header_attrs = array(
'id' => 'aria-label-' . $attrib['label'],
'class' => 'voice'
);
$header = html::tag($ariatag, $header_attrs, rcube::Q($header_label));
if ($attrib['options']) {
$options_button = $this->button(array(
'type' => 'link',
'href' => '#search-filter',
'class' => 'button options',
'label' => 'options',
'title' => 'options',
'tabindex' => '0',
'innerclass' => 'inner',
'data-target' => $options
));
}
$search_button = $this->button(array(
'type' => 'link',
'href' => '#search',
'class' => 'button search',
'label' => $attrib['buttontitle'],
'title' => $attrib['buttontitle'],
'tabindex' => '0',
'innerclass' => 'inner',
));
$reset_button = $this->button(array(
'type' => 'link',
'command' => !empty($attrib['reset-command']) ? $attrib['reset-command'] : 'reset-search',
'class' => 'button reset',
'label' => 'resetsearch',
'title' => 'resetsearch',
'tabindex' => '0',
'innerclass' => 'inner',
));
$out = html::div(array(
'role' => 'search',
'aria-labelledby' => !empty($attrib['label']) ? 'aria-label-' . $attrib['label'] : null,
'class' => $attrib['wrapper'],
), "$header$out\n$reset_button\n$options_button\n$search_button");
}
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 $attrib 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').')',
'KOI8-R' => 'KOI8-R ('.$this->app->gettext('cyrillic').')',
);
if ($post = rcube_utils::get_input_value('_charset', rcube_utils::INPUT_POST)) {
$set = $post;
}
else if (!empty($attrib['selected'])) {
$set = $attrib['selected'];
}
else {
$set = $this->get_charset();
}
$set = strtoupper($set);
if (!isset($charsets[$set]) && preg_match('/^[A-Z0-9-]+$/', $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;
}
/**
* Get logo URL for current template based on skin_logo config option
*
* @param string $type Type of the logo to check for (e.g. 'print' or 'small')
* default is null (no special type)
* @param string $match (optional) 'all' = type, template or wildcard, 'template' = type or template
* Note: when type is specified matches are limited to type only unless $match is defined
*
* @return string image URL
*/
protected function get_template_logo($type = null, $match = null)
{
$template_logo = null;
if ($logo = $this->config->get('skin_logo')) {
if (is_array($logo)) {
$template_names = array(
$this->skin_name . ':' . $this->template_name . '[' . $type . ']',
$this->skin_name . ':' . $this->template_name,
$this->skin_name . ':*[' . $type . ']',
$this->skin_name . ':[' . $type . ']',
$this->skin_name . ':*',
'*:' . $this->template_name . '[' . $type . ']',
'*:' . $this->template_name,
'*:*[' . $type . ']',
'*:[' . $type . ']',
$this->template_name . '[' . $type . ']',
$this->template_name,
'*[' . $type . ']',
'[' . $type . ']',
'*',
);
if (empty($type)) {
// If no type provided then remove those options from the list
$template_names = preg_grep("/\]$/", $template_names, PREG_GREP_INVERT);
}
elseif ($match === null) {
// Type specified with no special matching requirements so remove all none type specific options from the list
$template_names = preg_grep("/\]$/", $template_names);
}
if ($match == 'template') {
// Match only specific type or template name
$template_names = preg_grep("/\*$/", $template_names, PREG_GREP_INVERT);
}
foreach ($template_names as $key) {
if (isset($logo[$key])) {
$template_logo = $logo[$key];
break;
}
}
}
else {
$template_logo = $logo;
}
}
return $template_logo;
}
}
diff --git a/tests/ActionTestCase.php b/tests/ActionTestCase.php
index 5824523de..82faf8301 100644
--- a/tests/ActionTestCase.php
+++ b/tests/ActionTestCase.php
@@ -1,208 +1,214 @@
<?php
/**
* Test class to test rcmail_action_mail_index
*
* @package Tests
*/
class ActionTestCase extends PHPUnit\Framework\TestCase
{
static $files = [];
static function setUpBeforeClass()
{
$rcmail = rcmail::get_instance();
$rcmail->load_gui();
}
static function tearDownAfterClass()
{
foreach (self::$files as $file) {
unlink($file);
}
self::$files = [];
$rcmail = rcmail::get_instance();
$rcmail->shutdown();
}
public function setUp()
{
$_GET = [];
$_POST = [];
$_REQUEST = [];
}
/**
* Initialize the testing suite
*/
public static function init()
{
self::initSession();
self::initDB();
self::initUser();
self::initStorage();
}
/**
* Initialize "mocked" output class
*/
protected static function initOutput($mode, $task, $action, $framed = false)
{
$rcmail = rcmail::get_instance();
$rcmail->task = $task;
$rcmail->action = $action;
if ($mode == rcmail_action::MODE_AJAX) {
return $rcmail->output = new OutputJsonMock();
}
$rcmail->output = new OutputHtmlMock($task, $framed);
if ($framed) {
$rcmail->comm_path .= '&_framed=1';
$rcmail->output->set_env('framed', true);
}
$rcmail->output->set_env('task', $task);
$rcmail->output->set_env('action', $action);
$rcmail->output->set_env('comm_path', $rcmail->comm_path);
$rcmail->output->set_charset(RCUBE_CHARSET);
return $rcmail->output;
}
/**
* Wipe and re-initialize database
*/
public static function initDB($file = null)
{
$rcmail = rcmail::get_instance();
$dsn = rcube_db::parse_dsn($rcmail->config->get('db_dsnw'));
$db = $rcmail->get_dbh();
if ($file) {
self::loadSQLScript($db, $file);
return;
}
if ($dsn['phptype'] == 'mysql' || $dsn['phptype'] == 'mysqli') {
// drop all existing tables first
$db->query("SET FOREIGN_KEY_CHECKS=0");
$sql_res = $db->query("SHOW TABLES");
while ($sql_arr = $db->fetch_array($sql_res)) {
$table = reset($sql_arr);
$db->query("DROP TABLE $table");
}
// init database with schema
system(sprintf('cat %s %s | mysql -h %s -u %s --password=%s %s',
realpath(INSTALL_PATH . '/SQL/mysql.initial.sql'),
realpath(TESTS_DIR . 'src/sql/init.sql'),
escapeshellarg($dsn['hostspec']),
escapeshellarg($dsn['username']),
escapeshellarg($dsn['password']),
escapeshellarg($dsn['database'])
));
}
else if ($dsn['phptype'] == 'sqlite') {
$db->closeConnection();
// delete database file
system(sprintf('rm -f %s', escapeshellarg($dsn['database'])));
// load sample test data
self::loadSQLScript($db, 'init');
}
}
/**
* Set the $rcmail->user property
*/
public static function initUser()
{
$rcmail = rcmail::get_instance();
$rcmail->set_user(new rcube_user(1));
}
/**
* Set the $rcmail->session property
*/
public static function initSession()
{
$rcmail = rcmail::get_instance();
$rcmail->session = new rcube_session_php($rcmail->config);
}
/**
* Set the $rcmail->storage property
*/
public static function initStorage()
{
$rcmail = rcmail::get_instance();
$rcmail->storage = new StorageMock();
}
/**
* Create a temp file
*/
protected function createTempFile($content = '')
{
$file = rcube_utils::temp_filename('tests');
if ($content !== '') {
file_put_contents($file, $content);
}
self::$files[] = $file;
return $file;
}
/**
* Load an execute specified SQL script
*/
protected static function loadSQLScript($db, $name)
{
// load sample test data
// Note: exec_script() does not really work with these queries
$sql = file_get_contents(TESTS_DIR . "src/sql/{$name}.sql");
$sql = preg_split('/;\n/', $sql, -1, PREG_SPLIT_NO_EMPTY);
foreach ($sql as $query) {
$result = $db->query($query);
if ($db->is_error($result)) {
rcube::raise_error($db->is_error(), false, true);
}
}
}
/**
* Call the action's run() method and handle exit exception
*/
protected function runAndAssert($action, $expected_code)
{
// Reset output in case we execute the method multiple times in a single test
$rcmail = rcmail::get_instance();
$rcmail->output->reset(true);
+ // reset some static props
+ $class = new ReflectionClass('rcmail_action');
+ $property = $class->getProperty('edit_form');
+ $property->setAccessible(true);
+ $property->setValue($action, null);
+
try {
StderrMock::start();
$action->run();
StderrMock::stop();
}
catch (ExitException $e) {
$this->assertSame($expected_code, $e->getCode());
}
catch (Exception $e) {
if ($e->getMessage() == 'Error raised' && $expected_code == OutputHtmlMock::E_EXIT) {
return;
}
echo StderrMock::$output;
throw $e;
}
}
}
diff --git a/tests/Actions/Settings/Identities.php b/tests/Actions/Settings/Identities.php
index d49ee7153..3659226d9 100644
--- a/tests/Actions/Settings/Identities.php
+++ b/tests/Actions/Settings/Identities.php
@@ -1,19 +1,52 @@
<?php
/**
* Test class to test rcmail_action_settings_identities
*
* @package Tests
*/
class Actions_Settings_Identities extends ActionTestCase
{
/**
- * Class constructor
+ * Test run() method
*/
- function test_class()
+ function test_run()
{
- $object = new rcmail_action_settings_identities;
+ $action = new rcmail_action_settings_identities;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'identities');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ self::initDB('identities');
+
+ $this->runAndAssert($action, OutputHtmlMock::E_EXIT);
+
+ $result = $output->getOutput();
+
+ $this->assertSame('identities', $output->template);
+ $this->assertSame('Identities', $output->getProperty('pagetitle'));
+ $this->assertTrue(stripos($result, "<!DOCTYPE html>") === 0);
+ $this->assertTrue(strpos($result, "list.js") !== false);
+ $this->assertTrue(strpos($result, "test@example.org") !== false);
+ }
+
+ /**
+ * Test identities_list() method
+ */
+ function test_identities_list()
+ {
+ $action = new rcmail_action_settings_identities;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'identities');
+
+ self::initDB('identities');
+
+ $result = $action->identities_list([]);
+
+ $expected = '<table id="rcmIdentitiesList"><thead><tr><th class="mail">Mail</th></tr></thead>'
+ . '<tbody><tr id="rcmrow1"><td class="mail">test &lt;test@example.com&gt;</td></tr>'
+ . '<tr id="rcmrow2"><td class="mail">test &lt;test@example.org&gt;</td></tr></tbody></table>';
+
+ $this->assertSame($expected, $result);
}
}
diff --git a/tests/Actions/Settings/IdentityCreate.php b/tests/Actions/Settings/IdentityCreate.php
index 2e5af64d1..812d37110 100644
--- a/tests/Actions/Settings/IdentityCreate.php
+++ b/tests/Actions/Settings/IdentityCreate.php
@@ -1,19 +1,30 @@
<?php
/**
* Test class to test rcmail_action_settings_identity_create
*
* @package Tests
*/
class Actions_Settings_IdentityCreate extends ActionTestCase
{
/**
- * Class constructor
+ * Test run() method
*/
- function test_class()
+ function test_run()
{
- $object = new rcmail_action_settings_identity_create;
+ $action = new rcmail_action_settings_identity_create;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'add-identity');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ $this->runAndAssert($action, OutputHtmlMock::E_EXIT);
+
+ $result = $output->getOutput();
+
+ $this->assertSame('identityedit', $output->template);
+ $this->assertSame('Add identity', $output->getProperty('pagetitle'));
+ $this->assertTrue(stripos($result, "<!DOCTYPE html>") === 0);
+ $this->assertTrue(strpos($result, "rcmail.gui_object('editform', 'form')") !== false);
}
}
diff --git a/tests/Actions/Settings/IdentityDelete.php b/tests/Actions/Settings/IdentityDelete.php
index a4472826e..5fec7680e 100644
--- a/tests/Actions/Settings/IdentityDelete.php
+++ b/tests/Actions/Settings/IdentityDelete.php
@@ -1,19 +1,58 @@
<?php
/**
* Test class to test rcmail_action_settings_identity_delete
*
* @package Tests
*/
class Actions_Settings_IdentityDelete extends ActionTestCase
{
/**
- * Class constructor
+ * Test deleting an identity
*/
- function test_class()
+ function test_delete_identity()
{
- $object = new rcmail_action_settings_identity_delete;
+ $action = new rcmail_action_settings_identity_delete;
+ $output = $this->initOutput(rcmail_action::MODE_AJAX, 'settings', 'delete-identity');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ self::initDB('identities');
+
+ $db = rcmail::get_instance()->get_dbh();
+ $query = $db->query('SELECT * FROM `identities` WHERE `email` = ?', 'test@example.org');
+ $result = $db->fetch_assoc($query);
+ $iid = $result['identity_id'];
+
+ $_POST = ['_iid' => $iid];
+
+ $this->runAndAssert($action, OutputJsonMock::E_EXIT);
+
+ $result = $output->getOutput();
+
+ $this->assertSame(['Content-Type: application/json; charset=UTF-8'], $output->headers);
+ $this->assertSame('delete-identity', $result['action']);
+ $this->assertTrue(strpos($result['exec'], 'this.display_message("Successfully deleted.","confirmation",0);') !== false);
+ $this->assertTrue(strpos($result['exec'], 'this.remove_identity("' . $iid . '")') !== false);
+
+ $query = $db->query('SELECT * FROM `identities` WHERE `identity_id` = ?', $iid);
+ $result = $db->fetch_assoc($query);
+
+ $this->assertTrue(!empty($result['del']));
+
+ // Test error handling
+ $action = new rcmail_action_settings_identity_delete;
+ $output = $this->initOutput(rcmail_action::MODE_AJAX, 'settings', 'delete-identity');
+
+ $_POST = ['_iid' => 'unknown'];
+
+ $this->runAndAssert($action, OutputJsonMock::E_EXIT);
+
+ $result = $output->getOutput();
+
+ $this->assertSame(['Content-Type: application/json; charset=UTF-8'], $output->headers);
+ $this->assertSame('delete-identity', $result['action']);
+ $this->assertTrue(strpos($result['exec'], 'this.display_message("An error occurred while saving.","error",0);') !== false);
}
}
diff --git a/tests/Actions/Settings/IdentityEdit.php b/tests/Actions/Settings/IdentityEdit.php
index 98008da79..aca0e8dfc 100644
--- a/tests/Actions/Settings/IdentityEdit.php
+++ b/tests/Actions/Settings/IdentityEdit.php
@@ -1,19 +1,58 @@
<?php
/**
* Test class to test rcmail_action_settings_identity_edit
*
* @package Tests
*/
class Actions_Settings_IdentityEdit extends ActionTestCase
{
/**
- * Class constructor
+ * Test run() method
*/
- function test_class()
+ function test_run()
{
- $object = new rcmail_action_settings_identity_edit;
+ $action = new rcmail_action_settings_identity_edit;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'edit-identity');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ self::initDB('identities');
+
+ $db = rcmail::get_instance()->get_dbh();
+ $query = $db->query('SELECT * FROM `identities` WHERE `standard` = 1 LIMIT 1');
+ $identity = $db->fetch_assoc($query);
+
+ $_GET = ['_iid' => $identity['identity_id']];
+
+ $this->runAndAssert($action, OutputHtmlMock::E_EXIT);
+
+ $result = $output->getOutput();
+
+ $this->assertSame('identityedit', $output->template);
+ $this->assertSame('Edit identity', $output->getProperty('pagetitle'));
+ $this->assertSame($identity['identity_id'], $output->get_env('iid'));
+ $this->assertTrue(stripos($result, "<!DOCTYPE html>") === 0);
+ $this->assertTrue(strpos($result, "rcmail.gui_object('editform', 'form')") !== false);
+ $this->assertTrue(strpos($result, "test@example.com") !== false);
+
+ // TODO: Test error handling
+ }
+
+ /**
+ * Test identity_form() method
+ */
+ function test_identity_form()
+ {
+ $action = new rcmail_action_settings_identity_edit;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'edit-identity');
+
+ self::initDB('identities');
+
+ $result = $action->identity_form([]);
+
+ $this->assertTrue(strpos($result, '<form id="identityImageUpload"') !== false);
+ $this->assertTrue(strpos($result, '<legend>Settings</legend>') !== false);
}
}
diff --git a/tests/Actions/Settings/IdentitySave.php b/tests/Actions/Settings/IdentitySave.php
index 7c4d23f92..5eed67de9 100644
--- a/tests/Actions/Settings/IdentitySave.php
+++ b/tests/Actions/Settings/IdentitySave.php
@@ -1,19 +1,90 @@
<?php
/**
* Test class to test rcmail_action_settings_identity_save
*
* @package Tests
*/
class Actions_Settings_IdentitySave extends ActionTestCase
{
/**
- * Class constructor
+ * Test run() method
*/
- function test_class()
+ function test_identity_edit()
{
- $object = new rcmail_action_settings_identity_save;
+ $action = new rcmail_action_settings_identity_save;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'save-identity');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ self::initDB('identities');
+
+ $db = rcmail::get_instance()->get_dbh();
+ $query = $db->query('SELECT * FROM `identities` WHERE `standard` = 1 LIMIT 1');
+ $identity = $db->fetch_assoc($query);
+
+ // Test successful identity update
+
+ $_POST = [
+ '_iid' => $identity['identity_id'],
+ '_name' => 'new-name',
+ '_email' => 'new@example.com',
+ '_standard' => 1,
+ '_signature' => 'test',
+ ];
+
+ $action->run();
+
+ $result = $output->getOutput();
+
+ $this->assertSame('edit-identity', rcmail::get_instance()->action);
+ $this->assertSame('successfullysaved', $output->getProperty('message'));
+
+ $query = $db->query('SELECT * FROM `identities` WHERE `identity_id` = ?', $identity['identity_id']);
+ $identity = $db->fetch_assoc($query);
+
+ $this->assertSame('new-name', $identity['name']);
+ $this->assertSame('new@example.com', $identity['email']);
+ $this->assertSame('test', $identity['signature']);
+ $this->assertSame(1, (int) $identity['standard']);
+ }
+
+ /**
+ * Test run() method for a new identity
+ */
+ function test_new_identity()
+ {
+ $this->markTestIncomplete();
+ }
+
+ /**
+ * Test run() method errors handling
+ */
+ function test_run_errors()
+ {
+ $this->markTestIncomplete();
+ }
+
+ /**
+ * Test attach_images() method
+ */
+ function test_attach_images()
+ {
+ $result = rcmail_action_settings_identity_save::attach_images('<p>test</p>');
+
+ // TODO: test image replacement
+
+ $this->assertSame('<p>test</p>', $result);
+ }
+
+ /**
+ * Test wash_html() method
+ */
+ function test_wash_html()
+ {
+ $result = rcmail_action_settings_identity_save::wash_html('<p>test</p>');
+
+ $this->assertSame('<p>test</p>', $result);
}
}
diff --git a/tests/Actions/Settings/Index.php b/tests/Actions/Settings/Index.php
index 43230c093..2f51f5afd 100644
--- a/tests/Actions/Settings/Index.php
+++ b/tests/Actions/Settings/Index.php
@@ -1,19 +1,82 @@
<?php
/**
* Test class to test rcmail_action_settings_index
*
* @package Tests
*/
class Actions_Settings_Index extends ActionTestCase
{
/**
- * Class constructor
+ * Test run() method
*/
- function test_class()
+ function test_run()
{
- $object = new rcmail_action_settings_index;
+ $action = new rcmail_action_settings_index;
+ $output = $this->initOutput(rcmail_action::MODE_HTTP, 'settings', 'preferences');
- $this->assertInstanceOf('rcmail_action', $object);
+ $this->assertInstanceOf('rcmail_action', $action);
+ $this->assertTrue($action->checks());
+
+ $action->run();
+
+ $result = $output->getOutput();
+
+ $this->assertSame('Preferences', $output->getProperty('pagetitle'));
+ }
+
+ /**
+ * Test sections_list() method
+ */
+ function test_sections_list()
+ {
+ $result = rcmail_action_settings_index::sections_list([]);
+ $this->assertTrue(strpos($result, '<table id="rcmsectionslist">') === 0);
+ }
+
+ /**
+ * Test user_prefs() method
+ */
+ function test_user_prefs()
+ {
+ $result = rcmail_action_settings_index::user_prefs('general');
+ $this->assertSame('general', $result[0]['general']['id']);
+ }
+
+ /**
+ * Test get_skins() method
+ */
+ function test_get_skins()
+ {
+ $result = rcmail_action_settings_index::get_skins();
+ sort($result);
+ $this->assertSame(['classic', 'elastic', 'larry'], $result);
+ }
+
+ /**
+ * Test settings_tabs() method
+ */
+ function test_settings_tabs()
+ {
+ $result = rcmail_action_settings_index::settings_tabs([]);
+ $this->assertTrue(strpos($result, '<span id="settingstabpreferences" class="preferences selected"><a title="Edit user preferences" href="./?_task=settings&amp;_action=preferences"') === 0);
+ }
+
+ /**
+ * Test timezone_label() method
+ */
+ function test_timezone_label()
+ {
+ $result = rcmail_action_settings_index::timezone_label('Europe/Warsaw');
+ $this->assertSame('Europe/Warsaw', $result);
+ }
+
+ /**
+ * Test timezone_standard_time_label() method
+ */
+ function test_timezone_standard_time_data()
+ {
+ $result = rcmail_action_settings_index::timezone_standard_time_data('UTC');
+ $this->assertSame('+00:00', $result['offset']);
}
}
diff --git a/tests/src/sql/identities.sql b/tests/src/sql/identities.sql
new file mode 100644
index 000000000..bd99f2b41
--- /dev/null
+++ b/tests/src/sql/identities.sql
@@ -0,0 +1,3 @@
+DELETE FROM identities;
+INSERT INTO identities (user_id, name, standard, email) VALUES (1, 'test', '1', 'test@example.com');
+INSERT INTO identities (user_id, name, standard, email, signature) VALUES (1, 'test', '0', 'test@example.org', 'sig');

File Metadata

Mime Type
text/x-diff
Expires
Sun, Apr 19, 9:18 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
435984
Default Alt Text
(269 KB)

Event Timeline