Import Ruty
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
<?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: |
|
||||
| Copy a contact record from one directory to another |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_copy extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
$cids = self::get_cids();
|
||||
$target = rcube_utils::get_input_string('_to', rcube_utils::INPUT_POST);
|
||||
$target_group = rcube_utils::get_input_string('_togid', rcube_utils::INPUT_POST);
|
||||
|
||||
$success = 0;
|
||||
$errormsg = 'copyerror';
|
||||
$maxnum = $rcmail->config->get('max_group_members', 0);
|
||||
|
||||
foreach ($cids as $source => $cid) {
|
||||
// Something wrong, target not specified
|
||||
if (!strlen($target)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// It might happen when copying records from search result
|
||||
// Do nothing, go to next source
|
||||
if ((string) $target == (string) $source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$CONTACTS = $rcmail->get_address_book($source);
|
||||
$TARGET = $rcmail->get_address_book($target);
|
||||
|
||||
if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
|
||||
break;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
|
||||
foreach ($cid as $cid) {
|
||||
$a_record = $CONTACTS->get_record($cid, true);
|
||||
|
||||
// avoid copying groups
|
||||
if (isset($a_record['_type']) && $a_record['_type'] == 'group') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if contact exists, if so, we'll need it's ID
|
||||
// Note: Some addressbooks allows empty email address field
|
||||
// @TODO: should we check all email addresses?
|
||||
$email = $CONTACTS->get_col_values('email', $a_record, true);
|
||||
if (!empty($email)) {
|
||||
$result = $TARGET->search('email', $email[0], 1, true, true);
|
||||
}
|
||||
else if (!empty($a_record['name'])) {
|
||||
$result = $TARGET->search('name', $a_record['name'], 1, true, true);
|
||||
}
|
||||
else {
|
||||
$result = new rcube_result_set();
|
||||
}
|
||||
|
||||
// insert contact record
|
||||
if (!$result->count) {
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_create', [
|
||||
'record' => $a_record,
|
||||
'source' => $target,
|
||||
'group' => $target_group
|
||||
]);
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
if ($insert_id = $TARGET->insert($plugin['record'], false)) {
|
||||
$ids[] = $insert_id;
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
else if ($plugin['result']) {
|
||||
$ids = array_merge($ids, $plugin['result']);
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$record = $result->first();
|
||||
$ids[] = $record['ID'];
|
||||
$errormsg = empty($email) ? 'contactnameexists' : 'contactexists';
|
||||
}
|
||||
}
|
||||
|
||||
// assign to group
|
||||
if ($target_group && $TARGET->groups && !empty($ids)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
|
||||
'group_id' => $target_group,
|
||||
'ids' => $ids,
|
||||
'source' => $target
|
||||
]);
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
$TARGET->reset();
|
||||
$TARGET->set_group($target_group);
|
||||
|
||||
if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
|
||||
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success) {
|
||||
$success = $cnt;
|
||||
}
|
||||
}
|
||||
else if (!empty($plugin['result'])) {
|
||||
$success = $plugin['result'];
|
||||
}
|
||||
|
||||
$errormsg = !empty($plugin['message']) ? $plugin['message'] : 'copyerror';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
$rcmail->output->show_message($errormsg, 'error');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('copysuccess', 'confirmation', ['nr' => $success]);
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?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: |
|
||||
| Delete the submitted contacts (CIDs) from the users address book |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_delete extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$cids = self::get_cids(null, rcube_utils::INPUT_POST);
|
||||
$delcnt = 0;
|
||||
|
||||
// remove previous deletes
|
||||
$undo_time = $rcmail->config->get('undo_timeout', 0);
|
||||
$rcmail->session->remove('contact_undo');
|
||||
|
||||
foreach ($cids as $source => $cid) {
|
||||
$CONTACTS = self::contact_source($source);
|
||||
|
||||
if ($CONTACTS->readonly && empty($CONTACTS->deletable)) {
|
||||
// more sources? do nothing, probably we have search results from
|
||||
// more than one source, some of these sources can be readonly
|
||||
if (count($cids) == 1) {
|
||||
$rcmail->output->show_message('contactdelerror', 'error');
|
||||
$rcmail->output->command('list_contacts');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_delete', [
|
||||
'id' => $cid,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
$deleted = !$plugin['abort'] ? $CONTACTS->delete($cid, $undo_time < 1) : $plugin['result'];
|
||||
|
||||
if (!$deleted) {
|
||||
if (!empty($plugin['message'])) {
|
||||
$error = $plugin['message'];
|
||||
}
|
||||
else if (($error = $CONTACTS->get_error()) && !empty($error['message'])) {
|
||||
$error = $error['message'];
|
||||
}
|
||||
else {
|
||||
$error = 'contactdelerror';
|
||||
}
|
||||
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GP);
|
||||
$group = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP);
|
||||
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
$rcmail->output->command('list_contacts', $source, $group);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else {
|
||||
$delcnt += $deleted;
|
||||
|
||||
// store deleted contacts IDs in session for undo action
|
||||
if ($undo_time > 0 && $CONTACTS->undelete) {
|
||||
$_SESSION['contact_undo']['data'][$source] = $cid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['contact_undo'])) {
|
||||
$_SESSION['contact_undo']['ts'] = time();
|
||||
$msg = html::span(null, $rcmail->gettext('contactdeleted'))
|
||||
. ' ' . html::a(
|
||||
['onclick' => rcmail_output::JS_OBJECT_NAME.".command('undo', '', this)"],
|
||||
$rcmail->gettext('undo')
|
||||
);
|
||||
|
||||
$rcmail->output->show_message($msg, 'confirmation', null, true, $undo_time);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('contactdeleted', 'confirmation');
|
||||
}
|
||||
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$page = $_SESSION['page'] ?? 1;
|
||||
|
||||
// update saved search after data changed
|
||||
if (($records = self::search_update(true)) !== false) {
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$first = ($page-1) * $page_size;
|
||||
$result = new rcube_result_set($count, $first);
|
||||
$pages = ceil((count($records) + $delcnt) / $page_size);
|
||||
|
||||
// last page and it's empty, display previous one
|
||||
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
|
||||
$rcmail->output->command('list_page', 'prev');
|
||||
$rowcount = $rcmail->gettext('loading');
|
||||
}
|
||||
// get records from the next page to add to the list
|
||||
else if ($pages > 1 && $page < $pages) {
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
$first += $page_size;
|
||||
// create resultset object
|
||||
$res = new rcube_result_set($count, $first - $delcnt);
|
||||
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $first - $delcnt, $delcnt);
|
||||
}
|
||||
|
||||
$res->records = array_values($records);
|
||||
$records = $res;
|
||||
}
|
||||
else {
|
||||
unset($records);
|
||||
}
|
||||
}
|
||||
else if (isset($CONTACTS)) {
|
||||
// count contacts for this user
|
||||
$result = $CONTACTS->count();
|
||||
$pages = ceil(($result->count + $delcnt) / $page_size);
|
||||
|
||||
// last page and it's empty, display previous one
|
||||
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
|
||||
$rcmail->output->command('list_page', 'prev');
|
||||
$rowcount = $rcmail->gettext('loading');
|
||||
}
|
||||
// get records from the next page to add to the list
|
||||
else if ($pages > 1 && $page < $pages) {
|
||||
$CONTACTS->set_page($page);
|
||||
$records = $CONTACTS->list_records(null, -$delcnt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($rowcount)) {
|
||||
$rowcount = isset($result) ? self::get_rowcount_text($result) : '';
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 0);
|
||||
$rcmail->output->command('set_rowcount', $rowcount);
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if (!empty($records)) {
|
||||
self::js_contacts_list($records);
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
<?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 a contact entry |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_edit extends rcmail_action_contacts_index
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($rcmail->action == 'edit') {
|
||||
// Get contact ID and source ID from request
|
||||
$cids = self::get_cids();
|
||||
$source = key($cids);
|
||||
$cid = array_first($cids[$source]);
|
||||
|
||||
// Initialize addressbook
|
||||
$CONTACTS = self::contact_source($source, true);
|
||||
|
||||
// Contact edit
|
||||
if ($cid && (self::$contact = $CONTACTS->get_record($cid, true))) {
|
||||
$rcmail->output->set_env('cid', self::$contact['ID']);
|
||||
}
|
||||
|
||||
// editing not allowed here
|
||||
if ($CONTACTS->readonly || !empty(self::$contact['readonly'])) {
|
||||
$rcmail->output->show_message('sourceisreadonly');
|
||||
$rcmail->overwrite_action('show');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty(self::$contact)) {
|
||||
$rcmail->output->show_message('contactnotfound', 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
|
||||
if (strlen($source)) {
|
||||
$CONTACTS = $rcmail->get_address_book($source, true);
|
||||
}
|
||||
|
||||
if (empty($CONTACTS) || $CONTACTS->readonly) {
|
||||
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
|
||||
$source = $rcmail->get_address_book_id($CONTACTS);
|
||||
}
|
||||
|
||||
// Initialize addressbook
|
||||
$CONTACTS = self::contact_source($source, true);
|
||||
}
|
||||
|
||||
self::$SOURCE_ID = $source;
|
||||
self::$CONTACTS = $CONTACTS;
|
||||
self::set_sourcename($CONTACTS);
|
||||
|
||||
// check if we have a valid result
|
||||
if (!empty($args['contact'])) {
|
||||
self::$contact = $args['contact'];
|
||||
}
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'contactedithead' => [$this, 'contact_edithead'],
|
||||
'contacteditform' => [$this, 'contact_editform'],
|
||||
'contactphoto' => [$this, 'contact_photo'],
|
||||
'photouploadform' => [$this, 'upload_photo_form'],
|
||||
'sourceselector' => [$this, 'source_selector'],
|
||||
'filedroparea' => [$this, 'photo_drop_area'],
|
||||
]);
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext(($rcmail->action == 'add' ? 'addcontact' : 'editcontact')));
|
||||
|
||||
if ($rcmail->action == 'add' && $rcmail->output->template_exists('contactadd')) {
|
||||
$rcmail->output->send('contactadd');
|
||||
}
|
||||
|
||||
// this will be executed if no template for addcontact exists
|
||||
$rcmail->output->send('contactedit');
|
||||
}
|
||||
|
||||
public static function contact_edithead($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$business_mode = $rcmail->config->get('contact_form_mode') === 'business';
|
||||
|
||||
// check if we have a valid result
|
||||
$i_size = !empty($attrib['size']) ? $attrib['size'] : 20;
|
||||
|
||||
$form = [
|
||||
'head' => [
|
||||
'name' => $rcmail->gettext('contactnameandorg'),
|
||||
'content' => [
|
||||
'source' => ['id' => '_source', 'label' => $rcmail->gettext('addressbook')],
|
||||
'prefix' => ['size' => $i_size],
|
||||
'firstname' => ['size' => $i_size, 'visible' => true],
|
||||
'middlename' => ['size' => $i_size],
|
||||
'surname' => ['size' => $i_size, 'visible' => true],
|
||||
'suffix' => ['size' => $i_size],
|
||||
'name' => ['size' => $i_size * 2],
|
||||
'nickname' => ['size' => $i_size * 2],
|
||||
'organization' => ['size' => $i_size * 2, 'visible' => $business_mode],
|
||||
'department' => ['size' => $i_size * 2, 'visible' => $business_mode],
|
||||
'jobtitle' => ['size' => $i_size * 2, 'visible' => $business_mode],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
list($form_start, $form_end) = self::get_form_tags($attrib);
|
||||
unset($attrib['form'], $attrib['name'], $attrib['size']);
|
||||
|
||||
// return the address edit form
|
||||
$out = self::contact_form($form, self::$contact, $attrib);
|
||||
|
||||
return $form_start . $out . $form_end;
|
||||
}
|
||||
|
||||
public static function contact_editform($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$addr_tpl = $rcmail->config->get('address_template', '');
|
||||
|
||||
// copy (parsed) address template to client
|
||||
if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $addr_tpl, $templ, PREG_SET_ORDER)) {
|
||||
$rcmail->output->set_env('address_template', $templ);
|
||||
}
|
||||
|
||||
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
|
||||
$t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10;
|
||||
$t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
|
||||
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
|
||||
|
||||
$form = [
|
||||
'contact' => [
|
||||
'name' => $rcmail->gettext('properties'),
|
||||
'content' => [
|
||||
'email' => ['size' => $i_size, 'maxlength' => 254, 'visible' => true],
|
||||
'phone' => ['size' => $i_size, 'visible' => true],
|
||||
'address' => ['visible' => true],
|
||||
'website' => ['size' => $i_size],
|
||||
'im' => ['size' => $i_size],
|
||||
],
|
||||
],
|
||||
'personal' => [
|
||||
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
|
||||
'content' => [
|
||||
'gender' => ['visible' => true],
|
||||
'maidenname' => ['size' => $i_size],
|
||||
'birthday' => ['visible' => true],
|
||||
'anniversary' => [],
|
||||
'manager' => ['size' => $i_size],
|
||||
'assistant' => ['size' => $i_size],
|
||||
'spouse' => ['size' => $i_size],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (isset(self::$CONTACT_COLTYPES['notes'])) {
|
||||
$form['notes'] = [
|
||||
'name' => $rcmail->gettext('notes'),
|
||||
'single' => true,
|
||||
'content' => [
|
||||
'notes' => ['size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
list($form_start, $form_end) = self::get_form_tags($attrib);
|
||||
unset($attrib['form']);
|
||||
|
||||
// return the complete address edit form as table
|
||||
$out = self::contact_form($form, self::$contact, $attrib);
|
||||
|
||||
return $form_start . $out . $form_end;
|
||||
}
|
||||
|
||||
public static function upload_photo_form($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$hidden = new html_hiddenfield(['name' => '_cid', 'value' => $rcmail->output->get_env('cid')]);
|
||||
|
||||
$attrib['prefix'] = $hidden->show();
|
||||
$input_attr = ['name' => '_photo', 'accept' => 'image/*'];
|
||||
|
||||
$rcmail->output->add_label('addphoto','replacephoto');
|
||||
|
||||
return self::upload_form($attrib, 'uploadform', 'upload-photo', $input_attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* similar function as in /steps/settings/edit_identity.inc
|
||||
* @todo: Use rcmail_action::get_form_tags()
|
||||
*/
|
||||
public static function get_form_tags($attrib, $action = null, $id = null, $hidden = null)
|
||||
{
|
||||
static $edit_form;
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$form_start = $form_end = '';
|
||||
|
||||
if (empty($edit_form)) {
|
||||
$hiddenfields = new html_hiddenfield();
|
||||
|
||||
if ($rcmail->action == 'edit') {
|
||||
$hiddenfields->add(['name' => '_source', 'value' => self::$SOURCE_ID]);
|
||||
}
|
||||
|
||||
$hiddenfields->add(['name' => '_gid', 'value' => self::$CONTACTS->group_id]);
|
||||
$hiddenfields->add(['name' => '_search', 'value' => rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC)]);
|
||||
|
||||
if ($cid = $rcmail->output->get_env('cid')) {
|
||||
$hiddenfields->add(['name' => '_cid', 'value' => $cid]);
|
||||
}
|
||||
|
||||
$form_attrib = [
|
||||
'name' => 'form',
|
||||
'method' => 'post',
|
||||
'task' => $rcmail->task,
|
||||
'action' => 'save',
|
||||
'request' => 'save.' . intval($cid),
|
||||
'noclose' => true,
|
||||
];
|
||||
|
||||
$form_start = $rcmail->output->request_form($form_attrib + $attrib, $hiddenfields->show());
|
||||
$form_end = empty($attrib['form']) ? '</form>' : '';
|
||||
$edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form';
|
||||
|
||||
$rcmail->output->add_gui_object('editform', $edit_form);
|
||||
}
|
||||
|
||||
return [$form_start, $form_end];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register container as active area to drop photos onto
|
||||
*/
|
||||
public static function photo_drop_area($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (!empty($attrib['id'])) {
|
||||
$rcmail->output->add_gui_object('filedrop', $attrib['id']);
|
||||
$rcmail->output->set_env('filedrop', [
|
||||
'action' => 'upload-photo',
|
||||
'fieldname' => '_photo',
|
||||
'single' => 1,
|
||||
'filter' => '^image/.+'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?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: |
|
||||
| Export the selected address book as vCard file |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_export extends rcmail_action_contacts_index
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$rcmail->request_security_check(rcube_utils::INPUT_GET);
|
||||
|
||||
$sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
|
||||
// Use search result
|
||||
if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) {
|
||||
$search = (array) $_SESSION['contact_search'][$_REQUEST['_search']];
|
||||
$records = [];
|
||||
|
||||
// Get records from all sources
|
||||
foreach ($search as $s => $set) {
|
||||
$source = $rcmail->get_address_book($s);
|
||||
|
||||
// reset page
|
||||
$source->set_page(1);
|
||||
$source->set_pagesize(99999);
|
||||
$source->set_search_set($set);
|
||||
|
||||
// get records
|
||||
$result = $source->list_records();
|
||||
|
||||
while ($record = $result->next()) {
|
||||
// because vcard_map is per-source we need to create vcard here
|
||||
self::prepare_for_export($record, $source);
|
||||
|
||||
$record['sourceid'] = $s;
|
||||
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
|
||||
$records[$key] = $record;
|
||||
}
|
||||
|
||||
unset($result);
|
||||
}
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$result = new rcube_result_set($count);
|
||||
$result->records = array_values($records);
|
||||
}
|
||||
// selected contacts
|
||||
else if (!empty($_REQUEST['_cid'])) {
|
||||
$records = [];
|
||||
|
||||
// Selected contact IDs (with multi-source support)
|
||||
$cids = self::get_cids();
|
||||
|
||||
foreach ($cids as $s => $ids) {
|
||||
$source = $rcmail->get_address_book($s);
|
||||
|
||||
// reset page and page size (#6103)
|
||||
$source->set_page(1);
|
||||
$source->set_pagesize(count($ids));
|
||||
|
||||
$result = $source->search('ID', $ids, 1, true, true);
|
||||
|
||||
while ($record = $result->next()) {
|
||||
// because vcard_map is per-source we need to create vcard here
|
||||
self::prepare_for_export($record, $source);
|
||||
|
||||
$record['sourceid'] = $s;
|
||||
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
|
||||
$records[$key] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$result = new rcube_result_set($count);
|
||||
$result->records = array_values($records);
|
||||
}
|
||||
// selected directory/group
|
||||
else {
|
||||
$CONTACTS = self::contact_source(null, true);
|
||||
|
||||
// get contacts for this user
|
||||
$CONTACTS->set_page(1);
|
||||
$CONTACTS->set_pagesize(99999);
|
||||
$result = $CONTACTS->list_records(null, 0, true);
|
||||
}
|
||||
|
||||
// Give plugins a possibility to implement other output formats or modify the result
|
||||
$plugin = $rcmail->plugins->exec_hook('addressbook_export', ['result' => $result]);
|
||||
$result = $plugin['result'];
|
||||
|
||||
if ($plugin['abort']) {
|
||||
$rcmail->output->sendExit();
|
||||
}
|
||||
|
||||
// send download headers
|
||||
$rcmail->output->header('Content-Type: text/vcard; charset=' . RCUBE_CHARSET);
|
||||
$rcmail->output->header('Content-Disposition: attachment; filename="contacts.vcf"');
|
||||
|
||||
while ($result && ($row = $result->next())) {
|
||||
if (!empty($CONTACTS)) {
|
||||
self::prepare_for_export($row, $CONTACTS);
|
||||
}
|
||||
|
||||
// fix folding and end-of-line chars
|
||||
$row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']);
|
||||
$row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']);
|
||||
|
||||
echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol;
|
||||
}
|
||||
|
||||
$rcmail->output->sendExit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy contact record properties into a vcard object
|
||||
*/
|
||||
public static function prepare_for_export(&$record, $source = null)
|
||||
{
|
||||
$groups = $source && $source->groups && $source->export_groups ? $source->get_record_groups($record['ID']) : null;
|
||||
$fieldmap = $source ? $source->vcard_map : null;
|
||||
|
||||
if (empty($record['vcard'])) {
|
||||
$vcard = new rcube_vcard(null, RCUBE_CHARSET, false, $fieldmap);
|
||||
$vcard->reset();
|
||||
|
||||
foreach ($record as $key => $values) {
|
||||
list($field, $section) = rcube_utils::explode(':', $key);
|
||||
// avoid unwanted casting of DateTime objects to an array
|
||||
// (same as in rcube_contacts::convert_save_data())
|
||||
if (is_object($values) && is_a($values, 'DateTime')) {
|
||||
$values = [$values];
|
||||
}
|
||||
|
||||
foreach ((array) $values as $value) {
|
||||
if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) {
|
||||
$vcard->set($field, $value, $section ? strtoupper($section) : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append group names
|
||||
if ($groups) {
|
||||
$vcard->set('groups', join(',', $groups), null);
|
||||
}
|
||||
|
||||
$record['vcard'] = $vcard->export();
|
||||
}
|
||||
// patch categories to already existing vcard block
|
||||
else {
|
||||
$vcard = new rcube_vcard($record['vcard'], RCUBE_CHARSET, false, $fieldmap);
|
||||
|
||||
// unset CATEGORIES entry, it might be not up-to-date (#1490277)
|
||||
$vcard->set('groups', null);
|
||||
$record['vcard'] = $vcard->export();
|
||||
|
||||
if (!empty($groups)) {
|
||||
$vgroups = 'CATEGORIES:' . rcube_vcard::vcard_quote($groups, ',') . rcube_vcard::$eol;
|
||||
$record['vcard'] = str_replace('END:VCARD', $vgroups . 'END:VCARD', $record['vcard']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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: |
|
||||
| Handle adding members to a contact group |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_group_addmembers extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
if ($contacts->readonly || !$contacts->groups) {
|
||||
$rcmail->output->show_message('sourceisreadonly', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST);
|
||||
$ids = self::get_cids($source);
|
||||
$result = false;
|
||||
|
||||
if ($gid && $ids) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
|
||||
'group_id' => $gid,
|
||||
'ids' => $ids,
|
||||
'source' => $source,
|
||||
]);
|
||||
|
||||
$contacts->set_group($gid);
|
||||
$num2add = count($plugin['ids']);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
if (
|
||||
($maxnum = $rcmail->config->get('max_group_members'))
|
||||
&& ($contacts->count()->count + $num2add > $maxnum)
|
||||
) {
|
||||
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$result = $contacts->add_to_group($gid, $plugin['ids']);
|
||||
}
|
||||
else {
|
||||
$result = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$rcmail->output->show_message('contactaddedtogroup', 'confirmation');
|
||||
}
|
||||
else if (!empty($plugin['abort']) || $contacts->get_error()) {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
else {
|
||||
$message = !empty($plugin['message']) ? $plugin['message'] : 'nogroupassignmentschanged';
|
||||
$rcmail->output->show_message($message);
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 contact groups creation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_group_create extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
if ($contacts->readonly || !$contacts->groups) {
|
||||
$rcmail->output->show_message('sourceisreadonly', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true))) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_create', [
|
||||
'name' => $name,
|
||||
'source' => $source,
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$created = $contacts->create_group($plugin['name']);
|
||||
}
|
||||
else {
|
||||
$created = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($created)) {
|
||||
$rcmail->output->show_message('groupcreated', 'confirmation');
|
||||
$rcmail->output->command('insert_contact_group', ['source' => $source] + $created);
|
||||
}
|
||||
else if (empty($created)) {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 contact group delete action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_group_delete extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
if ($contacts->readonly || !$contacts->groups) {
|
||||
$rcmail->output->show_message('sourceisreadonly', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if ($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_delete', [
|
||||
'group_id' => $gid,
|
||||
'source' => $source,
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$deleted = $contacts->delete_group($gid);
|
||||
}
|
||||
else {
|
||||
$deleted = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($deleted)) {
|
||||
$rcmail->output->show_message('groupdeleted', 'confirmation');
|
||||
$rcmail->output->command('remove_group_item', ['source' => $source, 'id' => $gid]);
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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: |
|
||||
| Removing members from a contact group |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_group_delmembers extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
if ($contacts->readonly || !$contacts->groups) {
|
||||
$rcmail->output->show_message('sourceisreadonly', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST);
|
||||
$ids = self::get_cids($source);
|
||||
|
||||
if ($gid && $ids) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_delmembers', [
|
||||
'group_id' => $gid,
|
||||
'ids' => $ids,
|
||||
'source' => $source,
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$result = $contacts->remove_from_group($gid, $plugin['ids']);
|
||||
}
|
||||
else {
|
||||
$result = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result)) {
|
||||
$rcmail->output->show_message('contactremovedfromgroup', 'confirmation');
|
||||
$rcmail->output->command('remove_group_contacts', ['source' => $source, 'gid' => $gid]);
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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 contact groups rename action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_group_rename extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
if ($contacts->readonly || !$contacts->groups) {
|
||||
$rcmail->output->show_message('sourceisreadonly', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if (
|
||||
($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST))
|
||||
&& ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true)))
|
||||
) {
|
||||
$newgid = null;
|
||||
$plugin = $rcmail->plugins->exec_hook('group_rename', [
|
||||
'group_id' => $gid,
|
||||
'name' => $name,
|
||||
'source' => $source,
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$newname = $contacts->rename_group($gid, $plugin['name'], $newgid);
|
||||
}
|
||||
else {
|
||||
$newname = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($newname)) {
|
||||
$rcmail->output->show_message('grouprenamed', 'confirmation');
|
||||
$rcmail->output->command('update_contact_group', [
|
||||
'source' => $source,
|
||||
'id' => $gid,
|
||||
'name' => $newname,
|
||||
'newid' => $newgid ?? null
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
<?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: |
|
||||
| Import contacts from a vCard or CSV file |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_import extends rcmail_action_contacts_index
|
||||
{
|
||||
const UPLOAD_ERR_CSV_FIELDS = 101;
|
||||
|
||||
protected static $stats;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$importstep = 'import_form';
|
||||
$has_map = isset($_POST['_map']) && is_array($_POST['_map']);
|
||||
|
||||
if ($has_map || (isset($_FILES['_file']) && is_array($_FILES['_file']))) {
|
||||
$replace = (bool) rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC);
|
||||
$target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC);
|
||||
$with_groups = (int) rcube_utils::get_input_string('_groups', rcube_utils::INPUT_GPC);
|
||||
|
||||
// reload params for CSV field mapping screen
|
||||
if ($has_map && !empty($_SESSION['contactcsvimport']['params'])) {
|
||||
$params = $_SESSION['contactcsvimport']['params'];
|
||||
|
||||
$replace = $params['replace'];
|
||||
$target = $params['target'];
|
||||
$with_groups = $params['with_groups'];
|
||||
}
|
||||
|
||||
$vcards = [];
|
||||
$csvs = [];
|
||||
$map = [];
|
||||
$upload_error = null;
|
||||
|
||||
$CONTACTS = $rcmail->get_address_book($target, true);
|
||||
|
||||
if (!$CONTACTS->groups) {
|
||||
$with_groups = false;
|
||||
}
|
||||
|
||||
if ($CONTACTS->readonly) {
|
||||
$rcmail->output->show_message('addresswriterror', 'error');
|
||||
}
|
||||
else {
|
||||
$filepaths = [];
|
||||
if ($has_map) {
|
||||
$filepaths = $_SESSION['contactcsvimport']['files'];
|
||||
}
|
||||
else {
|
||||
foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded file if there is no error
|
||||
$err = $_FILES['_file']['error'][$i];
|
||||
|
||||
if ($err) {
|
||||
$upload_error = $err;
|
||||
}
|
||||
else {
|
||||
$filepaths[] = $filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($filepaths as $filepath) {
|
||||
$file_content = file_get_contents($filepath);
|
||||
|
||||
// let rcube_vcard do the hard work :-)
|
||||
$vcard_o = new rcube_vcard();
|
||||
$vcard_o->extend_fieldmap($CONTACTS->vcard_map);
|
||||
$v_list = $vcard_o->import($file_content);
|
||||
|
||||
if (!empty($v_list)) {
|
||||
$vcards = array_merge($vcards, $v_list);
|
||||
continue;
|
||||
}
|
||||
|
||||
// no vCards found, try CSV
|
||||
$csv = new rcube_csv2vcard($_SESSION['language']);
|
||||
|
||||
if ($has_map) {
|
||||
$skip_head = isset($_POST['_skip_header']);
|
||||
$map = rcube_utils::get_input_value('_map', rcube_utils::INPUT_GPC);
|
||||
$map = array_filter($map);
|
||||
|
||||
$csv->set_map($map);
|
||||
$csv->import($file_content, false, $skip_head);
|
||||
|
||||
unlink($filepath);
|
||||
}
|
||||
else {
|
||||
// save uploaded file for the real import in the next step
|
||||
$temp_csv = rcube_utils::temp_filename('csvimpt');
|
||||
if (move_uploaded_file($filepath, $temp_csv) && file_exists($temp_csv)) {
|
||||
$fields = $csv->get_fields();
|
||||
$last_map = $map;
|
||||
$map = $csv->import($file_content, true);
|
||||
|
||||
// when multiple CSV files are uploaded check they all have the same structure
|
||||
if ($last_map && $last_map !== $map) {
|
||||
$csvs = [];
|
||||
$upload_error = self::UPLOAD_ERR_CSV_FIELDS;
|
||||
break;
|
||||
}
|
||||
|
||||
$csvs[] = $temp_csv;
|
||||
}
|
||||
else {
|
||||
$upload_error = UPLOAD_ERR_CANT_WRITE;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$v_list = $csv->export();
|
||||
|
||||
if (!empty($v_list)) {
|
||||
$vcards = array_merge($vcards, $v_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($csvs) > 0) {
|
||||
// csv import, show field mapping options
|
||||
$importstep = 'import_map';
|
||||
|
||||
$_SESSION['contactcsvimport']['files'] = $csvs;
|
||||
$_SESSION['contactcsvimport']['params'] = [
|
||||
'replace' => $replace,
|
||||
'target' => $target,
|
||||
'with_groups' => $with_groups,
|
||||
'fields' => !empty($fields) ? $fields : [],
|
||||
];
|
||||
|
||||
// Stored separately due to nested array limitations in session
|
||||
$_SESSION['contactcsvimport']['map'] = $map;
|
||||
|
||||
// Re-enable the import button
|
||||
$rcmail->output->command('parent.import_state_set', 'error');
|
||||
}
|
||||
elseif (count($vcards) > 0) {
|
||||
// import vcards
|
||||
self::$stats = new stdClass;
|
||||
self::$stats->names = [];
|
||||
self::$stats->skipped_names = [];
|
||||
self::$stats->count = count($vcards);
|
||||
self::$stats->inserted = 0;
|
||||
self::$stats->skipped = 0;
|
||||
self::$stats->invalid = 0;
|
||||
self::$stats->errors = 0;
|
||||
|
||||
if ($replace) {
|
||||
$CONTACTS->delete_all($CONTACTS->groups && $with_groups < 2);
|
||||
}
|
||||
|
||||
if ($with_groups) {
|
||||
$import_groups = $CONTACTS->list_groups();
|
||||
}
|
||||
|
||||
foreach ($vcards as $vcard) {
|
||||
$a_record = $vcard->get_assoc();
|
||||
|
||||
// Generate contact's display name (must be before validation), the same we do in save.inc
|
||||
if (empty($a_record['name'])) {
|
||||
$a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
|
||||
// Reset it if equals to email address (from compose_display_name())
|
||||
if ($a_record['name'] == ($a_record['email'][0] ?? null)) {
|
||||
$a_record['name'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
// skip invalid (incomplete) entries
|
||||
if (!$CONTACTS->validate($a_record, true)) {
|
||||
self::$stats->invalid++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We're using UTF8 internally
|
||||
$email = null;
|
||||
if (isset($vcard->email[0])) {
|
||||
$email = $vcard->email[0];
|
||||
$email = rcube_utils::idn_to_utf8($email);
|
||||
}
|
||||
|
||||
if (!$replace) {
|
||||
$existing = null;
|
||||
// compare e-mail address
|
||||
if ($email) {
|
||||
$existing = $CONTACTS->search('email', $email, 1, false);
|
||||
}
|
||||
// compare display name if email not found
|
||||
if ((!$existing || !$existing->count) && $vcard->displayname) {
|
||||
$existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
|
||||
}
|
||||
if ($existing && $existing->count) {
|
||||
self::$stats->skipped++;
|
||||
self::$stats->skipped_names[] = $vcard->displayname ?: $email;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$a_record['vcard'] = $vcard->export();
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_create', ['record' => $a_record, 'source' => null]);
|
||||
$a_record = $plugin['record'];
|
||||
|
||||
// insert record and send response
|
||||
if (empty($plugin['abort'])) {
|
||||
$success = $CONTACTS->insert($a_record);
|
||||
}
|
||||
else {
|
||||
$success = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
// assign groups for this contact (if enabled)
|
||||
if ($with_groups && !empty($a_record['groups'])) {
|
||||
foreach (explode(',', $a_record['groups'][0]) as $group_name) {
|
||||
if ($group_id = self::import_group_id($group_name, $CONTACTS, $with_groups == 1, $import_groups)) {
|
||||
$CONTACTS->add_to_group($group_id, $success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$stats->inserted++;
|
||||
self::$stats->names[] = $a_record['name'] ?: $email;
|
||||
}
|
||||
else {
|
||||
self::$stats->errors++;
|
||||
}
|
||||
}
|
||||
|
||||
$importstep = 'import_confirm';
|
||||
$_SESSION['contactcsvimport'] = null;
|
||||
|
||||
$rcmail->output->command('parent.import_state_set', self::$stats->inserted ? 'reload' : 'ok');
|
||||
}
|
||||
else {
|
||||
if ($upload_error == self::UPLOAD_ERR_CSV_FIELDS) {
|
||||
$rcmail->output->show_message('csvfilemismatch', 'error');
|
||||
}
|
||||
else {
|
||||
self::upload_error($upload_error);
|
||||
}
|
||||
|
||||
$rcmail->output->command('parent.import_state_set', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext('importcontacts'));
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'importstep' => [$this, $importstep],
|
||||
]);
|
||||
|
||||
// render page
|
||||
if ($rcmail->output->template_exists('contactimport')) {
|
||||
$rcmail->output->send('contactimport');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->send('importcontacts'); // deprecated
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function to display the import/upload form
|
||||
*/
|
||||
public static function import_form($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC);
|
||||
|
||||
$attrib += ['id' => 'rcmImportForm'];
|
||||
|
||||
$writable_books = $rcmail->get_address_sources(true, true);
|
||||
$max_filesize = self::upload_init();
|
||||
|
||||
$form = '';
|
||||
$hint = $rcmail->gettext(['id' => 'importfile', 'name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]);
|
||||
$table = new html_table(['cols' => 2]);
|
||||
$upload = new html_inputfield([
|
||||
'type' => 'file',
|
||||
'name' => '_file[]',
|
||||
'id' => 'rcmimportfile',
|
||||
'size' => 40,
|
||||
'multiple' => 'multiple',
|
||||
'class' => 'form-control-file',
|
||||
]);
|
||||
|
||||
$table->add('title', html::label('rcmimportfile', $rcmail->gettext('importfromfile')));
|
||||
$table->add(null, $upload->show() . html::div('hint', $hint));
|
||||
|
||||
// addressbook selector
|
||||
if (count($writable_books) > 1) {
|
||||
$select = new html_select([
|
||||
'name' => '_target',
|
||||
'id' => 'rcmimporttarget',
|
||||
'is_escaped' => true,
|
||||
'class' => 'custom-select'
|
||||
]);
|
||||
|
||||
foreach ($writable_books as $book) {
|
||||
$select->add($book['name'], $book['id']);
|
||||
}
|
||||
|
||||
$table->add('title', html::label('rcmimporttarget', $rcmail->gettext('importtarget')));
|
||||
$table->add(null, $select->show($target));
|
||||
}
|
||||
else {
|
||||
$abook = new html_hiddenfield(['name' => '_target', 'value' => key($writable_books)]);
|
||||
$form .= $abook->show();
|
||||
}
|
||||
|
||||
$form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']);
|
||||
|
||||
// selector for group import options
|
||||
if (count($writable_books) >= 1 || $writable_books[0]->groups) {
|
||||
$select = new html_select([
|
||||
'name' => '_groups',
|
||||
'id' => 'rcmimportgroups',
|
||||
'is_escaped' => true,
|
||||
'class' => 'custom-select'
|
||||
]);
|
||||
$select->add($rcmail->gettext('none'), '0');
|
||||
$select->add($rcmail->gettext('importgroupsall'), '1');
|
||||
$select->add($rcmail->gettext('importgroupsexisting'), '2');
|
||||
|
||||
$table->add('title', html::label('rcmimportgroups', $rcmail->gettext('importgroups')));
|
||||
$table->add(null, $select->show(rcube_utils::get_input_value('_groups', rcube_utils::INPUT_GPC)));
|
||||
}
|
||||
|
||||
// checkbox to replace the entire address book
|
||||
$check_replace = new html_checkbox(['name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace']);
|
||||
$table->add('title', html::label('rcmimportreplace', $rcmail->gettext('importreplace')));
|
||||
$table->add(null, $check_replace->show(rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC)));
|
||||
|
||||
$form .= $table->show(['id' => null] + $attrib);
|
||||
|
||||
// remove any info left over info from previous import attempts
|
||||
$_SESSION['contactcsvimport'] = null;
|
||||
|
||||
$rcmail->output->set_env('writable_source', !empty($writable_books));
|
||||
$rcmail->output->add_label('selectimportfile','importwait');
|
||||
$rcmail->output->add_gui_object('importform', $attrib['id']);
|
||||
|
||||
$attrib = [
|
||||
'action' => $rcmail->url('import'),
|
||||
'method' => 'post',
|
||||
'enctype' => 'multipart/form-data'
|
||||
] + $attrib;
|
||||
|
||||
return html::p(null, rcube::Q($rcmail->gettext('importdesc'), 'show'))
|
||||
. $rcmail->output->form_tag($attrib, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the field mapping page for the CSV import process
|
||||
*/
|
||||
public static function import_map($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$params = $_SESSION['contactcsvimport']['params'];
|
||||
|
||||
// hide groups field from list when group import disabled
|
||||
if (empty($params['with_groups'])) {
|
||||
unset($params['fields']['groups']);
|
||||
}
|
||||
|
||||
$fieldlist = new html_select(['name' => '_map[]']);
|
||||
$fieldlist->add($rcmail->gettext('fieldnotmapped'), '');
|
||||
foreach ($params['fields'] as $id => $name) {
|
||||
$fieldlist->add($name, $id);
|
||||
}
|
||||
|
||||
$field_table = new html_table(['cols' => 2] + $attrib);
|
||||
|
||||
if ($classes = $attrib['table-header-class']) {
|
||||
$field_table->set_header_attribs($classes);
|
||||
}
|
||||
|
||||
$field_table->add_header($attrib['table-col-source-class'] ?: null, $rcmail->gettext('source'));
|
||||
$field_table->add_header($attrib['table-col-destination-class'] ?: null, $rcmail->gettext('destination'));
|
||||
|
||||
$map = $_SESSION['contactcsvimport']['map'];
|
||||
foreach ($map['source'] as $i => $name) {
|
||||
$field_table->add('title', html::label('rcmimportmap' . $i, rcube::Q($name)));
|
||||
$field_table->add(null, $fieldlist->show(array_key_exists($i, $map['destination']) ? $map['destination'][$i] : '', ['id' => 'rcmimportmap' . $i]));
|
||||
}
|
||||
|
||||
$form = '';
|
||||
$form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']);
|
||||
|
||||
// show option to import data from first line of the file
|
||||
$check_header = new html_checkbox(['name' => '_skip_header', 'value' => 1, 'id' => 'rcmskipheader']);
|
||||
$form .= html::p(null, html::label('rcmskipheader', $check_header->show(1) . $rcmail->gettext('skipheader')));
|
||||
|
||||
$form .= $field_table->show();
|
||||
|
||||
$attrib = ['action' => $rcmail->url('import'), 'method' => 'post'] + $attrib + ['id' => 'rcmImportFormMap'];
|
||||
|
||||
$rcmail->output->add_gui_object('importformmap', $attrib['id']);
|
||||
|
||||
return html::p(null, rcube::Q($rcmail->gettext('importmapdesc'), 'show'))
|
||||
. $rcmail->output->form_tag($attrib, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the confirmation page for the import process
|
||||
*/
|
||||
public static function import_confirm($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$vars = get_object_vars(self::$stats);
|
||||
$vars['names'] = $vars['skipped_names'] = '';
|
||||
|
||||
$content = html::p(null, $rcmail->gettext([
|
||||
'name' => 'importconfirm',
|
||||
'nr' => self::$stats->inserted,
|
||||
'vars' => $vars,
|
||||
]) . (self::$stats->names ? ':' : '.')
|
||||
);
|
||||
|
||||
if (self::$stats->names) {
|
||||
$content .= html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->names)));
|
||||
}
|
||||
|
||||
if (self::$stats->skipped) {
|
||||
$content .= html::p(null, $rcmail->gettext([
|
||||
'name' => 'importconfirmskipped',
|
||||
'nr' => self::$stats->skipped,
|
||||
'vars' => $vars,
|
||||
]) . ':')
|
||||
. html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->skipped_names)));
|
||||
}
|
||||
|
||||
return html::div($attrib, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching group id. If group doesn't exist, it'll be created if allowed.
|
||||
*/
|
||||
public static function import_group_id($group_name, $contacts, $create, &$import_groups)
|
||||
{
|
||||
$group_id = 0;
|
||||
foreach ($import_groups as $group) {
|
||||
if (strtolower($group['name']) === strtolower($group_name)) {
|
||||
$group_id = $group['ID'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// create a new group
|
||||
if (!$group_id && $create) {
|
||||
$new_group = $contacts->create_group($group_name);
|
||||
|
||||
if (empty($new_group['ID'])) {
|
||||
$new_group['ID'] = $new_group['id'];
|
||||
}
|
||||
|
||||
$import_groups[] = $new_group;
|
||||
$group_id = $new_group['ID'];
|
||||
}
|
||||
|
||||
return $group_id;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
<?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: |
|
||||
| Send contacts list to client (as remote response) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_list extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
if (!empty($_GET['_page'])) {
|
||||
$page = intval($_GET['_page']);
|
||||
}
|
||||
else {
|
||||
$page = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
|
||||
}
|
||||
|
||||
$_SESSION['page'] = $page;
|
||||
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$group_data = null;
|
||||
|
||||
// Use search result
|
||||
if (($records = self::search_update(true)) !== false) {
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$first = ($page-1) * $page_size;
|
||||
$result = new rcube_result_set($count, $first);
|
||||
|
||||
// we need only records for current page
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $first, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
}
|
||||
// List selected directory
|
||||
else {
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$contacts = self::contact_source(null, true);
|
||||
|
||||
// get contacts for this user
|
||||
$result = $contacts->list_records($afields);
|
||||
|
||||
if (!$result->count && $result->searchonly) {
|
||||
$rcmail->output->show_message('contactsearchonly', 'notice');
|
||||
// Don't invoke advanced search dialog automatically from here (#6679)
|
||||
}
|
||||
|
||||
if (!empty($contacts->group_id)) {
|
||||
$group_data = ['ID' => $contacts->group_id]
|
||||
+ array_intersect_key((array) $contacts->get_group($contacts->group_id), ['name' => 1,'email' => 1]);
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->command('set_group_prop', $group_data);
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
|
||||
$rcmail->output->command('set_rowcount', self::get_rowcount_text($result));
|
||||
|
||||
// create javascript list
|
||||
self::js_contacts_list($result);
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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: |
|
||||
| Compose a recipient list with all selected contacts |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_mailto extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$cids = self::get_cids();
|
||||
$mailto = [];
|
||||
$sources = [];
|
||||
|
||||
foreach ($cids as $source => $cid) {
|
||||
$contacts = $rcmail->get_address_book($source);
|
||||
|
||||
if ($contacts->ready) {
|
||||
$contacts->set_page(1);
|
||||
$contacts->set_pagesize(count($cid) + 2); // +2 to skip counting query
|
||||
$sources[] = $contacts->search($contacts->primary_key, $cid, 0, true, true, 'email');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['_gid']) && isset($_REQUEST['_source'])) {
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GP);
|
||||
$group_id = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP);
|
||||
|
||||
$contacts = $rcmail->get_address_book($source);
|
||||
$group_data = $contacts->get_group($group_id);
|
||||
|
||||
// group has an email address assigned: use that
|
||||
if (!empty($group_data['email'])) {
|
||||
$mailto[] = format_email_recipient($group_data['email'][0], $group_data['name']);
|
||||
}
|
||||
else if ($contacts->ready) {
|
||||
$maxnum = (int) $rcmail->config->get('max_group_members');
|
||||
|
||||
$contacts->set_group($group_id);
|
||||
$contacts->set_page(1);
|
||||
$contacts->set_pagesize($maxnum ?: 999);
|
||||
$sources[] = $contacts->list_records();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($sources as $source) {
|
||||
while (is_object($source) && ($rec = $source->iterate())) {
|
||||
$emails = rcube_addressbook::get_col_values('email', $rec, true);
|
||||
|
||||
if (!empty($emails)) {
|
||||
$mailto[] = format_email_recipient($emails[0], $rec['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($mailto)) {
|
||||
$mailto_str = join(', ', $mailto);
|
||||
$mailto_id = substr(md5($mailto_str), 0, 16);
|
||||
$_SESSION['mailto'][$mailto_id] = urlencode($mailto_str);
|
||||
$rcmail->output->command('open_compose_step', ['_mailto' => $mailto_id]);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('nocontactsfound', 'warning');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?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: |
|
||||
| Move a contact record from one directory to another |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_move extends rcmail_action_contacts_index
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$cids = self::get_cids();
|
||||
$target = rcube_utils::get_input_string('_to', rcube_utils::INPUT_POST);
|
||||
$target_group = rcube_utils::get_input_string('_togid', rcube_utils::INPUT_POST);
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$all = 0;
|
||||
$deleted = 0;
|
||||
$success = 0;
|
||||
$errormsg = 'moveerror';
|
||||
$maxnum = $rcmail->config->get('max_group_members', 0);
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$page = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
|
||||
|
||||
foreach ($cids as $source => $source_cids) {
|
||||
// Something wrong, target not specified
|
||||
if (!strlen($target)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// It might happen when moving records from search result
|
||||
// Do nothing, go to the next source
|
||||
if ((string) $target === (string) $source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$CONTACTS = $rcmail->get_address_book($source);
|
||||
$TARGET = $rcmail->get_address_book($target);
|
||||
|
||||
if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$CONTACTS || !$CONTACTS->ready || ($CONTACTS->readonly && empty($CONTACTS->deletable))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
|
||||
foreach ($source_cids as $idx => $cid) {
|
||||
$record = $CONTACTS->get_record($cid, true);
|
||||
|
||||
// avoid moving groups
|
||||
if (isset($record['_type']) && $record['_type'] == 'group') {
|
||||
unset($source_cids[$idx]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if contact exists, if so, we'll need it's ID
|
||||
// Note: Some addressbooks allows empty email address field
|
||||
// @TODO: should we check all email addresses?
|
||||
$email = $CONTACTS->get_col_values('email', $record, true);
|
||||
|
||||
if (!empty($email)) {
|
||||
$result = $TARGET->search('email', $email[0], 1, true, true);
|
||||
}
|
||||
else if (!empty($record['name'])) {
|
||||
$result = $TARGET->search('name', $record['name'], 1, true, true);
|
||||
}
|
||||
else {
|
||||
$result = new rcube_result_set();
|
||||
}
|
||||
|
||||
// insert contact record
|
||||
if (!$result->count) {
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_create', [
|
||||
'record' => $record,
|
||||
'source' => $target,
|
||||
'group' => $target_group
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
if ($insert_id = $TARGET->insert($plugin['record'], false)) {
|
||||
$ids[] = $insert_id;
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
else if (!empty($plugin['result'])) {
|
||||
$ids = array_merge($ids, $plugin['result']);
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$record = $result->first();
|
||||
$ids[] = $record['ID'];
|
||||
$errormsg = empty($email) ? 'contactnameexists' : 'contactexists';
|
||||
}
|
||||
}
|
||||
|
||||
// remove source contacts
|
||||
if ($success && !empty($source_cids)) {
|
||||
$all += count($source_cids);
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_delete', [
|
||||
'id' => $source_cids,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
$del_status = !$plugin['abort'] ? $CONTACTS->delete($source_cids) : $plugin['result'];
|
||||
|
||||
if ($del_status) {
|
||||
$deleted += $del_status;
|
||||
}
|
||||
}
|
||||
|
||||
// assign to group
|
||||
if ($target_group && $TARGET->groups && !empty($ids)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
|
||||
'group_id' => $target_group,
|
||||
'ids' => $ids,
|
||||
'source' => $target
|
||||
]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$TARGET->reset();
|
||||
$TARGET->set_group($target_group);
|
||||
|
||||
if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
|
||||
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success) {
|
||||
$success = $cnt;
|
||||
}
|
||||
}
|
||||
else if ($plugin['result']) {
|
||||
$success = $plugin['result'];
|
||||
}
|
||||
|
||||
$errormsg = !empty($plugin['message']) ? $plugin['message'] : 'moveerror';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$deleted || $deleted != $all) {
|
||||
$rcmail->output->command('list_contacts');
|
||||
}
|
||||
else {
|
||||
// update saved search after data changed
|
||||
if (($records = self::search_update(true)) !== false) {
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$first = ($page-1) * $page_size;
|
||||
$result = new rcube_result_set($count, $first);
|
||||
$pages = ceil((count($records) + $deleted) / $page_size);
|
||||
|
||||
// last page and it's empty, display previous one
|
||||
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
|
||||
$rcmail->output->command('list_page', 'prev');
|
||||
$rowcount = $rcmail->gettext('loading');
|
||||
}
|
||||
// get records from the next page to add to the list
|
||||
else if ($pages > 1 && $page < $pages) {
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
$first += $page_size;
|
||||
// create resultset object
|
||||
$res = new rcube_result_set($count, $first - $deleted);
|
||||
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $first - $deleted, $deleted);
|
||||
}
|
||||
|
||||
$res->records = array_values($records);
|
||||
$records = $res;
|
||||
}
|
||||
else {
|
||||
unset($records);
|
||||
}
|
||||
}
|
||||
else if (isset($CONTACTS)) {
|
||||
// count contacts for this user
|
||||
$result = $CONTACTS->count();
|
||||
$pages = ceil(($result->count + $deleted) / $page_size);
|
||||
|
||||
// last page and it's empty, display previous one
|
||||
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
|
||||
$rcmail->output->command('list_page', 'prev');
|
||||
$rowcount = $rcmail->gettext('loading');
|
||||
}
|
||||
// get records from the next page to add to the list
|
||||
else if ($pages > 1 && $page < $pages) {
|
||||
$CONTACTS->set_page($page);
|
||||
$records = $CONTACTS->list_records(null, -$deleted);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($rowcount)) {
|
||||
$rowcount = isset($result) ? self::get_rowcount_text($result) : 0;
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 0);
|
||||
$rcmail->output->command('set_rowcount', $rowcount);
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if (!empty($records)) {
|
||||
self::js_contacts_list($records);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
$rcmail->output->show_message($errormsg, 'error');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('movesuccess', 'confirmation', ['nr' => $success]);
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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 contact photo |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_photo extends rcmail_action_contacts_index
|
||||
{
|
||||
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();
|
||||
|
||||
// Get contact ID and source ID from request
|
||||
$cids = self::get_cids();
|
||||
$source = key($cids);
|
||||
$cid = $cids ? array_first($cids[$source]) : null;
|
||||
$file_id = rcube_utils::get_input_string('_photo', rcube_utils::INPUT_GPC);
|
||||
|
||||
// read the referenced file
|
||||
if ($file_id && !empty($_SESSION['contacts']['files'][$file_id])) {
|
||||
$tempfile = $_SESSION['contacts']['files'][$file_id];
|
||||
$tempfile = $rcmail->plugins->exec_hook('attachment_display', $tempfile);
|
||||
|
||||
if (!empty($tempfile['status'])) {
|
||||
if (!empty($tempfile['data'])) {
|
||||
$data = $tempfile['data'];
|
||||
}
|
||||
else if ($tempfile['path']) {
|
||||
$data = file_get_contents($tempfile['path']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// by email, search for contact first
|
||||
if ($email = rcube_utils::get_input_string('_email', rcube_utils::INPUT_GPC)) {
|
||||
foreach ($rcmail->get_address_sources() as $s) {
|
||||
$abook = $rcmail->get_address_book($s['id']);
|
||||
$result = $abook->search(['email'], $email, 1, true, true, 'photo');
|
||||
while ($result && ($record = $result->iterate())) {
|
||||
if (!empty($record['photo'])) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// by contact id
|
||||
if (empty($record) && $cid) {
|
||||
// Initialize addressbook source
|
||||
$CONTACTS = self::contact_source($source, true);
|
||||
// read contact record
|
||||
$record = $CONTACTS->get_record($cid, true);
|
||||
}
|
||||
|
||||
if (!empty($record['photo'])) {
|
||||
$data = is_array($record['photo']) ? $record['photo'][0] : $record['photo'];
|
||||
if (!preg_match('![^a-z0-9/=+-]!i', $data)) {
|
||||
$data = base64_decode($data, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let plugins do fancy things with contact photos
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_photo', [
|
||||
'record' => $record ?? null,
|
||||
'email' => $email ?? null,
|
||||
'data' => $data ?? null,
|
||||
]);
|
||||
|
||||
// redirect to url provided by a plugin
|
||||
if (!empty($plugin['url'])) {
|
||||
$rcmail->output->redirect($plugin['url']);
|
||||
}
|
||||
|
||||
$data = $plugin['data'];
|
||||
|
||||
// detect if photo data is a URL
|
||||
if ($data && strlen($data) < 1024 && filter_var($data, FILTER_VALIDATE_URL)) {
|
||||
$rcmail->output->redirect($data);
|
||||
}
|
||||
|
||||
// cache for one day if requested by email
|
||||
if (!$cid && !empty($email)) {
|
||||
$rcmail->output->future_expire_header(86400);
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
$rcmail->output->sendExit($data, ['Content-Type: ' . rcube_mime::image_content_type($data)]);
|
||||
}
|
||||
|
||||
if (!empty($_GET['_error'])) {
|
||||
$rcmail->output->sendExit('', ['HTTP/1.0 204 Photo not found']);
|
||||
}
|
||||
|
||||
$rcmail->output->sendExit(base64_decode(rcmail_output::BLANK_GIF), ['Content-Type: image/gif']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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: |
|
||||
| Print contact details |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_print extends rcmail_action_contacts_index
|
||||
{
|
||||
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();
|
||||
|
||||
// Get contact ID and source ID from request
|
||||
$cids = self::get_cids();
|
||||
$source = key($cids);
|
||||
$cid = $cids ? array_first($cids[$source]) : null;
|
||||
|
||||
// Initialize addressbook source
|
||||
self::$CONTACTS = self::contact_source($source, true);
|
||||
self::$SOURCE_ID = $source;
|
||||
|
||||
// read contact record
|
||||
if ($cid && self::$CONTACTS) {
|
||||
self::$contact = self::$CONTACTS->get_record($cid, true);
|
||||
}
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'contacthead' => [$this, 'contact_head'],
|
||||
'contactdetails' => [$this, 'contact_details'],
|
||||
'contactphoto' => [$this, 'contact_photo'],
|
||||
]);
|
||||
|
||||
$rcmail->output->send('contactprint');
|
||||
}
|
||||
|
||||
public static function contact_head($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// check if we have a valid result
|
||||
if (!self::$contact) {
|
||||
$rcmail->output->show_message('contactnotfound', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
$form = [
|
||||
'head' => [ // section 'head' is magic!
|
||||
'name' => $rcmail->gettext('contactnameandorg'),
|
||||
'content' => [
|
||||
'prefix' => [],
|
||||
'name' => [],
|
||||
'firstname' => [],
|
||||
'middlename' => [],
|
||||
'surname' => [],
|
||||
'suffix' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
unset($attrib['name']);
|
||||
|
||||
return self::contact_form($form, self::$contact, $attrib);
|
||||
}
|
||||
|
||||
public static function contact_details($attrib)
|
||||
{
|
||||
// check if we have a valid result
|
||||
if (!self::$contact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$form = [
|
||||
'contact' => [
|
||||
'name' => $rcmail->gettext('properties'),
|
||||
'content' => [
|
||||
'organization' => [],
|
||||
'department' => [],
|
||||
'jobtitle' => [],
|
||||
'email' => [],
|
||||
'phone' => [],
|
||||
'address' => [],
|
||||
'website' => [],
|
||||
'im' => [],
|
||||
'groups' => [],
|
||||
],
|
||||
],
|
||||
'personal' => [
|
||||
'name' => $rcmail->gettext('personalinfo'),
|
||||
'content' => [
|
||||
'nickname' => [],
|
||||
'gender' => [],
|
||||
'maidenname' => [],
|
||||
'birthday' => [],
|
||||
'anniversary' => [],
|
||||
'manager' => [],
|
||||
'assistant' => [],
|
||||
'spouse' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (isset(rcmail_action_contacts_index::$CONTACT_COLTYPES['notes'])) {
|
||||
$form['notes'] = [
|
||||
'name' => $rcmail->gettext('notes'),
|
||||
'content' => [
|
||||
'notes' => ['type' => 'textarea', 'label' => false],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (self::$CONTACTS->groups) {
|
||||
$groups = self::$CONTACTS->get_record_groups(self::$contact['ID']);
|
||||
if (!empty($groups)) {
|
||||
$form['contact']['content']['groups'] = [
|
||||
'value' => rcube::Q(implode(', ', $groups)),
|
||||
'label' => $rcmail->gettext('groups')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return self::contact_form($form, self::$contact, $attrib);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?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 contact data as QR code |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_qrcode extends rcmail_action_contacts_index
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
// Get contact ID and source ID from request
|
||||
$cids = self::get_cids();
|
||||
$source = key($cids);
|
||||
$cid = $cids ? array_first($cids[$source]) : null;
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// read contact record
|
||||
$abook = self::contact_source($source, true);
|
||||
$contact = $abook->get_record($cid, true);
|
||||
|
||||
// generate QR code image
|
||||
if ($data = self::contact_qrcode($contact)) {
|
||||
$headers = [
|
||||
'Content-Type: ' . self::check_support(),
|
||||
'Content-Length: ' . strlen($data)
|
||||
];
|
||||
|
||||
$rcmail->output->sendExit($data, $headers);
|
||||
}
|
||||
|
||||
$rcmail->output->sendExit('', ['HTTP/1.0 404 Contact not found']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a QR-code image for a contact
|
||||
*
|
||||
* @param array $contact Contact record
|
||||
*
|
||||
* @return string|null Image content, Null on error or missing PHP extensions
|
||||
*/
|
||||
public static function contact_qrcode($contact)
|
||||
{
|
||||
if (empty($contact)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = self::check_support();
|
||||
|
||||
if (empty($type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$vcard = new rcube_vcard();
|
||||
|
||||
// QR code input is limited, use only common fields
|
||||
$fields = ['name', 'firstname', 'surname', 'middlename', 'nickname',
|
||||
'organization', 'phone', 'email', 'jobtitle', 'prefix', 'suffix'];
|
||||
|
||||
foreach ($contact as $field => $value) {
|
||||
if (strpos($field, ':') !== false) {
|
||||
list($field, $section) = explode(':', $field, 2);
|
||||
}
|
||||
else {
|
||||
$section = null;
|
||||
}
|
||||
|
||||
if (in_array($field, $fields)) {
|
||||
foreach ((array) $value as $v) {
|
||||
$vcard->set($field, $v, $section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = $vcard->export();
|
||||
|
||||
if (empty($data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$renderer_style = new BaconQrCode\Renderer\RendererStyle\RendererStyle(300, 1);
|
||||
$renderer_image = $type == 'image/png'
|
||||
? new BaconQrCode\Renderer\Image\ImagickImageBackEnd()
|
||||
: new BaconQrCode\Renderer\Image\SvgImageBackEnd();
|
||||
|
||||
$renderer = new BaconQrCode\Renderer\ImageRenderer($renderer_style, $renderer_image);
|
||||
$writer = new BaconQrCode\Writer($renderer);
|
||||
|
||||
return $writer->writeString($data, RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check required extensions and classes for QR code generation
|
||||
*
|
||||
* @return string|null Content-type of the image result
|
||||
*/
|
||||
public static function check_support()
|
||||
{
|
||||
if (extension_loaded('iconv') && class_exists('BaconQrCode\Renderer\ImageRenderer')) {
|
||||
if (extension_loaded('xmlwriter')) {
|
||||
return 'image/svg+xml';
|
||||
}
|
||||
|
||||
if (extension_loaded('imagick')) {
|
||||
return 'image/png';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?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 a contact entry or to add a new one |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_save extends rcmail_action_contacts_index
|
||||
{
|
||||
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();
|
||||
$contacts = self::contact_source(null, true);
|
||||
$cid = rcube_utils::get_input_string('_cid', rcube_utils::INPUT_POST);
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$return_action = empty($cid) ? 'add' : 'edit';
|
||||
|
||||
// Source changed, display the form again
|
||||
if (!empty($_GET['_reload'])) {
|
||||
$rcmail->overwrite_action($return_action);
|
||||
return;
|
||||
}
|
||||
|
||||
// cannot edit record
|
||||
if (!$contacts || $contacts->readonly) {
|
||||
$rcmail->output->show_message('contactreadonly', 'error');
|
||||
$rcmail->overwrite_action($return_action);
|
||||
return;
|
||||
}
|
||||
|
||||
// read POST values into hash array
|
||||
$a_record = self::process_input();
|
||||
|
||||
// do input checks (delegated to $contacts instance)
|
||||
if (!$contacts->validate($a_record)) {
|
||||
$err = (array) $contacts->get_error();
|
||||
$rcmail->output->show_message(!empty($err['message']) ? rcube::Q($err['message']) : 'formincomplete', 'warning');
|
||||
$rcmail->overwrite_action($return_action, ['contact' => $a_record]);
|
||||
return;
|
||||
}
|
||||
|
||||
// get raw photo data if changed
|
||||
if (isset($a_record['photo'])) {
|
||||
if ($a_record['photo'] == '-del-') {
|
||||
$a_record['photo'] = '';
|
||||
}
|
||||
else if (!empty($_SESSION['contacts']['files'][$a_record['photo']])) {
|
||||
$tempfile = $_SESSION['contacts']['files'][$a_record['photo']];
|
||||
$tempfile = $rcmail->plugins->exec_hook('attachment_get', $tempfile);
|
||||
if ($tempfile['status']) {
|
||||
$a_record['photo'] = $tempfile['data'] ?: @file_get_contents($tempfile['path']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unset($a_record['photo']);
|
||||
}
|
||||
|
||||
// cleanup session data
|
||||
$rcmail->plugins->exec_hook('attachments_cleanup', ['group' => 'contact']);
|
||||
$rcmail->session->remove('contacts');
|
||||
}
|
||||
|
||||
// update an existing contact
|
||||
if (!empty($cid)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_update', [
|
||||
'id' => $cid,
|
||||
'record' => $a_record,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
$a_record = $plugin['record'];
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
$result = $contacts->update($cid, $a_record);
|
||||
}
|
||||
else {
|
||||
$result = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// show confirmation
|
||||
$rcmail->output->show_message('successfullysaved', 'confirmation', null, false);
|
||||
|
||||
// in search mode, just reload the list (#1490015)
|
||||
if (!empty($_REQUEST['_search'])) {
|
||||
$rcmail->output->command('parent.command', 'list');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$newcid = null;
|
||||
|
||||
// LDAP DN change
|
||||
if (is_string($result) && strlen($result) > 1) {
|
||||
$newcid = $result;
|
||||
// change cid in POST for 'show' action
|
||||
$_POST['_cid'] = $newcid;
|
||||
}
|
||||
|
||||
// refresh contact data for list update and 'show' action
|
||||
$contact = $contacts->get_record($newcid ?: $cid, true);
|
||||
|
||||
// Plugins can decide to remove the contact on edit, e.g. automatic_addressbook
|
||||
// Best we can do is to refresh the list (#5522)
|
||||
if (empty($contact)) {
|
||||
$rcmail->output->command('parent.command', 'list');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
// Update contacts list
|
||||
$a_js_cols = [];
|
||||
$record = $contact;
|
||||
$record['email'] = array_first($contacts->get_col_values('email', $record, true));
|
||||
$record['name'] = rcube_addressbook::compose_list_name($record);
|
||||
|
||||
foreach (['name'] as $col) {
|
||||
$a_js_cols[] = rcube::Q((string) $record[$col]);
|
||||
}
|
||||
|
||||
// performance: unset some big data items we don't need here
|
||||
$record = array_intersect_key($record, ['ID' => 1,'email' => 1,'name' => 1]);
|
||||
$record['_type'] = 'person';
|
||||
|
||||
// update the changed col in list
|
||||
$rcmail->output->command('parent.update_contact_row', $cid, $a_js_cols, $newcid, $source, $record);
|
||||
|
||||
$rcmail->overwrite_action('show', ['contact' => $contact]);
|
||||
}
|
||||
else {
|
||||
// show error message
|
||||
$error = self::error_str($contacts, $plugin);
|
||||
|
||||
$rcmail->output->show_message($error, 'error', null, false);
|
||||
$rcmail->overwrite_action('show');
|
||||
}
|
||||
}
|
||||
// insert a new contact
|
||||
else {
|
||||
// Name of the addressbook already selected on the list
|
||||
$orig_source = rcube_utils::get_input_string('_orig_source', rcube_utils::INPUT_GPC);
|
||||
|
||||
if (!strlen($source)) {
|
||||
$source = $orig_source;
|
||||
}
|
||||
|
||||
// show notice if existing contacts with same e-mail are found
|
||||
foreach ($contacts->get_col_values('email', $a_record, true) as $email) {
|
||||
if ($email && ($res = $contacts->search('email', $email, 1, false, true)) && $res->count) {
|
||||
$rcmail->output->show_message('contactexists', 'notice', null, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_create', [
|
||||
'record' => $a_record,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
$a_record = $plugin['record'];
|
||||
|
||||
// insert record and send response
|
||||
if (!$plugin['abort']) {
|
||||
$insert_id = $contacts->insert($a_record);
|
||||
}
|
||||
else {
|
||||
$insert_id = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($insert_id) {
|
||||
$contacts->reset();
|
||||
|
||||
// add new contact to the specified group
|
||||
if ($contacts->groups && $contacts->group_id) {
|
||||
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
|
||||
'group_id' => $contacts->group_id,
|
||||
'ids' => $insert_id,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
if (($maxnum = $rcmail->config->get('max_group_members', 0)) && ($contacts->count()->count + 1 > $maxnum)) {
|
||||
// @FIXME: should we remove the contact?
|
||||
$msgtext = $rcmail->gettext(['name' => 'maxgroupmembersreached', 'vars' => ['max' => $maxnum]]);
|
||||
$rcmail->output->command('parent.display_message', $msgtext, 'warning');
|
||||
}
|
||||
else {
|
||||
$contacts->add_to_group($plugin['group_id'], $plugin['ids']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show confirmation
|
||||
$rcmail->output->show_message('successfullysaved', 'confirmation', null, false);
|
||||
|
||||
$rcmail->output->command('parent.set_rowcount', $rcmail->gettext('loading'));
|
||||
$rcmail->output->command('parent.list_contacts');
|
||||
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
else {
|
||||
// show error message
|
||||
$error = self::error_str($contacts, $plugin);
|
||||
$rcmail->output->show_message($error, 'error', null, false);
|
||||
$rcmail->overwrite_action('add');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function process_input()
|
||||
{
|
||||
$record = [];
|
||||
|
||||
foreach (rcmail_action_contacts_index::$CONTACT_COLTYPES as $col => $colprop) {
|
||||
if (!empty($colprop['composite'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fname = '_' . $col;
|
||||
|
||||
// gather form data of composite fields
|
||||
if (!empty($colprop['childs'])) {
|
||||
$values = [];
|
||||
foreach ($colprop['childs'] as $childcol => $cp) {
|
||||
$vals = rcube_utils::get_input_value('_' . $childcol, rcube_utils::INPUT_POST, true);
|
||||
foreach ((array) $vals as $i => $val) {
|
||||
$values[$i][$childcol] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_REQUEST['_subtype_' . $col])) {
|
||||
$subtypes = (array) rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST);
|
||||
}
|
||||
else {
|
||||
$subtypes = [''];
|
||||
}
|
||||
|
||||
foreach ($subtypes as $i => $subtype) {
|
||||
$suffix = $subtype ? ":$subtype" : '';
|
||||
if ($values[$i]) {
|
||||
$record[$col . $suffix][] = $values[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// assign values and subtypes
|
||||
else if (isset($_POST[$fname]) && is_array($_POST[$fname])) {
|
||||
$values = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
|
||||
$subtypes = rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST);
|
||||
|
||||
foreach ($values as $i => $val) {
|
||||
if ($col == 'email') {
|
||||
// extract email from full address specification, e.g. "Name" <addr@domain.tld>
|
||||
$addr = rcube_mime::decode_address_list($val, 1, false);
|
||||
if (!empty($addr) && ($addr = array_pop($addr)) && $addr['mailto']) {
|
||||
$val = $addr['mailto'];
|
||||
}
|
||||
}
|
||||
|
||||
$subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
|
||||
$record[$col.$subtype][] = $val;
|
||||
}
|
||||
}
|
||||
else if (isset($_POST[$fname])) {
|
||||
$record[$col] = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
|
||||
|
||||
// normalize the submitted date strings
|
||||
if ($colprop['type'] == 'date') {
|
||||
if ($record[$col] && ($dt = rcube_utils::anytodatetime($record[$col]))) {
|
||||
$record[$col] = $dt->format('Y-m-d');
|
||||
}
|
||||
else {
|
||||
unset($record[$col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate contact's display name (must be before validation)
|
||||
if (empty($record['name'])) {
|
||||
$record['name'] = rcube_addressbook::compose_display_name($record, true);
|
||||
|
||||
// Reset it if equals to email address (from compose_display_name())
|
||||
$email = rcube_addressbook::get_col_values('email', $record, true);
|
||||
if (isset($email[0]) && $record['name'] == $email[0]) {
|
||||
$record['name'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public static function error_str($contacts, $plugin)
|
||||
{
|
||||
if (!empty($plugin['message'])) {
|
||||
return $plugin['message'];
|
||||
}
|
||||
|
||||
$err = $contacts->get_error();
|
||||
|
||||
if (!empty($err['message'])) {
|
||||
return $err['message'];
|
||||
}
|
||||
|
||||
return 'errorsaving';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?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: |
|
||||
| Search action (and form) for address book contacts |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_search extends rcmail_action_contacts_index
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (empty($_GET['_form'])) {
|
||||
self::contact_search();
|
||||
}
|
||||
|
||||
$rcmail->output->add_handler('searchform', [$this, 'contact_search_form']);
|
||||
$rcmail->output->send('contactsearch');
|
||||
}
|
||||
|
||||
public static function contact_search()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$adv = isset($_POST['_adv']);
|
||||
$sid = rcube_utils::get_input_string('_sid', rcube_utils::INPUT_GET);
|
||||
$search = null;
|
||||
|
||||
// get search criteria from saved search
|
||||
if ($sid && ($search = $rcmail->user->get_search($sid))) {
|
||||
$fields = $search['data']['fields'];
|
||||
$search = $search['data']['search'];
|
||||
}
|
||||
// get fields/values from advanced search form
|
||||
else if ($adv) {
|
||||
foreach (array_keys($_POST) as $key) {
|
||||
$s = trim(rcube_utils::get_input_string($key, rcube_utils::INPUT_POST, true));
|
||||
if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
|
||||
$search[] = $s;
|
||||
$fields[] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
// do nothing, show the form again
|
||||
return;
|
||||
}
|
||||
}
|
||||
// quick-search
|
||||
else {
|
||||
$search = trim(rcube_utils::get_input_string('_q', rcube_utils::INPUT_GET, true));
|
||||
$fields = rcube_utils::get_input_string('_headers', rcube_utils::INPUT_GET);
|
||||
|
||||
if (empty($fields)) {
|
||||
$fields = array_keys(self::$SEARCH_MODS_DEFAULT);
|
||||
}
|
||||
else {
|
||||
$fields = array_filter(explode(',', $fields));
|
||||
}
|
||||
|
||||
// update search_mods setting
|
||||
$old_mods = $rcmail->config->get('addressbook_search_mods');
|
||||
$search_mods = array_fill_keys($fields, 1);
|
||||
|
||||
if ($old_mods != $search_mods) {
|
||||
$rcmail->user->save_prefs(['addressbook_search_mods' => $search_mods]);
|
||||
}
|
||||
|
||||
if (in_array('*', $fields)) {
|
||||
$fields = '*';
|
||||
}
|
||||
}
|
||||
|
||||
// Values matching mode
|
||||
$mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$mode |= rcube_addressbook::SEARCH_GROUPS;
|
||||
|
||||
// get sources list
|
||||
$sources = $rcmail->get_address_sources();
|
||||
$sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$search_set = [];
|
||||
$records = [];
|
||||
|
||||
foreach ($sources as $s) {
|
||||
$source = $rcmail->get_address_book($s['id']);
|
||||
|
||||
// check if search fields are supported....
|
||||
if (is_array($fields)) {
|
||||
$cols = !empty($source->coltypes[0]) ? array_flip($source->coltypes) : $source->coltypes;
|
||||
$supported = 0;
|
||||
|
||||
foreach ($fields as $f) {
|
||||
if (array_key_exists($f, $cols)) {
|
||||
$supported ++;
|
||||
}
|
||||
}
|
||||
|
||||
// in advanced search we require all fields (AND operator)
|
||||
// in quick search we require at least one field (OR operator)
|
||||
if (($adv && $supported < count($fields)) || (!$adv && !$supported)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// reset page
|
||||
$source->set_page(1);
|
||||
$source->set_pagesize(9999);
|
||||
|
||||
// get contacts count
|
||||
$result = $source->search($fields, $search, $mode, false);
|
||||
|
||||
if (empty($result) || !$result->count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get records
|
||||
$result = $source->list_records($afields);
|
||||
|
||||
while ($row = $result->next()) {
|
||||
$row['sourceid'] = $s['id'];
|
||||
$key = rcube_addressbook::compose_contact_key($row, $sort_col);
|
||||
$records[$key] = $row;
|
||||
}
|
||||
|
||||
unset($result);
|
||||
$search_set[$s['id']] = $source->get_search_set();
|
||||
}
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$result = new rcube_result_set($count);
|
||||
|
||||
// cut first-page records
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, 0, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
|
||||
// search request ID
|
||||
$search_request = md5('addr'
|
||||
. (is_array($fields) ? implode(',', $fields) : $fields)
|
||||
. (is_array($search) ? implode(',', $search) : $search)
|
||||
);
|
||||
|
||||
// save search settings in session
|
||||
$_SESSION['contact_search'][$search_request] = $search_set;
|
||||
$_SESSION['contact_search_params'] = ['id' => $search_request, 'data' => [$fields, $search]];
|
||||
$_SESSION['page'] = 1;
|
||||
|
||||
if ($adv) {
|
||||
$rcmail->output->command('list_contacts_clear');
|
||||
}
|
||||
|
||||
if ($result->count > 0) {
|
||||
// create javascript list
|
||||
self::js_contacts_list($result);
|
||||
$rcmail->output->show_message('contactsearchsuccessful', 'confirmation', ['nr' => $result->count]);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('nocontactsfound', 'notice');
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('search_request', $search_request);
|
||||
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
|
||||
$rcmail->output->command('set_rowcount', self::get_rowcount_text($result));
|
||||
// Re-set current source
|
||||
$rcmail->output->set_env('search_id', $sid);
|
||||
$rcmail->output->set_env('source', '');
|
||||
$rcmail->output->set_env('group', '');
|
||||
// Re-set list header
|
||||
$rcmail->output->command('set_group_prop', null);
|
||||
|
||||
if (!$sid) {
|
||||
// unselect currently selected directory/group
|
||||
$rcmail->output->command('unselect_directory');
|
||||
// enable "Save search" command
|
||||
$rcmail->output->command('enable_command', 'search-create', true);
|
||||
}
|
||||
|
||||
$rcmail->output->command('update_group_commands');
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
public static function contact_search_form($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$i_size = !empty($attrib['size']) ? $attrib['size'] : 30;
|
||||
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
|
||||
|
||||
$form = [
|
||||
'main' => [
|
||||
'name' => $rcmail->gettext('properties'),
|
||||
'content' => [],
|
||||
],
|
||||
'personal' => [
|
||||
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
|
||||
'content' => [],
|
||||
],
|
||||
'other' => [
|
||||
'name' => $rcmail->gettext('other'),
|
||||
'content' => [],
|
||||
],
|
||||
];
|
||||
|
||||
// get supported coltypes from all address sources
|
||||
$sources = $rcmail->get_address_sources();
|
||||
$coltypes = [];
|
||||
|
||||
foreach ($sources as $s) {
|
||||
$CONTACTS = $rcmail->get_address_book($s['id']);
|
||||
|
||||
if (!empty($CONTACTS->coltypes)) {
|
||||
$contact_cols = isset($CONTACTS->coltypes[0]) ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
|
||||
$coltypes = array_merge($coltypes, $contact_cols);
|
||||
}
|
||||
}
|
||||
|
||||
// merge supported coltypes with global coltypes
|
||||
foreach ($coltypes as $col => $colprop) {
|
||||
if (!empty(rcmail_action_contacts_index::$CONTACT_COLTYPES[$col])) {
|
||||
$coltypes[$col] = array_merge(rcmail_action_contacts_index::$CONTACT_COLTYPES[$col], (array) $colprop);
|
||||
}
|
||||
else {
|
||||
$coltypes[$col] = (array) $colprop;
|
||||
}
|
||||
}
|
||||
|
||||
// build form fields list
|
||||
foreach ($coltypes as $col => $colprop) {
|
||||
if (!isset($colprop['type'])) {
|
||||
$colprop['type'] = 'text';
|
||||
}
|
||||
if ($colprop['type'] != 'image' && empty($colprop['nosearch'])) {
|
||||
$ftype = $colprop['type'] == 'select' ? 'select' : 'text';
|
||||
$label = $colprop['label'] ?? $rcmail->gettext($col);
|
||||
$category = !empty($colprop['category']) ? $colprop['category'] : 'other';
|
||||
|
||||
// load jquery UI datepicker for date fields
|
||||
if ($colprop['type'] == 'date') {
|
||||
$colprop['class'] = (!empty($colprop['class']) ? $colprop['class'] . ' ' : '') . 'datepicker';
|
||||
}
|
||||
else if ($ftype == 'text') {
|
||||
$colprop['size'] = $i_size;
|
||||
}
|
||||
|
||||
$colprop['id'] = '_search_' . $col;
|
||||
|
||||
$content = html::div('row',
|
||||
html::label(['class' => 'contactfieldlabel label', 'for' => $colprop['id']], rcube::Q($label))
|
||||
. html::div('contactfieldcontent', rcube_output::get_edit_field('search_' . $col, '', $colprop, $ftype))
|
||||
);
|
||||
|
||||
$form[$category]['content'][] = $content;
|
||||
}
|
||||
}
|
||||
|
||||
$hiddenfields = new html_hiddenfield();
|
||||
$hiddenfields->add(['name' => '_adv', 'value' => 1]);
|
||||
|
||||
$out = $rcmail->output->request_form([
|
||||
'name' => 'form',
|
||||
'method' => 'post',
|
||||
'task' => $rcmail->task,
|
||||
'action' => 'search',
|
||||
'noclose' => true,
|
||||
] + $attrib, $hiddenfields->show()
|
||||
);
|
||||
|
||||
$rcmail->output->add_gui_object('editform', $attrib['id']);
|
||||
|
||||
unset($attrib['name']);
|
||||
unset($attrib['id']);
|
||||
|
||||
foreach ($form as $f) {
|
||||
if (!empty($f['content'])) {
|
||||
$content = html::div('contactfieldgroup', join("\n", $f['content']));
|
||||
$legend = html::tag('legend', null, rcube::Q($f['name']));
|
||||
|
||||
$out .= html::tag('fieldset', $attrib, $legend . $content) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $out . '</form>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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: |
|
||||
| Create saved search |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_search_create extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$id = rcube_utils::get_input_value('_search', rcube_utils::INPUT_POST);
|
||||
$name = rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (
|
||||
!empty($_SESSION['contact_search_params'])
|
||||
&& ($params = $_SESSION['contact_search_params'])
|
||||
&& $params['id'] == $id
|
||||
) {
|
||||
$data = [
|
||||
'type' => rcube_user::SEARCH_ADDRESSBOOK,
|
||||
'name' => $name,
|
||||
'data' => [
|
||||
'fields' => $params['data'][0],
|
||||
'search' => $params['data'][1],
|
||||
],
|
||||
];
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('saved_search_create', ['data' => $data]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$result = $rcmail->user->insert_search($plugin['data']);
|
||||
}
|
||||
else {
|
||||
$result = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result)) {
|
||||
$rcmail->output->show_message('savedsearchcreated', 'confirmation');
|
||||
$rcmail->output->command('insert_saved_search', rcube::Q($name), rcube::Q($result));
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'savedsearchcreateerror';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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: |
|
||||
| Delete saved search |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_search_delete extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$id = rcube_utils::get_input_string('_sid', rcube_utils::INPUT_POST);
|
||||
$result = false;
|
||||
|
||||
if (!empty($id)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('saved_search_delete', ['id' => $id]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$result = $rcmail->user->delete_search($id);
|
||||
}
|
||||
else {
|
||||
$result = $plugin['result'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$rcmail->output->show_message('savedsearchdeleted', 'confirmation');
|
||||
$rcmail->output->command('remove_search_item', rcube::Q($id));
|
||||
// contact list will be cleared, clear also page counter
|
||||
$rcmail->output->command('set_rowcount', $rcmail->gettext('nocontactsfound'));
|
||||
$rcmail->output->set_env('pagecount', 0);
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'savedsearchdeleteerror';
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?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 contact details |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_show extends rcmail_action_contacts_index
|
||||
{
|
||||
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();
|
||||
|
||||
// Get contact ID and source ID from request
|
||||
$cids = self::get_cids();
|
||||
$source = key($cids);
|
||||
$cid = $cids ? array_first($cids[$source]) : null;
|
||||
|
||||
// Initialize addressbook source
|
||||
self::$CONTACTS = self::contact_source($source, true);
|
||||
self::$SOURCE_ID = $source;
|
||||
|
||||
// read contact record (or get the one defined in 'save' action)
|
||||
if (!empty($args['contact'])) {
|
||||
self::$contact = $args['contact'];
|
||||
}
|
||||
else if ($cid) {
|
||||
self::$contact = self::$CONTACTS->get_record($cid, true);
|
||||
}
|
||||
|
||||
if ($cid && self::$contact) {
|
||||
$rcmail->output->set_env('readonly', self::$CONTACTS->readonly || !empty(self::$contact['readonly']));
|
||||
$rcmail->output->set_env('cid', self::$contact['ID']);
|
||||
|
||||
// remember current search request ID (if in search mode)
|
||||
if ($search = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GET)) {
|
||||
$rcmail->output->set_env('search_request', $search);
|
||||
}
|
||||
}
|
||||
|
||||
// get address book name (for display)
|
||||
self::set_sourcename(self::$CONTACTS);
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'contacthead' => [$this, 'contact_head'],
|
||||
'contactdetails' => [$this, 'contact_details'],
|
||||
'contactphoto' => [$this, 'contact_photo'],
|
||||
]);
|
||||
|
||||
$rcmail->output->send('contact');
|
||||
}
|
||||
|
||||
public static function contact_head($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// check if we have a valid result
|
||||
if (!self::$contact) {
|
||||
$rcmail->output->show_message('contactnotfound', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
$form = [
|
||||
'head' => [ // section 'head' is magic!
|
||||
'name' => $rcmail->gettext('contactnameandorg'),
|
||||
'content' => [
|
||||
'source' => ['type' => 'text'],
|
||||
'prefix' => ['type' => 'text'],
|
||||
'firstname' => ['type' => 'text'],
|
||||
'middlename' => ['type' => 'text'],
|
||||
'surname' => ['type' => 'text'],
|
||||
'suffix' => ['type' => 'text'],
|
||||
'name' => ['type' => 'text'],
|
||||
'nickname' => ['type' => 'text'],
|
||||
'organization' => ['type' => 'text'],
|
||||
'department' => ['type' => 'text'],
|
||||
'jobtitle' => ['type' => 'text'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
unset($attrib['name']);
|
||||
|
||||
return self::contact_form($form, self::$contact, $attrib);
|
||||
}
|
||||
|
||||
public static function contact_details($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// check if we have a valid result
|
||||
if (!self::$contact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
|
||||
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
|
||||
|
||||
$form = [
|
||||
'contact' => [
|
||||
'name' => $rcmail->gettext('properties'),
|
||||
'content' => [
|
||||
'email' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_email_value'],
|
||||
'phone' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_phone_value'],
|
||||
'address' => [],
|
||||
'website' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_url_value'],
|
||||
'im' => ['size' => $i_size],
|
||||
],
|
||||
],
|
||||
'personal' => [
|
||||
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
|
||||
'content' => [
|
||||
'gender' => ['size' => $i_size],
|
||||
'maidenname' => ['size' => $i_size],
|
||||
'birthday' => ['size' => $i_size],
|
||||
'anniversary' => ['size' => $i_size],
|
||||
'manager' => ['size' => $i_size],
|
||||
'assistant' => ['size' => $i_size],
|
||||
'spouse' => ['size' => $i_size],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (isset(rcmail_action_contacts_index::$CONTACT_COLTYPES['notes'])) {
|
||||
$form['notes'] = [
|
||||
'name' => $rcmail->gettext('notes'),
|
||||
'content' => [
|
||||
'notes' => ['type' => 'textarea', 'label' => false],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (self::$CONTACTS->groups) {
|
||||
$form['groups'] = [
|
||||
'name' => $rcmail->gettext('groups'),
|
||||
'content' => self::contact_record_groups(self::$contact['ID']),
|
||||
];
|
||||
}
|
||||
|
||||
return self::contact_form($form, self::$contact, $attrib);
|
||||
}
|
||||
|
||||
public static function render_email_value($email)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
return html::a([
|
||||
'href' => 'mailto:' . $email,
|
||||
'onclick' => sprintf(
|
||||
"return %s.command('compose','%s',this)",
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
rcube::JQ($email)
|
||||
),
|
||||
'title' => $rcmail->gettext('composeto'),
|
||||
'class' => 'email',
|
||||
],
|
||||
rcube::Q($email)
|
||||
);
|
||||
}
|
||||
|
||||
public static function render_phone_value($phone)
|
||||
{
|
||||
$attrs = [
|
||||
'href' => 'tel:' . preg_replace('/[^0-9+,;-]/', '', $phone),
|
||||
'class' => 'phone',
|
||||
];
|
||||
|
||||
return html::a($attrs, rcube::Q($phone));
|
||||
}
|
||||
|
||||
public static function render_url_value($url)
|
||||
{
|
||||
$prefix = preg_match('!^(http|ftp)s?://!', $url) ? '' : 'http://';
|
||||
|
||||
return html::a([
|
||||
'href' => $prefix . $url,
|
||||
'target' => '_blank',
|
||||
'class' => 'url',
|
||||
],
|
||||
rcube::Q($url)
|
||||
);
|
||||
}
|
||||
|
||||
public static function contact_record_groups($contact_id)
|
||||
{
|
||||
$groups = self::$CONTACTS->list_groups();
|
||||
|
||||
if (empty($groups)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$members = self::$CONTACTS->get_record_groups($contact_id);
|
||||
$table = new html_table(['tagname' => 'ul', 'cols' => 1, 'class' => 'proplist simplelist']);
|
||||
$checkbox = new html_checkbox(['name' => '_gid[]', 'class' => 'groupmember', 'disabled' => self::$CONTACTS->readonly]);
|
||||
|
||||
foreach ($groups as $group) {
|
||||
$gid = $group['ID'];
|
||||
$input = $checkbox->show(!empty($members[$gid]) ? $gid : null, ['value' => $gid]);
|
||||
$table->add(null, html::label(null, $input . rcube::Q($group['name'])));
|
||||
}
|
||||
|
||||
$hiddenfields = new html_hiddenfield(['name' => '_source', 'value' => $source]);
|
||||
$hiddenfields->add(['name' => '_cid', 'value' => $contact_id]);
|
||||
|
||||
$form_attrs = [
|
||||
'name' => 'form',
|
||||
'method' => 'post',
|
||||
'task' => $rcmail->task,
|
||||
'action' => 'save',
|
||||
'request' => 'save.' . intval($contact_id),
|
||||
'noclose' => true,
|
||||
];
|
||||
|
||||
$form_start = $rcmail->output->request_form($form_attrs, $hiddenfields->show());
|
||||
$form_end = '</form>';
|
||||
|
||||
$rcmail->output->add_gui_object('editform', 'form');
|
||||
$rcmail->output->add_label('addingmember', 'removingmember');
|
||||
|
||||
return $form_start . html::tag('fieldset', 'contactfieldgroup contactgroups', $table->show()) . $form_end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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: |
|
||||
| Undelete contacts (CIDs) from last delete action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <machniak@kolabsys.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_undo extends rcmail_action_contacts_index
|
||||
{
|
||||
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();
|
||||
$delcnt = 0;
|
||||
|
||||
if (!empty($_SESSION['contact_undo']) && !empty($_SESSION['contact_undo']['data'])) {
|
||||
foreach ((array) $_SESSION['contact_undo']['data'] as $source => $cid) {
|
||||
$contacts = self::contact_source($source);
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('contact_undelete', [
|
||||
'id' => $cid,
|
||||
'source' => $source
|
||||
]);
|
||||
|
||||
$restored = empty($plugin['abort']) ? $contacts->undelete($cid) : $plugin['result'];
|
||||
|
||||
if (!$restored) {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'contactrestoreerror';
|
||||
|
||||
$rcmail->output->show_message($error, 'error');
|
||||
$rcmail->output->command('list_contacts');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else {
|
||||
$delcnt += $restored;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->session->remove('contact_undo');
|
||||
|
||||
if ($delcnt) {
|
||||
$rcmail->output->show_message('contactrestored', 'confirmation');
|
||||
$rcmail->output->command('list_contacts');
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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: |
|
||||
| Handles contact photo uploads |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_contacts_upload_photo extends rcmail_action_contacts_index
|
||||
{
|
||||
/**
|
||||
* Supported image format types
|
||||
* ImageMagick works with other non-image types (e.g. pdf), we don't want that here
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $IMAGE_TYPES = ['jpeg','jpg','jp2','tiff','tif','bmp','eps','gif','png','png8','png24','png32','svg','ico'];
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
if (!empty($_FILES['_photo']['tmp_name'])) {
|
||||
$filepath = $_FILES['_photo']['tmp_name'];
|
||||
|
||||
// check file type and resize image
|
||||
$image = new rcube_image($_FILES['_photo']['tmp_name']);
|
||||
$imageprop = $image->props();
|
||||
|
||||
if (
|
||||
in_array(strtolower($imageprop['type']), self::$IMAGE_TYPES)
|
||||
&& $imageprop['width']
|
||||
&& $imageprop['height']
|
||||
) {
|
||||
$maxsize = intval($rcmail->config->get('contact_photo_size', 160));
|
||||
$tmpfname = rcube_utils::temp_filename('imgconvert');
|
||||
$save_hook = 'attachment_upload';
|
||||
|
||||
// scale image to a maximum size
|
||||
if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) && $image->resize($maxsize, $tmpfname)) {
|
||||
$filepath = $tmpfname;
|
||||
$save_hook = 'attachment_save';
|
||||
}
|
||||
|
||||
// save uploaded file in storage backend
|
||||
$attachment = $rcmail->plugins->exec_hook($save_hook, [
|
||||
'path' => $filepath,
|
||||
'size' => $_FILES['_photo']['size'],
|
||||
'name' => $_FILES['_photo']['name'],
|
||||
'mimetype' => 'image/' . $imageprop['type'],
|
||||
'group' => 'contact',
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$attachment = ['error' => $rcmail->gettext('invalidimageformat')];
|
||||
}
|
||||
|
||||
if (!empty($attachment['status']) && empty($attachment['abort'])) {
|
||||
$file_id = $attachment['id'];
|
||||
$_SESSION['contacts']['files'][$file_id] = $attachment;
|
||||
$rcmail->output->command('replace_contact_photo', $file_id);
|
||||
}
|
||||
else {
|
||||
// upload failed
|
||||
self::upload_error($_FILES['_photo']['error'], $attachment);
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::upload_failure();
|
||||
}
|
||||
|
||||
$rcmail->output->command('photo_upload_end');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?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: |
|
||||
| Perform OAuth2 user login |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_login_oauth extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$auth_code = rcube_utils::get_input_string('code', rcube_utils::INPUT_GET);
|
||||
$auth_error = rcube_utils::get_input_string('error', rcube_utils::INPUT_GET);
|
||||
$auth_state = rcube_utils::get_input_string('state', rcube_utils::INPUT_GET);
|
||||
|
||||
// auth code return from oauth login
|
||||
if (!empty($auth_code)) {
|
||||
$auth = $rcmail->oauth->request_access_token($auth_code, $auth_state);
|
||||
|
||||
// oauth success
|
||||
if ($auth && isset($auth['username'], $auth['authorization'], $auth['token'])) {
|
||||
// enforce XOAUTH2 auth type
|
||||
$rcmail->config->set('imap_auth_type', 'XOAUTH2');
|
||||
$rcmail->config->set('login_password_maxlen', strlen($auth['authorization']));
|
||||
|
||||
// use access_token and user info for IMAP login
|
||||
$storage_host = $rcmail->autoselect_host();
|
||||
if ($rcmail->login($auth['username'], $auth['authorization'], $storage_host, true)) {
|
||||
// replicate post-login tasks from index.php
|
||||
$rcmail->session->remove('temp');
|
||||
$rcmail->session->regenerate_id(false);
|
||||
|
||||
// send auth cookie if necessary
|
||||
$rcmail->session->set_auth_cookie();
|
||||
|
||||
// save OAuth token in session
|
||||
$_SESSION['oauth_token'] = $auth['token'];
|
||||
|
||||
// log successful login
|
||||
$rcmail->log_login();
|
||||
|
||||
// allow plugins to control the redirect url after login success
|
||||
$redir = $rcmail->plugins->exec_hook('login_after', ['_task' => 'mail']);
|
||||
unset($redir['abort'], $redir['_err']);
|
||||
|
||||
// send redirect
|
||||
header('Location: ' . $rcmail->url($redir, true, false));
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('loginfailed', 'warning');
|
||||
|
||||
// log failed login
|
||||
$error_code = $rcmail->login_error();
|
||||
$rcmail->log_login($auth['username'], true, $error_code);
|
||||
|
||||
$rcmail->plugins->exec_hook('login_failed', [
|
||||
'code' => $error_code,
|
||||
'host' => $storage_host,
|
||||
'user' => $auth['username'],
|
||||
]);
|
||||
|
||||
$rcmail->kill_session();
|
||||
// fall through -> login page
|
||||
}
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('oauthloginfailed', 'warning');
|
||||
}
|
||||
}
|
||||
// error return from oauth login
|
||||
else if (!empty($auth_error)) {
|
||||
$error_message = rcube_utils::get_input_string('error_description', rcube_utils::INPUT_GET) ?: $auth_error;
|
||||
$rcmail->output->show_message($error_message, 'warning');
|
||||
}
|
||||
// login action: redirect to `oauth_auth_uri`
|
||||
else if ($rcmail->task === 'login') {
|
||||
// this will always exit() the process
|
||||
$rcmail->oauth->login_redirect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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: |
|
||||
| Add the submitted contact to the user's address book |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_addcontact extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$address = rcube_utils::get_input_string('_address', rcube_utils::INPUT_POST, true);
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_POST);
|
||||
|
||||
// Get the default addressbook
|
||||
$CONTACTS = null;
|
||||
$SENDERS = null;
|
||||
$type = 0;
|
||||
|
||||
if ($source != rcube_addressbook::TYPE_TRUSTED_SENDER) {
|
||||
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
|
||||
$type = rcube_addressbook::TYPE_DEFAULT;
|
||||
}
|
||||
|
||||
// Get the trusted senders addressbook
|
||||
if (!empty($_POST['_reload']) || $source == rcube_addressbook::TYPE_TRUSTED_SENDER) {
|
||||
$collected_senders = $rcmail->config->get('collected_senders');
|
||||
|
||||
if (strlen($collected_senders)) {
|
||||
$type |= rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
$SENDERS = $rcmail->get_address_book($collected_senders);
|
||||
if ($CONTACTS == $SENDERS) {
|
||||
$SENDERS = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contact = rcube_mime::decode_address_list($address, 1, false);
|
||||
|
||||
if (empty($contact[1]['mailto'])) {
|
||||
$rcmail->output->show_message('errorsavingcontact', 'error', null, false);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$contact = [
|
||||
'email' => $contact[1]['mailto'],
|
||||
'name' => $contact[1]['name'],
|
||||
];
|
||||
|
||||
$email = rcube_utils::idn_to_ascii($contact['email']);
|
||||
|
||||
if (!rcube_utils::check_email($email, false)) {
|
||||
$rcmail->output->show_message('emailformaterror', 'error', ['email' => $contact['email']], false);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
if ($rcmail->contact_exists($contact['email'], $type)) {
|
||||
$rcmail->output->show_message('contactexists', 'warning');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$done = $rcmail->contact_create($contact, $SENDERS ?: $CONTACTS, $error);
|
||||
|
||||
if ($done) {
|
||||
$rcmail->output->show_message('addedsuccessfully', 'confirmation');
|
||||
|
||||
if (!empty($_POST['_reload'])) {
|
||||
$rcmail->output->command('command', 'load-remote');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message($error ?: 'errorsavingcontact', 'error', null, false);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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: |
|
||||
| Delete attachments from compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_delete extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
self::init();
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$attachment = self::get_attachment();
|
||||
|
||||
if (is_array($attachment)) {
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_delete', $attachment);
|
||||
|
||||
if (!empty($attachment['status'])) {
|
||||
$rcmail->session->remove(self::$SESSION_KEY . '.attachments.' . self::$file_id);
|
||||
$rcmail->output->command('remove_from_attachment_list', 'rcmfile' . self::$file_id);
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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: |
|
||||
| Display attachments in compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
self::init();
|
||||
self::display_uploaded_file(self::get_attachment());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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: |
|
||||
| Rename attachments in compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_rename extends rcmail_action_mail_attachment_upload
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
self::init();
|
||||
|
||||
$filename = rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST);
|
||||
$filename = trim($filename);
|
||||
|
||||
if (
|
||||
strlen($filename)
|
||||
&& ($attachment = self::get_attachment())
|
||||
&& is_array($attachment)
|
||||
) {
|
||||
$attachment['name'] = $filename;
|
||||
|
||||
$rcmail->session->remove(self::$SESSION_KEY . '.attachments. ' . self::$file_id);
|
||||
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
|
||||
|
||||
$rcmail->output->command('rename_attachment_handler', 'rcmfile' . self::$file_id, $filename);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?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: |
|
||||
| Attachment uploads handler for the compose form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_attachment_upload extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
protected static $SESSION_KEY;
|
||||
protected static $COMPOSE;
|
||||
protected static $COMPOSE_ID;
|
||||
protected static $file_id;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
self::init();
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
$uploadid = rcube_utils::get_input_string('_uploadid', rcube_utils::INPUT_GPC);
|
||||
$uri = rcube_utils::get_input_string('_uri', rcube_utils::INPUT_POST);
|
||||
|
||||
// handle dropping a reference to an attachment part of some message
|
||||
if ($uri) {
|
||||
$attachment = null;
|
||||
|
||||
$url = parse_url($uri);
|
||||
|
||||
if (!empty($url['query'])) {
|
||||
parse_str($url['query'], $params);
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($params) && isset($params['_mbox']) && strlen($params['_mbox'])
|
||||
&& !empty($params['_uid']) && !empty($params['_part'])
|
||||
) {
|
||||
// @TODO: at some point we might support drag-n-drop between
|
||||
// two different accounts on the same server, for now make sure
|
||||
// this is the same server and the same user
|
||||
list($host, $port) = rcube_utils::explode(':', $_SERVER['HTTP_HOST']);
|
||||
|
||||
if (
|
||||
$host == $url['host']
|
||||
&& $port == $url['port']
|
||||
&& $rcmail->get_user_name() == rawurldecode($url['user'])
|
||||
) {
|
||||
$message = new rcube_message($params['_uid'], $params['_mbox']);
|
||||
|
||||
if ($message && !empty($message->headers)) {
|
||||
$attachment = rcmail_action_mail_compose::save_attachment($message, $params['_part'], self::$COMPOSE_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('attachment_from_uri', [
|
||||
'attachment' => $attachment,
|
||||
'uri' => $uri,
|
||||
'compose_id' => self::$COMPOSE_ID
|
||||
]);
|
||||
|
||||
if ($plugin['attachment']) {
|
||||
self::attachment_success($plugin['attachment'], $uploadid);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('display_message', $rcmail->gettext('filelinkerror'), 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
// handle file(s) upload
|
||||
if (is_array($_FILES['_attachments']['tmp_name'])) {
|
||||
$multiple = count($_FILES['_attachments']['tmp_name']) > 1;
|
||||
$errors = [];
|
||||
|
||||
foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded attachment if there is no error
|
||||
$err = $_FILES['_attachments']['error'][$i];
|
||||
|
||||
if (!$err) {
|
||||
$filename = $_FILES['_attachments']['name'][$i];
|
||||
$filesize = $_FILES['_attachments']['size'][$i];
|
||||
$filetype = rcube_mime::file_content_type($filepath, $filename, $_FILES['_attachments']['type'][$i]);
|
||||
|
||||
if ($err = self::check_message_size($filesize, $filetype)) {
|
||||
if (!in_array($err, $errors)) {
|
||||
$rcmail->output->command('display_message', $err, 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
$errors[] = $err;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_upload', [
|
||||
'path' => $filepath,
|
||||
'name' => $filename,
|
||||
'size' => $filesize,
|
||||
'mimetype' => $filetype,
|
||||
'group' => self::$COMPOSE_ID,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$err && !empty($attachment['status']) && empty($attachment['abort'])) {
|
||||
// store new attachment in session
|
||||
unset($attachment['status'], $attachment['abort']);
|
||||
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
|
||||
|
||||
self::attachment_success($attachment, $uploadid);
|
||||
}
|
||||
else { // upload failed
|
||||
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
|
||||
$size = self::show_bytes(rcube_utils::max_upload_size());
|
||||
$msg = $rcmail->gettext(['name' => 'filesizeerror', 'vars' => ['size' => $size]]);
|
||||
}
|
||||
else if (!empty($attachment['error'])) {
|
||||
$msg = $attachment['error'];
|
||||
}
|
||||
else {
|
||||
$msg = $rcmail->gettext('fileuploaderror');
|
||||
}
|
||||
|
||||
if (!empty($attachment['error']) || $err != UPLOAD_ERR_NO_FILE) {
|
||||
if (!in_array($msg, $errors)) {
|
||||
$rcmail->output->command('display_message', $msg, 'error');
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
$errors[] = $msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (self::upload_failure()) {
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
// send html page with JS calls as response
|
||||
$rcmail->output->command('auto_save_start', false);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
|
||||
self::$COMPOSE = null;
|
||||
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;
|
||||
|
||||
if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
|
||||
self::$COMPOSE =& $_SESSION[self::$SESSION_KEY];
|
||||
}
|
||||
|
||||
if (!self::$COMPOSE) {
|
||||
die("Invalid session var!");
|
||||
}
|
||||
|
||||
self::$file_id = rcube_utils::get_input_string('_file', rcube_utils::INPUT_GPC);
|
||||
self::$file_id = preg_replace('/^rcmfile/', '', self::$file_id) ?: 'unknown';
|
||||
}
|
||||
|
||||
public static function get_attachment()
|
||||
{
|
||||
return self::$COMPOSE['attachments'][self::$file_id];
|
||||
}
|
||||
|
||||
public static function attachment_success($attachment, $uploadid)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$id = $attachment['id'];
|
||||
|
||||
if (!empty(self::$COMPOSE['deleteicon']) && is_file(self::$COMPOSE['deleteicon'])) {
|
||||
$button = html::img([
|
||||
'src' => self::$COMPOSE['deleteicon'],
|
||||
'alt' => $rcmail->gettext('delete')
|
||||
]);
|
||||
}
|
||||
else if (!empty(self::$COMPOSE['textbuttons'])) {
|
||||
$button = rcube::Q($rcmail->gettext('delete'));
|
||||
}
|
||||
else {
|
||||
$button = '';
|
||||
}
|
||||
|
||||
$link_content = sprintf(
|
||||
'<span class="attachment-name">%s</span><span class="attachment-size">(%s)</span>',
|
||||
rcube::Q($attachment['name']), self::show_bytes($attachment['size'])
|
||||
);
|
||||
|
||||
$content_link = html::a([
|
||||
'href' => "#load",
|
||||
'class' => 'filename',
|
||||
'onclick' => sprintf(
|
||||
"return %s.command('load-attachment','rcmfile%s', this, event)",
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
$id
|
||||
),
|
||||
], $link_content);
|
||||
|
||||
$delete_link = html::a([
|
||||
'href' => "#delete",
|
||||
'onclick' => sprintf(
|
||||
"return %s.command('remove-attachment','rcmfile%s', this, event)",
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
$id
|
||||
),
|
||||
'title' => $rcmail->gettext('delete'),
|
||||
'class' => 'delete',
|
||||
'aria-label' => $rcmail->gettext('delete') . ' ' . $attachment['name'],
|
||||
], $button);
|
||||
|
||||
if (!empty(self::$COMPOSE['icon_pos']) && self::$COMPOSE['icon_pos'] == 'left') {
|
||||
$content = $delete_link . $content_link;
|
||||
}
|
||||
else {
|
||||
$content = $content_link . $delete_link;
|
||||
}
|
||||
|
||||
$rcmail->output->command('add2attachment_list', "rcmfile$id", [
|
||||
'html' => $content,
|
||||
'name' => $attachment['name'],
|
||||
'mimetype' => $attachment['mimetype'],
|
||||
'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']),
|
||||
'complete' => true
|
||||
],
|
||||
$uploadid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the attached file will fit in message size limit.
|
||||
* Calculates size of all attachments and compares with the limit.
|
||||
*
|
||||
* @param int $filesize File size
|
||||
* @param string $filetype File mimetype
|
||||
*
|
||||
* @return string Error message if the limit is exceeded
|
||||
*/
|
||||
public static function check_message_size($filesize, $filetype)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$limit = parse_bytes($rcmail->config->get('max_message_size'));
|
||||
$size = 10 * 1024; // size of message body
|
||||
|
||||
if (!$limit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add size of already attached files
|
||||
if (!empty(self::$COMPOSE['attachments'])) {
|
||||
foreach ((array) self::$COMPOSE['attachments'] as $att) {
|
||||
// All attachments are base64-encoded except message/rfc822 (see sendmail.inc)
|
||||
$multip = $att['mimetype'] == 'message/rfc822' ? 1 : 1.33;
|
||||
$size += $att['size'] * $multip;
|
||||
}
|
||||
}
|
||||
|
||||
// add size of the new attachment
|
||||
$multip = $filetype == 'message/rfc822' ? 1 : 1.33;
|
||||
$size += $filesize * $multip;
|
||||
|
||||
if ($size > $limit) {
|
||||
$limit = self::show_bytes($limit);
|
||||
return $rcmail->gettext(['name' => 'msgsizeerror', 'vars' => ['size' => $limit]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?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: |
|
||||
| Perform a search on configured address books for the email |
|
||||
| address autocompletion |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_autocomplete 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();
|
||||
$MAXNUM = (int) $rcmail->config->get('autocomplete_max', 15);
|
||||
$mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$single = (bool) $rcmail->config->get('autocomplete_single');
|
||||
$search = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC, true);
|
||||
$reqid = rcube_utils::get_input_string('_reqid', rcube_utils::INPUT_GPC);
|
||||
|
||||
$contacts = [];
|
||||
|
||||
if (strlen($search) && ($book_types = self::autocomplete_addressbooks())) {
|
||||
$sort_keys = [];
|
||||
$books_num = count($book_types);
|
||||
$search_lc = mb_strtolower($search);
|
||||
$mode |= rcube_addressbook::SEARCH_GROUPS;
|
||||
$fields = $rcmail->config->get('contactlist_fields');
|
||||
|
||||
foreach ($book_types as $abook_id) {
|
||||
$abook = $rcmail->get_address_book($abook_id);
|
||||
$abook->set_pagesize($MAXNUM);
|
||||
|
||||
if ($result = $abook->search($fields, $search, $mode, true, true, 'email')) {
|
||||
while ($record = $result->iterate()) {
|
||||
// Contact can have more than one e-mail address
|
||||
$email_arr = (array) $abook->get_col_values('email', $record, true);
|
||||
$email_cnt = count($email_arr);
|
||||
$idx = 0;
|
||||
|
||||
foreach ($email_arr as $email) {
|
||||
if (empty($email)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = rcube_addressbook::compose_list_name($record);
|
||||
$contact = format_email_recipient($email, $name);
|
||||
|
||||
// skip entries that don't match
|
||||
if ($email_cnt > 1 && strpos(mb_strtolower($contact), $search_lc) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$index = $contact;
|
||||
|
||||
// skip duplicates
|
||||
if (empty($contacts[$index])) {
|
||||
$contact = [
|
||||
'name' => $contact,
|
||||
'type' => $record['_type'] ?? null,
|
||||
'id' => $record['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
$display = rcube_addressbook::compose_search_name($record, $email, $name);
|
||||
|
||||
if ($display && $display != $contact['name']) {
|
||||
$contact['display'] = $display;
|
||||
}
|
||||
|
||||
// groups with defined email address will not be expanded to its members' addresses
|
||||
if ($contact['type'] == 'group') {
|
||||
$contact['email'] = $email;
|
||||
}
|
||||
|
||||
$name = !empty($contact['display']) ? $contact['display'] : $name;
|
||||
$contacts[$index] = $contact;
|
||||
$sort_keys[$index] = sprintf('%s %03d', $name, $idx++);
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
// skip redundant entries (show only first email address)
|
||||
if ($single) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also list matching contact groups
|
||||
if ($abook->groups && count($contacts) < $MAXNUM) {
|
||||
foreach ($abook->list_groups($search, $mode) as $group) {
|
||||
$abook->reset();
|
||||
$abook->set_group($group['ID']);
|
||||
|
||||
$group_prop = $abook->get_group($group['ID']);
|
||||
|
||||
// group (distribution list) with email address(es)
|
||||
if (!empty($group_prop['email'])) {
|
||||
$idx = 0;
|
||||
foreach ((array) $group_prop['email'] as $email) {
|
||||
$index = format_email_recipient($email, $group['name']);
|
||||
|
||||
if (empty($contacts[$index])) {
|
||||
$sort_keys[$index] = sprintf('%s %03d', $group['name'] , $idx++);
|
||||
$contacts[$index] = [
|
||||
'name' => $index,
|
||||
'email' => $email,
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// show group with count
|
||||
else if (($result = $abook->count()) && $result->count) {
|
||||
if (empty($contacts[$group['name']])) {
|
||||
$sort_keys[$group['name']] = $group['name'];
|
||||
$contacts[$group['name']] = [
|
||||
'name' => $group['name'] . ' (' . intval($result->count) . ')',
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
if (count($contacts) >= $MAXNUM) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($contacts)) {
|
||||
// sort contacts index
|
||||
asort($sort_keys, SORT_LOCALE_STRING);
|
||||
// re-sort contacts according to index
|
||||
foreach ($sort_keys as $idx => $val) {
|
||||
$sort_keys[$idx] = $contacts[$idx];
|
||||
}
|
||||
$contacts = array_values($sort_keys);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow autocomplete result optimization via plugin
|
||||
$plugin = $rcmail->plugins->exec_hook('contacts_autocomplete_after', [
|
||||
'search' => $search,
|
||||
// Provide already-found contacts to plugin if they are required
|
||||
'contacts' => $contacts,
|
||||
]);
|
||||
|
||||
$contacts = $plugin['contacts'];
|
||||
|
||||
$rcmail->output->command('ksearch_query_results', $contacts, $search, $reqid);
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect addressbook sources used for autocompletion
|
||||
*/
|
||||
public static function autocomplete_addressbooks()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
|
||||
if (strlen($source)) {
|
||||
$book_types = [$source];
|
||||
}
|
||||
else {
|
||||
$book_types = (array) $rcmail->config->get('autocomplete_addressbooks', 'sql');
|
||||
}
|
||||
|
||||
$collected_recipients = $rcmail->config->get('collected_recipients');
|
||||
$collected_senders = $rcmail->config->get('collected_senders');
|
||||
|
||||
if (strlen($collected_recipients) && !in_array($collected_recipients, $book_types)) {
|
||||
$book_types[] = $collected_recipients;
|
||||
}
|
||||
|
||||
if (strlen($collected_senders) && !in_array($collected_senders, $book_types)) {
|
||||
$book_types[] = $collected_senders;
|
||||
}
|
||||
|
||||
return !empty($book_types) ? $book_types : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?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: |
|
||||
| Bounce/resend an email message |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_bounce extends rcmail_action
|
||||
{
|
||||
protected static $MESSAGE;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$msg_uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
|
||||
$msg_folder = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GP, true);
|
||||
$MESSAGE = new rcube_message($msg_uid, $msg_folder);
|
||||
|
||||
self::$MESSAGE = $MESSAGE;
|
||||
|
||||
if (!$MESSAGE->headers) {
|
||||
$rcmail->output->show_message('messageopenerror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
// Display Bounce form
|
||||
if (empty($_POST)) {
|
||||
if (!empty($MESSAGE->headers->charset)) {
|
||||
$rcmail->storage->set_charset($MESSAGE->headers->charset);
|
||||
}
|
||||
|
||||
// Initialize helper class to build the UI
|
||||
$SENDMAIL = new rcmail_sendmail(
|
||||
['mode' => rcmail_sendmail::MODE_FORWARD],
|
||||
['message' => $MESSAGE]
|
||||
);
|
||||
|
||||
$rcmail->output->set_env('mailbox', $msg_folder);
|
||||
$rcmail->output->set_env('uid', $msg_uid);
|
||||
$rcmail->output->add_handler('bounceobjects', [$this, 'bounce_objects']);
|
||||
$rcmail->output->send('bounce');
|
||||
}
|
||||
|
||||
// Initialize helper class to send the message
|
||||
$SENDMAIL = new rcmail_sendmail(
|
||||
['mode' => rcmail_sendmail::MODE_FORWARD],
|
||||
[
|
||||
'sendmail' => true,
|
||||
'error_handler' => function(...$args) use ($rcmail) {
|
||||
call_user_func_array([$rcmail->output, 'show_message'], $args);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Handle the form input
|
||||
$input_headers = $SENDMAIL->headers_input();
|
||||
|
||||
// Set Resent-* headers, these will be added on top of the bounced message
|
||||
$headers = [];
|
||||
foreach (['From', 'To', 'Cc', 'Bcc', 'Date', 'Message-ID'] as $name) {
|
||||
if (!empty($input_headers[$name])) {
|
||||
$headers['Resent-' . $name] = $input_headers[$name];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the bounce message
|
||||
$BOUNCE = new rcmail_resend_mail([
|
||||
'bounce_message' => $MESSAGE,
|
||||
'bounce_headers' => $headers,
|
||||
]);
|
||||
|
||||
// Send the bounce message
|
||||
$SENDMAIL->deliver_message($BOUNCE);
|
||||
|
||||
// Save in Sent (if requested)
|
||||
$saved = $SENDMAIL->save_message($BOUNCE);
|
||||
|
||||
if (!$saved && strlen($SENDMAIL->options['store_target'])) {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('messagesent', 'confirmation', null, false);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for template object 'bounceObjects'
|
||||
*
|
||||
* @param array $attrib HTML attributes
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
public static function bounce_objects($attrib)
|
||||
{
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'bounce-objects';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$content = [];
|
||||
|
||||
// Always display a hint about the bounce feature behavior
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('bouncehint')));
|
||||
$msg_attrib = ['id' => 'bounce-hint', 'class' => 'boxinformation'];
|
||||
$content[] = html::div($msg_attrib, $msg);
|
||||
|
||||
// Add a warning about Bcc recipients
|
||||
if (self::$MESSAGE->headers->get('bcc', false) || self::$MESSAGE->headers->get('resent-bcc', false)) {
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('bccemail')));
|
||||
$msg_attrib = ['id' => 'bcc-warning', 'class' => 'boxwarning'];
|
||||
$content[] = html::div($msg_attrib, $msg);
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('bounce_objects',
|
||||
['content' => $content, 'message' => self::$MESSAGE]);
|
||||
|
||||
$content = implode("\n", $plugin['content']);
|
||||
|
||||
return $content ? html::div($attrib, $content) : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?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: |
|
||||
| Check for recent messages, in all mailboxes |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_check_recent extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// If there's no folder or messages list, there's nothing to update
|
||||
// This can happen on 'refresh' request
|
||||
if (empty($_POST['_folderlist']) && empty($_POST['_list'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$current = $rcmail->storage->get_folder();
|
||||
$check_all = $rcmail->action != 'refresh' || (bool) $rcmail->config->get('check_all_folders');
|
||||
$page = $rcmail->storage->get_page();
|
||||
$page_size = $rcmail->storage->get_pagesize();
|
||||
|
||||
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
|
||||
if ($search_request && $_SESSION['search_request'] != $search_request) {
|
||||
$search_request = null;
|
||||
}
|
||||
|
||||
// list of folders to check
|
||||
if ($check_all) {
|
||||
$a_mailboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
}
|
||||
else if ($search_request && isset($_SESSION['search'][1]) && is_object($_SESSION['search'][1])) {
|
||||
$a_mailboxes = (array) $_SESSION['search'][1]->get_parameters('MAILBOX');
|
||||
}
|
||||
else {
|
||||
$a_mailboxes = (array) $current;
|
||||
if ($current != 'INBOX') {
|
||||
$a_mailboxes[] = 'INBOX';
|
||||
}
|
||||
}
|
||||
|
||||
// Control folders list from a plugin
|
||||
$plugin = $rcmail->plugins->exec_hook('check_recent', ['folders' => $a_mailboxes, 'all' => $check_all]);
|
||||
$a_mailboxes = $plugin['folders'];
|
||||
$list_cleared = false;
|
||||
|
||||
self::storage_fatal_error();
|
||||
|
||||
// check recent/unseen counts
|
||||
foreach ($a_mailboxes as $mbox_name) {
|
||||
$is_current = $mbox_name == $current
|
||||
|| (
|
||||
!empty($search_request)
|
||||
&& isset($_SESSION['search'][1])
|
||||
&& is_object($_SESSION['search'][1])
|
||||
&& in_array($mbox_name, (array)$_SESSION['search'][1]->get_parameters('MAILBOX'))
|
||||
);
|
||||
|
||||
if ($is_current) {
|
||||
// Synchronize mailbox cache, handle flag changes
|
||||
$rcmail->storage->folder_sync($mbox_name);
|
||||
}
|
||||
|
||||
// Get mailbox status
|
||||
$status = $rcmail->storage->folder_status($mbox_name, $diff);
|
||||
|
||||
if ($is_current) {
|
||||
self::storage_fatal_error();
|
||||
}
|
||||
|
||||
if ($status & 1) {
|
||||
// trigger plugin hook
|
||||
$rcmail->plugins->exec_hook('new_messages', [
|
||||
'mailbox' => $mbox_name,
|
||||
'is_current' => $is_current,
|
||||
'diff' => $diff
|
||||
]);
|
||||
}
|
||||
|
||||
self::send_unread_count($mbox_name, true, null, (!$is_current && ($status & 1)) ? 'recent' : '');
|
||||
|
||||
if ($status && $is_current) {
|
||||
// refresh saved search set
|
||||
if (!empty($search_request) && isset($_SESSION['search'])) {
|
||||
unset($search_request); // only do this once
|
||||
$_SESSION['search'] = $rcmail->storage->refresh_search();
|
||||
if (!empty($_SESSION['search'][1]->multi)) {
|
||||
$mbox_name = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_POST['_quota'])) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox_name));
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('exists', $rcmail->storage->count($mbox_name, 'EXISTS', true));
|
||||
|
||||
// "No-list" mode, don't get messages
|
||||
if (empty($_POST['_list'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get overall message count; allow caching because rcube_storage::folder_status()
|
||||
// did a refresh but only in list mode
|
||||
$list_mode = $rcmail->storage->get_threading() ? 'THREADS' : 'ALL';
|
||||
$all_count = $rcmail->storage->count($mbox_name, $list_mode, $list_mode == 'THREADS', false);
|
||||
|
||||
// check current page if we're not on the first page
|
||||
if ($all_count && $page > 1) {
|
||||
$remaining = $all_count - $page_size * ($page - 1);
|
||||
if ($remaining <= 0) {
|
||||
$page -= 1;
|
||||
$rcmail->storage->set_page($page);
|
||||
$_SESSION['page'] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('messagecount', $all_count);
|
||||
$rcmail->output->set_env('pagecount', ceil($all_count/$page_size));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($all_count), $mbox_name);
|
||||
$rcmail->output->set_env('current_page', $all_count ? $page : 1);
|
||||
|
||||
// remove old rows (and clear selection if new list is empty)
|
||||
$rcmail->output->command('message_list.clear', $all_count ? false : true);
|
||||
|
||||
if ($all_count) {
|
||||
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
|
||||
// add message rows
|
||||
self::js_message_list($a_headers, false);
|
||||
// remove messages that don't exists from list selection array
|
||||
$rcmail->output->command('update_selection');
|
||||
}
|
||||
|
||||
$list_cleared = true;
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox_name === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox_name, 'EXISTS', true));
|
||||
}
|
||||
}
|
||||
|
||||
// handle flag updates
|
||||
if (!$list_cleared) {
|
||||
$uids = rcube_utils::get_input_value('_uids', rcube_utils::INPUT_POST);
|
||||
$uids = self::get_uids($uids, null, $multifolder);
|
||||
|
||||
$recent_flags = [];
|
||||
|
||||
foreach ($uids as $mbox_name => $set) {
|
||||
$get_flags = true;
|
||||
$modseq = null;
|
||||
|
||||
if ($mbox_name == $current) {
|
||||
$data = $rcmail->storage->folder_data($mbox_name);
|
||||
$modseq = !empty($_SESSION['list_mod_seq']) ? $_SESSION['list_mod_seq'] : null;
|
||||
$get_flags = empty($modseq) || empty($data['HIGHESTMODSEQ']) || $modseq != $data['HIGHESTMODSEQ'];
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider HIGHESTMODSEQ for all folders in multifolder search, otherwise
|
||||
// flags for all messages in a set are requested on every refresh
|
||||
|
||||
if ($get_flags) {
|
||||
$flags = $rcmail->storage->list_flags($mbox_name, $set, $modseq);
|
||||
|
||||
foreach ($flags as $idx => $row) {
|
||||
if ($multifolder) {
|
||||
$idx .= '-' . $mbox_name;
|
||||
}
|
||||
$recent_flags[$idx] = array_change_key_case(array_map('intval', $row));
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('recent_flags', $recent_flags);
|
||||
}
|
||||
}
|
||||
|
||||
// trigger refresh hook
|
||||
$rcmail->plugins->exec_hook('refresh', []);
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
<?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: |
|
||||
| Copy the submitted messages to a specific mailbox |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_copy extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// copy messages
|
||||
if (empty($_POST['_uid']) || !isset($_POST['_target_mbox']) || !strlen($_POST['_target_mbox'])) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
}
|
||||
|
||||
$uids = self::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST);
|
||||
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
|
||||
$sources = [];
|
||||
$copied = false;
|
||||
|
||||
foreach ($uids as $mbox => $uids) {
|
||||
if ($mbox === $target) {
|
||||
$copied++;
|
||||
}
|
||||
else {
|
||||
$copied += (int) $rcmail->storage->copy_message($uids, $target, $mbox);
|
||||
$sources[] = $mbox;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$copied) {
|
||||
self::display_server_error('errorcopying');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('messagecopied', 'confirmation');
|
||||
|
||||
self::send_unread_count($target, true);
|
||||
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?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: |
|
||||
| Handler for mail delete operation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_delete extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
|
||||
// count messages before changing anything
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$sources = [];
|
||||
$old_count = 0;
|
||||
$deleted = 0;
|
||||
$count = 0;
|
||||
|
||||
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
|
||||
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
}
|
||||
|
||||
if (empty($_POST['_uid'])) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
|
||||
$deleted += (int) $rcmail->storage->delete_message($uids, $mbox);
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
$sources[] = $mbox;
|
||||
}
|
||||
|
||||
if (empty($deleted)) {
|
||||
// send error message
|
||||
if ($_POST['_from'] != 'show') {
|
||||
$rcmail->output->command('list_mailbox');
|
||||
}
|
||||
|
||||
self::display_server_error('errordeleting');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('messagedeleted', 'confirmation');
|
||||
}
|
||||
|
||||
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
|
||||
|
||||
// refresh saved search set after moving some messages
|
||||
if ($search_request && $rcmail->storage->get_search_set()) {
|
||||
$_SESSION['search'] = $rcmail->storage->refresh_search();
|
||||
}
|
||||
|
||||
if (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
|
||||
if ($next = rcube_utils::get_input_string('_next_uid', rcube_utils::INPUT_GPC)) {
|
||||
$rcmail->output->command('show_message', $next);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('command', 'list');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$mbox = $rcmail->storage->get_folder();
|
||||
$msg_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
$exists = $rcmail->storage->count($mbox, 'EXISTS', true);
|
||||
$page_size = $rcmail->storage->get_pagesize();
|
||||
$page = $rcmail->storage->get_page();
|
||||
$pages = ceil($msg_count / $page_size);
|
||||
$nextpage_count = $old_count - $page_size * $page;
|
||||
$remaining = $msg_count - $page_size * ($page - 1);
|
||||
$jump_back = false;
|
||||
|
||||
// jump back one page (user removed the whole last page)
|
||||
if ($page > 1 && $remaining == 0) {
|
||||
$page -= 1;
|
||||
$rcmail->storage->set_page($page);
|
||||
$_SESSION['page'] = $page;
|
||||
$jump_back = true;
|
||||
}
|
||||
|
||||
// update unseen messages counts for all involved folders
|
||||
foreach ($sources as $source) {
|
||||
self::send_unread_count($source, true);
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('messagecount', $msg_count);
|
||||
$rcmail->output->set_env('current_page', $page);
|
||||
$rcmail->output->set_env('pagecount', $pages);
|
||||
$rcmail->output->set_env('exists', $exists);
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
|
||||
|
||||
if ($threading) {
|
||||
$count = rcube_utils::get_input_string('_count', rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if (!empty($count) && $_POST['_uid'] != '*' && ($jump_back || $nextpage_count > 0)) {
|
||||
// #5862: Don't add more rows than it was on the next page
|
||||
$count = $jump_back ? null : min($nextpage_count, $count);
|
||||
|
||||
$a_headers = $rcmail->storage->list_messages($mbox, null, self::sort_column(), self::sort_order(), $count);
|
||||
|
||||
self::js_message_list($a_headers, false);
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $exists);
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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: |
|
||||
| Implement folder EXPUNGE request |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_folder_expunge extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
$success = $rcmail->storage->expunge_folder($mbox);
|
||||
|
||||
// reload message list if current mailbox
|
||||
if ($success) {
|
||||
$rcmail->output->show_message('folderexpunged', 'confirmation');
|
||||
|
||||
if (!empty($_REQUEST['_reload'])) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
|
||||
$rcmail->output->command('message_list.clear');
|
||||
$rcmail->action = 'list';
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::display_server_error();
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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: |
|
||||
| Implement folder PURGE request |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_folder_purge extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
$trash_mbox = $rcmail->config->get('trash_mbox');
|
||||
$trash_regexp = '/^' . preg_quote($trash_mbox . $delimiter, '/') . '/';
|
||||
|
||||
// we should only be purging trash (or their subfolders)
|
||||
if (!strlen($trash_mbox) || $mbox === $trash_mbox || preg_match($trash_regexp, $mbox)) {
|
||||
$success = $storage->delete_message('*', $mbox);
|
||||
$delete = true;
|
||||
}
|
||||
// move to Trash
|
||||
else {
|
||||
$success = $storage->move_message('1:*', $trash_mbox, $mbox);
|
||||
$delete = false;
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$rcmail->output->show_message('folderpurged', 'confirmation');
|
||||
$rcmail->output->command('set_unread_count', $mbox, 0);
|
||||
self::set_unseen_count($mbox, 0);
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash_mbox) {
|
||||
$rcmail->output->command('set_trash_count', 0);
|
||||
}
|
||||
else if (strlen($trash_mbox)) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash_mbox, 'EXISTS'));
|
||||
}
|
||||
|
||||
if (!$delete && strlen($trash_mbox)) {
|
||||
self::send_unread_count($trash_mbox, true);
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['_reload'])) {
|
||||
$rcmail->output->set_env('messagecount', 0);
|
||||
$rcmail->output->set_env('pagecount', 0);
|
||||
$rcmail->output->set_env('exists', 0);
|
||||
$rcmail->output->command('message_list.clear');
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text(), $mbox);
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::display_server_error();
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?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: |
|
||||
| Delivering a specific uploaded file or mail message attachment |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_get extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $attachment;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// This resets X-Frame-Options for framed output (#6688)
|
||||
$rcmail->output->page_headers();
|
||||
|
||||
// show loading page
|
||||
if (!empty($_GET['_preload'])) {
|
||||
unset($_GET['_preload']);
|
||||
unset($_GET['_safe']);
|
||||
|
||||
$url = $rcmail->url($_GET + ['_mimewarning' => 1, '_embed' => 1]);
|
||||
$message = $rcmail->gettext('loadingdata');
|
||||
|
||||
header('Content-Type: text/html; charset=' . RCUBE_CHARSET);
|
||||
print "<html>\n<head>\n"
|
||||
. '<meta http-equiv="refresh" content="0; url='.rcube::Q($url).'">' . "\n"
|
||||
. '<meta http-equiv="content-type" content="text/html; charset=' . RCUBE_CHARSET . '">' . "\n"
|
||||
. "</head>\n<body>\n$message\n</body>\n</html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
$attachment = new rcmail_attachment_handler;
|
||||
$mimetype = $attachment->mimetype;
|
||||
$filename = $attachment->filename;
|
||||
|
||||
self::$attachment = $attachment;
|
||||
|
||||
// show part page
|
||||
if (!empty($_GET['_frame'])) {
|
||||
$rcmail->output->set_pagetitle($filename);
|
||||
|
||||
// register UI objects
|
||||
$rcmail->output->add_handlers([
|
||||
'messagepartframe' => [$this, 'message_part_frame'],
|
||||
'messagepartcontrols' => [$this, 'message_part_controls'],
|
||||
]);
|
||||
|
||||
$part_id = rcube_utils::get_input_string('_part', rcube_utils::INPUT_GET);
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
|
||||
// message/rfc822 preview (Note: handle also multipart/ parts, they can
|
||||
// come from Enigma, which replaces message/rfc822 with real mimetype)
|
||||
if ($part_id && ($mimetype == 'message/rfc822' || strpos($mimetype, 'multipart/') === 0)) {
|
||||
$uid = preg_replace('/\.[0-9.]+/', '', $uid);
|
||||
$uid .= '.' . $part_id;
|
||||
|
||||
$rcmail->output->set_env('is_message', true);
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('mailbox', $rcmail->storage->get_folder());
|
||||
$rcmail->output->set_env('uid', $uid);
|
||||
$rcmail->output->set_env('part', $part_id);
|
||||
$rcmail->output->set_env('filename', $filename);
|
||||
$rcmail->output->set_env('mimetype', $mimetype);
|
||||
|
||||
$rcmail->output->send('messagepart');
|
||||
}
|
||||
|
||||
// render thumbnail of an image attachment
|
||||
if (!empty($_GET['_thumb']) && $attachment->is_valid()) {
|
||||
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
|
||||
$file_ident = $attachment->ident;
|
||||
$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) && $attachment->body_to_file($orig_name = rcube_utils::temp_filename('attmnt'))) {
|
||||
$image = new rcube_image($orig_name);
|
||||
|
||||
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
|
||||
$mimetype = 'image/' . $imgtype;
|
||||
}
|
||||
else {
|
||||
// Resize failed, we need to check the file mimetype
|
||||
// So, we do not exit here, but goto generic file body handler below
|
||||
$_GET['_thumb'] = 0;
|
||||
$_REQUEST['_embed'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_GET['_thumb'])) {
|
||||
if (is_file($cache_file)) {
|
||||
$rcmail->output->future_expire_header(3600);
|
||||
header('Content-Type: ' . $mimetype);
|
||||
header('Content-Length: ' . filesize($cache_file));
|
||||
readfile($cache_file);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle attachment body (display or download)
|
||||
if (empty($_GET['_thumb']) && $attachment->is_valid()) {
|
||||
// require CSRF protected url for downloads
|
||||
if (!empty($_GET['_download'])) {
|
||||
$rcmail->request_security_check(rcube_utils::INPUT_GET);
|
||||
}
|
||||
|
||||
$extensions = rcube_mime::get_mime_extensions($mimetype);
|
||||
|
||||
// compare file mimetype with the stated content-type headers and file extension to avoid malicious operations
|
||||
if (!empty($_REQUEST['_embed']) && empty($_REQUEST['_nocheck'])) {
|
||||
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
|
||||
// 1. compare filename suffix with expected suffix derived from mimetype
|
||||
$valid = $file_extension && in_array($file_extension, (array)$extensions)
|
||||
|| empty($extensions)
|
||||
|| !empty($_REQUEST['_mimeclass']);
|
||||
|
||||
// 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension
|
||||
if ($valid || !$file_extension || $mimetype == 'application/octet-stream' || stripos($mimetype, 'text/') === 0) {
|
||||
$tmp_body = $attachment->body(2048);
|
||||
|
||||
// detect message part mimetype
|
||||
$real_mimetype = rcube_mime::file_content_type($tmp_body, $filename, $mimetype, true, true);
|
||||
list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype);
|
||||
|
||||
// accept text/plain with any extension
|
||||
if ($real_mimetype == 'text/plain' && self::mimetype_compare($real_mimetype, $mimetype)) {
|
||||
$valid_extension = true;
|
||||
}
|
||||
// ignore differences in text/* mimetypes. Filetype detection isn't very reliable here
|
||||
else if ($real_ctype_primary == 'text' && strpos($mimetype, $real_ctype_primary) === 0) {
|
||||
$real_mimetype = $mimetype;
|
||||
$valid_extension = true;
|
||||
}
|
||||
// ignore filename extension if mimeclass matches (#1489029)
|
||||
else if (!empty($_REQUEST['_mimeclass']) && $real_ctype_primary == $_REQUEST['_mimeclass']) {
|
||||
$valid_extension = true;
|
||||
}
|
||||
else {
|
||||
// get valid file extensions
|
||||
$extensions = rcube_mime::get_mime_extensions($real_mimetype);
|
||||
$valid_extension = !$file_extension || empty($extensions) || in_array($file_extension, (array)$extensions);
|
||||
}
|
||||
|
||||
if (
|
||||
// fix mimetype for files wrongly declared as octet-stream
|
||||
($mimetype == 'application/octet-stream' && $valid_extension)
|
||||
// force detected mimetype for images (#8158)
|
||||
|| (strpos($real_mimetype, 'image/') === 0)
|
||||
) {
|
||||
$mimetype = $real_mimetype;
|
||||
}
|
||||
|
||||
// "fix" real mimetype the same way the original is before comparison
|
||||
$real_mimetype = rcube_mime::fix_mimetype($real_mimetype);
|
||||
|
||||
$valid = $valid_extension && self::mimetype_compare($real_mimetype, $mimetype);
|
||||
}
|
||||
else {
|
||||
$real_mimetype = $mimetype;
|
||||
}
|
||||
|
||||
// show warning if validity checks failed
|
||||
if (!$valid) {
|
||||
// send blocked.gif for expected images
|
||||
if (empty($_REQUEST['_mimewarning']) && strpos($mimetype, 'image/') === 0) {
|
||||
// Do not cache. Failure might be the result of a misconfiguration,
|
||||
// thus real content should be returned once fixed.
|
||||
$content = self::get_resource_content('blocked.gif');
|
||||
$rcmail->output->nocacheing_headers();
|
||||
header("Content-Type: image/gif");
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
header("Content-Length: " . strlen($content));
|
||||
echo $content;
|
||||
}
|
||||
// html warning with a button to load the file anyway
|
||||
else {
|
||||
$rcmail->output = new rcmail_html_page();
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext([
|
||||
'name' => 'attachmentvalidationerror',
|
||||
'vars' => [
|
||||
'expected' => $mimetype . (!empty($file_extension) ? rcube::Q(" (.{$file_extension})") : ''),
|
||||
'detected' => $real_mimetype . (!empty($extensions[0]) ? " (.{$extensions[0]})" : ''),
|
||||
]
|
||||
]),
|
||||
$rcmail->gettext('showanyway'),
|
||||
$rcmail->url(array_merge($_GET, ['_nocheck' => 1]))
|
||||
);
|
||||
|
||||
$rcmail->output->write();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// TIFF/WEBP to JPEG conversion, if needed
|
||||
foreach (['tiff', 'webp'] as $type) {
|
||||
$img_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps'][$type]);
|
||||
if (
|
||||
!empty($_REQUEST['_embed'])
|
||||
&& !$img_support
|
||||
&& $attachment->image_type() == 'image/' . $type
|
||||
&& rcube_image::is_convertable('image/' . $type)
|
||||
) {
|
||||
$convert2jpeg = true;
|
||||
$mimetype = 'image/jpeg';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// deliver part content
|
||||
if ($mimetype == 'text/html' && empty($_GET['_download'])) {
|
||||
$rcmail->output = new rcmail_html_page();
|
||||
$out = '';
|
||||
|
||||
// Check if we have enough memory to handle the message in it
|
||||
// #1487424: we need up to 10x more memory than the body
|
||||
if (!rcube_utils::mem_check($attachment->size * 10)) {
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext('messagetoobig'),
|
||||
$rcmail->gettext('download'),
|
||||
$rcmail->url(array_merge($_GET, ['_download' => 1]))
|
||||
);
|
||||
}
|
||||
else {
|
||||
// render HTML body
|
||||
$out = $attachment->html();
|
||||
|
||||
// insert remote objects warning into HTML body
|
||||
if (self::$REMOTE_OBJECTS) {
|
||||
$rcmail->output->register_inline_warning(
|
||||
$rcmail->gettext('blockedresources'),
|
||||
$rcmail->gettext('allow'),
|
||||
$rcmail->url(array_merge($_GET, ['_safe' => 1]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->write($out);
|
||||
exit;
|
||||
}
|
||||
|
||||
// add filename extension if missing
|
||||
if (!pathinfo($filename, PATHINFO_EXTENSION) && ($extensions = rcube_mime::get_mime_extensions($mimetype))) {
|
||||
$filename .= '.' . $extensions[0];
|
||||
}
|
||||
|
||||
$rcmail->output->download_headers($filename, [
|
||||
'type' => $mimetype,
|
||||
'type_charset' => $attachment->charset,
|
||||
'disposition' => !empty($_GET['_download']) ? 'attachment' : 'inline',
|
||||
]);
|
||||
|
||||
// handle tiff to jpeg conversion
|
||||
if (!empty($convert2jpeg)) {
|
||||
$file_path = rcube_utils::temp_filename('attmnt');
|
||||
|
||||
// convert image to jpeg and send it to the browser
|
||||
if ($attachment->body_to_file($file_path)) {
|
||||
$image = new rcube_image($file_path);
|
||||
if ($image->convert(rcube_image::TYPE_JPG, $file_path)) {
|
||||
header("Content-Length: " . filesize($file_path));
|
||||
readfile($file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$attachment->output($mimetype);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// if we arrive here, the requested part was not found
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two mimetype strings with making sure that
|
||||
* e.g. image/bmp and image/x-ms-bmp are treated as equal.
|
||||
*/
|
||||
public static function mimetype_compare($type1, $type2)
|
||||
{
|
||||
$regexp = '~/(x-ms-|x-)~';
|
||||
$type1 = preg_replace($regexp, '/', $type1);
|
||||
$type2 = preg_replace($regexp, '/', $type2);
|
||||
|
||||
return $type1 === $type2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment properties table
|
||||
*/
|
||||
public static function message_part_controls($attrib)
|
||||
{
|
||||
if (!self::$attachment->is_valid()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('namex')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->filename));
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('type')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->mimetype));
|
||||
|
||||
$table->add('title', rcube::Q($rcmail->gettext('size')).':');
|
||||
$table->add('header', rcube::Q(self::$attachment->size()));
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment preview frame
|
||||
*/
|
||||
public static function message_part_frame($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($rcmail->output->get_env('is_message')) {
|
||||
$url = [
|
||||
'task' => 'mail',
|
||||
'action' => 'preview',
|
||||
'uid' => $rcmail->output->get_env('uid'),
|
||||
'mbox' => $rcmail->output->get_env('mailbox'),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$mimetype = $rcmail->output->get_env('mimetype');
|
||||
$url = $_GET;
|
||||
$url[strpos($mimetype, 'text/') === 0 ? '_embed' : '_preload'] = 1;
|
||||
unset($url['_frame']);
|
||||
}
|
||||
|
||||
$url['_framed'] = 1; // For proper X-Frame-Options:deny handling
|
||||
|
||||
$attrib['src'] = $rcmail->url($url);
|
||||
|
||||
$rcmail->output->add_gui_object('messagepartframe', $attrib['id']);
|
||||
|
||||
return html::iframe($attrib);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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: |
|
||||
| Check all mailboxes for unread messages and update GUI |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_getunread extends rcmail_action_mail_index
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$a_folders = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
|
||||
if (!empty($a_folders)) {
|
||||
$current = $rcmail->storage->get_folder();
|
||||
$inbox = $current == 'INBOX';
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$check_all = (bool) $rcmail->config->get('check_all_folders');
|
||||
|
||||
foreach ($a_folders as $mbox) {
|
||||
$unseen_old = self::get_unseen_count($mbox);
|
||||
|
||||
if (!$check_all && $unseen_old !== null && $mbox != $current) {
|
||||
$unseen = $unseen_old;
|
||||
}
|
||||
else {
|
||||
$unseen = $rcmail->storage->count($mbox, 'UNSEEN', $unseen_old === null);
|
||||
}
|
||||
|
||||
// call it always for current folder, so it can update counter
|
||||
// after possible message status change when opening a message
|
||||
// not in preview frame
|
||||
if ($unseen || $unseen_old === null || $mbox == $current) {
|
||||
$rcmail->output->command('set_unread_count', $mbox, $unseen, $inbox && $mbox == 'INBOX');
|
||||
}
|
||||
|
||||
self::set_unseen_count($mbox, $unseen);
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox, 'EXISTS'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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: |
|
||||
| Expand addressbook group into list of email addresses |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_group_expand 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();
|
||||
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET);
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$abook = $rcmail->get_address_book($source);
|
||||
|
||||
if ($gid && $abook) {
|
||||
$abook->set_group($gid);
|
||||
$abook->set_pagesize(9999); // TODO: limit number of group members by config?
|
||||
|
||||
$result = $abook->list_records($rcmail->config->get('contactlist_fields'));
|
||||
$members = [];
|
||||
|
||||
while ($result && ($record = $result->iterate())) {
|
||||
$email = array_first((array) $abook->get_col_values('email', $record, true));
|
||||
if (!empty($email)) {
|
||||
$members[] = format_email_recipient($email, rcube_addressbook::compose_list_name($record));
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->command('replace_group_recipients', $gid, join(', ', array_unique($members)));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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: |
|
||||
| Fetch message headers in raw format for display |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_headers extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $source;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
|
||||
$inline = $rcmail->output instanceof rcmail_output_html;
|
||||
|
||||
if (!$uid) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($pos = strpos($uid, '.')) {
|
||||
$message = new rcube_message($uid);
|
||||
$source = $message->get_part_body(substr($uid, $pos + 1));
|
||||
$source = substr($source, 0, strpos($source, "\r\n\r\n"));
|
||||
}
|
||||
else {
|
||||
$source = $rcmail->storage->get_raw_headers($uid);
|
||||
}
|
||||
|
||||
if ($source !== false) {
|
||||
$source = trim(rcube_charset::clean($source));
|
||||
$source = htmlspecialchars($source, ENT_COMPAT | ENT_HTML401, RCUBE_CHARSET);
|
||||
$source = preg_replace(
|
||||
[
|
||||
'/\n[\t\s]+/',
|
||||
'/^([a-z0-9_:-]+)/im',
|
||||
'/\r?\n/'
|
||||
],
|
||||
[
|
||||
"\n ",
|
||||
'<font class="bold">\1</font>',
|
||||
'<br />'
|
||||
],
|
||||
$source
|
||||
);
|
||||
|
||||
self::$source = $source;
|
||||
|
||||
$rcmail->output->add_handlers(['dialogcontent' => [$this, 'headers_output']]);
|
||||
|
||||
if ($inline) {
|
||||
$rcmail->output->set_env('dialog_class', 'text-nowrap');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('set_headers', $source);
|
||||
}
|
||||
}
|
||||
else if (!$inline) {
|
||||
$rcmail->output->show_message('messageopenerror', 'error');
|
||||
}
|
||||
|
||||
$rcmail->output->send($inline ? 'dialog' : null);
|
||||
}
|
||||
|
||||
public static function headers_output()
|
||||
{
|
||||
return self::$source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?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 the uploaded file(s) as messages to the current IMAP folder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_import extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
if (!empty($_FILES['_file']) && is_array($_FILES['_file'])) {
|
||||
$imported = 0;
|
||||
$folder = $rcmail->storage->get_folder();
|
||||
|
||||
foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded file if there is no error
|
||||
$err = $_FILES['_file']['error'][$i];
|
||||
|
||||
if (!$err) {
|
||||
// check file content type first
|
||||
$ctype = rcube_mime::file_content_type($filepath, $_FILES['_file']['name'][$i], $_FILES['_file']['type'][$i]);
|
||||
list($mtype_primary, $mtype_secondary) = explode('/', $ctype);
|
||||
|
||||
if (in_array($ctype, ['application/zip', 'application/x-zip'])) {
|
||||
$filepath = self::zip_extract($filepath);
|
||||
if (empty($filepath)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!in_array($mtype_primary, ['text', 'message'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ((array) $filepath as $file) {
|
||||
// read the first few lines to detect header-like structure
|
||||
$fp = fopen($file, 'r');
|
||||
do {
|
||||
$line = fgets($fp);
|
||||
}
|
||||
while ($line !== false && trim($line) == '');
|
||||
|
||||
if (!preg_match('/^From .+/', $line) && !preg_match('/^[a-z-_]+:\s+.+/i', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = $lastline = '';
|
||||
fseek($fp, 0);
|
||||
|
||||
while (($line = fgets($fp)) !== false) {
|
||||
// importing mbox file, split by From - lines
|
||||
if ($lastline === '' && strncmp($line, 'From ', 5) === 0 && strlen($line) > 5) {
|
||||
if (!empty($message)) {
|
||||
$imported += (int) self::save_message($folder, $message);
|
||||
}
|
||||
|
||||
$message = $line;
|
||||
$lastline = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
$message .= $line;
|
||||
$lastline = rtrim($line);
|
||||
}
|
||||
|
||||
if (!empty($message)) {
|
||||
$imported += (int) self::save_message($folder, $message);
|
||||
}
|
||||
|
||||
// remove temp files extracted from zip
|
||||
if (is_array($filepath)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::upload_error($err);
|
||||
}
|
||||
}
|
||||
|
||||
if ($imported) {
|
||||
$rcmail->output->show_message($rcmail->gettext(['name' => 'importmessagesuccess', 'nr' => $imported, 'vars' => ['nr' => $imported]]), 'confirmation');
|
||||
$rcmail->output->command('command', 'list');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('importmessageerror', 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::upload_failure();
|
||||
}
|
||||
|
||||
// send html page with JS calls as response
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function zip_extract($path)
|
||||
{
|
||||
if (!class_exists('ZipArchive', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$zip = new ZipArchive;
|
||||
$files = [];
|
||||
|
||||
if ($zip->open($path)) {
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entry = $zip->getNameIndex($i);
|
||||
$tmpfname = rcube_utils::temp_filename('zipimport');
|
||||
|
||||
if (copy("zip://$path#$entry", $tmpfname)) {
|
||||
$ctype = rcube_mime::file_content_type($tmpfname, $entry);
|
||||
list($mtype_primary, ) = explode('/', $ctype);
|
||||
|
||||
if (in_array($mtype_primary, ['text', 'message'])) {
|
||||
$files[] = $tmpfname;
|
||||
}
|
||||
else {
|
||||
unlink($tmpfname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
public static function save_message($folder, &$message)
|
||||
{
|
||||
$date = null;
|
||||
|
||||
if (strncmp($message, 'From ', 5) === 0) {
|
||||
// Extract the mbox from_line
|
||||
$pos = strpos($message, "\n");
|
||||
$from = substr($message, 0, $pos);
|
||||
$message = substr($message, $pos + 1);
|
||||
|
||||
// Read the received date, support only known date formats
|
||||
|
||||
// RFC4155: "Sat Jan 3 01:05:34 1996"
|
||||
$mboxdate_rx = '/^([a-z]{3} [a-z]{3} [0-9 ][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4})/i';
|
||||
// Roundcube/Zipdownload: "12-Dec-2016 10:56:33 +0100"
|
||||
$imapdate_rx = '/^([0-9]{1,2}-[a-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9+-]{5})/i';
|
||||
|
||||
if (
|
||||
($pos = strpos($from, ' ', 6))
|
||||
&& ($dt_str = substr($from, $pos + 1))
|
||||
&& (preg_match($mboxdate_rx, $dt_str, $m) || preg_match($imapdate_rx, $dt_str, $m))
|
||||
) {
|
||||
try {
|
||||
$date = new DateTime($m[0], new DateTimeZone('UTC'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unquote ">From " lines in message body
|
||||
$message = preg_replace('/\n>([>]*)From /', "\n\\1From ", $message);
|
||||
$message = rtrim($message);
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($rcmail->storage->save_message($folder, $message, '', false, [], $date)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
rcube::raise_error("Failed to import message to $folder", true, false);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
<?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: |
|
||||
| Send message list to client (as remote response) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_list extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
$save_arr = [];
|
||||
$dont_override = (array) $rcmail->config->get('dont_override');
|
||||
$cols = null;
|
||||
|
||||
// is there a sort type for this request?
|
||||
$sort = rcube_utils::get_input_string('_sort', rcube_utils::INPUT_GET);
|
||||
if ($sort && preg_match('/^[a-zA-Z_-]+$/', $sort)) {
|
||||
// yes, so set the sort vars
|
||||
list($sort_col, $sort_order) = explode('_', $sort);
|
||||
|
||||
// set session vars for sort (so next page and task switch know how to sort)
|
||||
if (!in_array('message_sort_col', $dont_override)) {
|
||||
$_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
|
||||
}
|
||||
if (!in_array('message_sort_order', $dont_override)) {
|
||||
$_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
|
||||
}
|
||||
}
|
||||
|
||||
// is there a set of columns for this request?
|
||||
if ($cols = rcube_utils::get_input_string('_cols', rcube_utils::INPUT_GET)) {
|
||||
$_SESSION['list_attrib']['columns'] = explode(',', $cols);
|
||||
if (!in_array('list_cols', $dont_override)) {
|
||||
$save_arr['list_cols'] = explode(',', $cols);
|
||||
}
|
||||
}
|
||||
|
||||
// register layout change
|
||||
if ($layout = rcube_utils::get_input_string('_layout', rcube_utils::INPUT_GET)) {
|
||||
$rcmail->output->set_env('layout', $layout);
|
||||
$save_arr['layout'] = $layout;
|
||||
// force header replace on layout change
|
||||
if (!empty($_SESSION['list_attrib']['columns'])) {
|
||||
$cols = $_SESSION['list_attrib']['columns'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($save_arr)) {
|
||||
$rcmail->user->save_prefs($save_arr);
|
||||
}
|
||||
|
||||
$mbox_name = $rcmail->storage->get_folder();
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
|
||||
// Synchronize mailbox cache, handle flag changes
|
||||
$rcmail->storage->folder_sync($mbox_name);
|
||||
|
||||
// fetch message headers
|
||||
$a_headers = [];
|
||||
if ($count = $rcmail->storage->count($mbox_name, $threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) {
|
||||
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
|
||||
}
|
||||
|
||||
// update search set (possible change of threading mode)
|
||||
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
|
||||
&& $_SESSION['search_request'] == $_REQUEST['_search']
|
||||
) {
|
||||
$search_request = $_REQUEST['_search'];
|
||||
$_SESSION['search'] = $rcmail->storage->get_search_set();
|
||||
$multifolder = !empty($_SESSION['search']) && !empty($_SESSION['search'][1]->multi);
|
||||
}
|
||||
// remove old search data
|
||||
else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) {
|
||||
$rcmail->session->remove('search');
|
||||
}
|
||||
|
||||
self::list_pagetitle();
|
||||
|
||||
// update mailboxlist
|
||||
if (empty($search_request)) {
|
||||
self::send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), empty($a_headers) ? 0 : null);
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$pages = ceil($count / $rcmail->storage->get_pagesize());
|
||||
$page = $count ? $rcmail->storage->get_page() : 1;
|
||||
$exists = $rcmail->storage->count($mbox_name, 'EXISTS', true);
|
||||
|
||||
$rcmail->output->set_env('messagecount', $count);
|
||||
$rcmail->output->set_env('pagecount', $pages);
|
||||
$rcmail->output->set_env('threading', $threading);
|
||||
$rcmail->output->set_env('current_page', $page);
|
||||
$rcmail->output->set_env('exists', $exists);
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count), $mbox_name);
|
||||
|
||||
// remove old message rows if commanded by the client
|
||||
if (!empty($_REQUEST['_clear'])) {
|
||||
$rcmail->output->command('clear_message_list');
|
||||
}
|
||||
|
||||
// add message rows
|
||||
self::js_message_list($a_headers, false, $cols);
|
||||
|
||||
if (!empty($a_headers)) {
|
||||
if (!empty($search_request)) {
|
||||
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $count]);
|
||||
}
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
// we need it for flag updates in check-recent
|
||||
$data = $rcmail->storage->folder_data($mbox_name);
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// handle IMAP errors (e.g. #1486905)
|
||||
if ($err_code = $rcmail->storage->get_error_code()) {
|
||||
self::display_server_error();
|
||||
}
|
||||
else if (!empty($search_request)) {
|
||||
$rcmail->output->show_message('searchnomatch', 'notice');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('nomessagesfound', 'notice');
|
||||
}
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox_name === $rcmail->config->get('trash_mbox')) {
|
||||
$rcmail->output->command('set_trash_count', $exists);
|
||||
}
|
||||
|
||||
if ($page == 1) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, !empty($multifolder) ? 'INBOX' : $mbox_name));
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?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: |
|
||||
| Send contacts list to client (as remote response) |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_list_contacts extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$list_page = max(1, $_GET['_page'] ?? 0);
|
||||
$jsresult = [];
|
||||
|
||||
// Use search result
|
||||
if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) {
|
||||
$search = (array) $_SESSION['contact_search'][$_REQUEST['_search']];
|
||||
$sparam = $_SESSION['contact_search_params']['id'] == $_REQUEST['_search'] ? $_SESSION['contact_search_params']['data'] : [];
|
||||
$mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$records = [];
|
||||
|
||||
// get records from all sources
|
||||
foreach ($search as $s => $set) {
|
||||
$CONTACTS = $rcmail->get_address_book($s);
|
||||
|
||||
// list matching groups of this source (on page one)
|
||||
if ($sparam[1] && $CONTACTS->groups && $list_page == 1) {
|
||||
$jsresult += self::compose_contact_groups($CONTACTS, $s, $sparam[1], $mode);
|
||||
}
|
||||
|
||||
// reset page
|
||||
$CONTACTS->set_page(1);
|
||||
$CONTACTS->set_pagesize(9999);
|
||||
$CONTACTS->set_search_set($set);
|
||||
|
||||
// get records
|
||||
$result = $CONTACTS->list_records($afields);
|
||||
|
||||
while ($row = $result->next()) {
|
||||
$row['sourceid'] = $s;
|
||||
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
|
||||
$records[$key] = $row;
|
||||
}
|
||||
unset($result);
|
||||
}
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$first = ($list_page-1) * $page_size;
|
||||
$result = new rcube_result_set($count, $first);
|
||||
|
||||
// we need only records for current page
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $first, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
}
|
||||
// list contacts from selected source
|
||||
else {
|
||||
$CONTACTS = $rcmail->get_address_book($source);
|
||||
|
||||
if ($CONTACTS && $CONTACTS->ready) {
|
||||
// set list properties
|
||||
$CONTACTS->set_pagesize($page_size);
|
||||
$CONTACTS->set_page($list_page);
|
||||
|
||||
if ($group_id = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET)) {
|
||||
$CONTACTS->set_group($group_id);
|
||||
}
|
||||
// list groups of this source (on page one)
|
||||
else if ($CONTACTS->groups && $CONTACTS->list_page == 1) {
|
||||
$jsresult = self::compose_contact_groups($CONTACTS, $source);
|
||||
}
|
||||
|
||||
// get contacts for this user
|
||||
$result = $CONTACTS->list_records($afields);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result) && !$result->count && $result->searchonly) {
|
||||
$rcmail->output->show_message('contactsearchonly', 'notice');
|
||||
}
|
||||
else if (!empty($result) && $result->count > 0) {
|
||||
// create javascript list
|
||||
while ($row = $result->next()) {
|
||||
$name = rcube_addressbook::compose_list_name($row);
|
||||
|
||||
// add record for every email address of the contact
|
||||
$emails = rcube_addressbook::get_col_values('email', $row, true);
|
||||
foreach ($emails as $i => $email) {
|
||||
$source = !empty($row['sourceid']) ? $row['sourceid'] : $source;
|
||||
$row_id = $source.'-'.$row['ID'].'-'.$i;
|
||||
$is_group = isset($row['_type']) && $row['_type'] == 'group';
|
||||
$classname = $is_group ? 'group' : 'person';
|
||||
$keyname = $is_group ? 'contactgroup' : 'contact';
|
||||
|
||||
$jsresult[$row_id] = format_email_recipient($email, $name);
|
||||
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
$keyname => html::a(
|
||||
['title' => $email],
|
||||
rcube::Q($name ?: $email)
|
||||
. ($name && count($emails) > 1 ? ' ' . html::span('email', rcube::Q($email)) : '')
|
||||
)
|
||||
],
|
||||
$classname
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update env
|
||||
$rcmail->output->set_env('contactdata', $jsresult);
|
||||
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 1);
|
||||
$rcmail->output->command('set_page_buttons');
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add groups from the given address source to the address book widget
|
||||
*/
|
||||
public static function compose_contact_groups($abook, $source_id, $search = null, $search_mode = 0)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$jsresult = [];
|
||||
|
||||
foreach ($abook->list_groups($search, $search_mode) as $group) {
|
||||
$abook->reset();
|
||||
$abook->set_group($group['ID']);
|
||||
|
||||
// group (distribution list) with email address(es)
|
||||
if (!empty($group['email'])) {
|
||||
foreach ((array) $group['email'] as $email) {
|
||||
$row_id = 'G'.$group['ID'];
|
||||
$jsresult[$row_id] = format_email_recipient($email, $group['name']);
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => html::span(['title' => $email], rcube::Q($group['name']))
|
||||
], 'group');
|
||||
}
|
||||
}
|
||||
// make virtual groups clickable to list their members
|
||||
else if (!empty($group['virtual'])) {
|
||||
$row_id = 'G'.$group['ID'];
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => html::a([
|
||||
'href' => '#list',
|
||||
'rel' => $group['ID'],
|
||||
'title' => $rcmail->gettext('listgroup'),
|
||||
'onclick' => sprintf("return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)",
|
||||
rcmail_output::JS_OBJECT_NAME, $source_id, $group['ID']),
|
||||
],
|
||||
rcube::Q($group['name']) . ' ' . html::span('action', '»')
|
||||
)],
|
||||
'group',
|
||||
['ID' => $group['ID'], 'name' => $group['name'], 'virtual' => true]
|
||||
);
|
||||
}
|
||||
// show group with count
|
||||
else if (($result = $abook->count()) && $result->count) {
|
||||
$row_id = 'E'.$group['ID'];
|
||||
$jsresult[$row_id] = ['name' => $group['name'], 'source' => $source_id];
|
||||
$rcmail->output->command('add_contact_row', $row_id, [
|
||||
'contactgroup' => rcube::Q($group['name'] . ' (' . intval($result->count) . ')')
|
||||
], 'group');
|
||||
}
|
||||
}
|
||||
|
||||
$abook->reset();
|
||||
$abook->set_group(0);
|
||||
|
||||
return $jsresult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?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: |
|
||||
| Mark the submitted messages with the specified flag |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_mark extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
$_uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
|
||||
$flag = rcube_utils::get_input_string('_flag', rcube_utils::INPUT_POST);
|
||||
$folders = rcube_utils::get_input_string('_folders', rcube_utils::INPUT_POST);
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST);
|
||||
|
||||
if (empty($_uids) || empty($flag)) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
$skip_deleted = (bool) $rcmail->config->get('skip_deleted');
|
||||
$read_deleted = (bool) $rcmail->config->get('read_when_deleted');
|
||||
$flag = self::imap_flag($flag);
|
||||
$old_count = 0;
|
||||
$from = $_POST['_from'] ?? null;
|
||||
|
||||
if ($flag == 'DELETED' && $skip_deleted && $from != 'show') {
|
||||
// count messages before changing anything
|
||||
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
}
|
||||
|
||||
if ($folders == 'all') {
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
|
||||
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
|
||||
}
|
||||
else if ($folders == 'sub') {
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
|
||||
array_unshift($mboxes, $mbox);
|
||||
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
|
||||
}
|
||||
else if ($folders == 'cur') {
|
||||
$input = [$mbox => '*'];
|
||||
}
|
||||
else {
|
||||
$input = self::get_uids(null, null, $dummy, rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
$marked = 0;
|
||||
$count = 0;
|
||||
$read = 0;
|
||||
|
||||
foreach ($input as $mbox => $uids) {
|
||||
$marked += (int) $rcmail->storage->set_flag($uids, $flag, $mbox);
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
}
|
||||
|
||||
if (!$marked) {
|
||||
// send error message
|
||||
if ($from != 'show') {
|
||||
$rcmail->output->command('list_mailbox');
|
||||
}
|
||||
|
||||
self::display_server_error('errormarking');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else if (empty($_POST['_quiet'])) {
|
||||
$rcmail->output->show_message('messagemarked', 'confirmation');
|
||||
}
|
||||
|
||||
if ($flag == 'DELETED' && $read_deleted && !empty($_POST['_ruid'])) {
|
||||
if ($ruids = rcube_utils::get_input_value('_ruid', rcube_utils::INPUT_POST)) {
|
||||
foreach (self::get_uids($ruids) as $mbox => $uids) {
|
||||
$read += (int) $rcmail->storage->set_flag($uids, 'SEEN', $mbox);
|
||||
}
|
||||
}
|
||||
|
||||
if ($read && !$skip_deleted) {
|
||||
$rcmail->output->command('flag_deleted_as_read', $ruids);
|
||||
}
|
||||
}
|
||||
|
||||
if ($flag == 'SEEN' || $flag == 'UNSEEN' || ($flag == 'DELETED' && !$skip_deleted)) {
|
||||
foreach ($input as $mbox => $uids) {
|
||||
self::send_unread_count($mbox);
|
||||
}
|
||||
|
||||
$rcmail->output->set_env('last_flag', $flag);
|
||||
}
|
||||
else if ($flag == 'DELETED' && $skip_deleted) {
|
||||
if ($from == 'show') {
|
||||
if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC)) {
|
||||
$rcmail->output->command('show_message', $next);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('command', 'list');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC);
|
||||
|
||||
// refresh saved search set after moving some messages
|
||||
if ($search_request && $rcmail->storage->get_search_set()) {
|
||||
$_SESSION['search'] = $rcmail->storage->refresh_search();
|
||||
}
|
||||
|
||||
$msg_count = $rcmail->storage->count(NULL, $threading ? 'THREADS' : 'ALL');
|
||||
$page_size = $rcmail->storage->get_pagesize();
|
||||
$page = $rcmail->storage->get_page();
|
||||
$pages = ceil($msg_count / $page_size);
|
||||
$nextpage_count = $old_count - $page_size * $page;
|
||||
$remaining = $msg_count - $page_size * ($page - 1);
|
||||
$jump_back = false;
|
||||
|
||||
// jump back one page (user removed the whole last page)
|
||||
if ($page > 1 && $remaining == 0) {
|
||||
$page -= 1;
|
||||
$rcmail->storage->set_page($page);
|
||||
$_SESSION['page'] = $page;
|
||||
$jump_back = true;
|
||||
}
|
||||
|
||||
foreach ($input as $mbox => $uids) {
|
||||
self::send_unread_count($mbox, true);
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('messagecount', $msg_count);
|
||||
$rcmail->output->set_env('current_page', $page);
|
||||
$rcmail->output->set_env('pagecount', $pages);
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
|
||||
|
||||
if ($threading) {
|
||||
$count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if ($old_count && $_uids != '*' && ($jump_back || $nextpage_count > 0)) {
|
||||
// #5862: Don't add more rows than it was on the next page
|
||||
$count = $jump_back ? null : min($nextpage_count, $count);
|
||||
|
||||
$a_headers = $rcmail->storage->list_messages($mbox, null,
|
||||
self::sort_column(), self::sort_order(), $count);
|
||||
|
||||
self::js_message_list($a_headers, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Roundcube UI's flag label into IMAP flag
|
||||
*
|
||||
* @param string $flag Flag label
|
||||
*
|
||||
* @return string Uppercase IMAP flag
|
||||
*/
|
||||
public static function imap_flag($flag)
|
||||
{
|
||||
$flags_map = [
|
||||
'undelete' => 'UNDELETED',
|
||||
'delete' => 'DELETED',
|
||||
'read' => 'SEEN',
|
||||
'unread' => 'UNSEEN',
|
||||
'flagged' => 'FLAGGED',
|
||||
'unflagged' => 'UNFLAGGED',
|
||||
];
|
||||
|
||||
return !empty($flags_map[$flag]) ? $flags_map[$flag] : strtoupper($flag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?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: |
|
||||
| Handler for mail move operation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_move extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
|
||||
// count messages before changing anything
|
||||
$threading = (bool) $rcmail->storage->get_threading();
|
||||
$trash = $rcmail->config->get('trash_mbox');
|
||||
$old_count = 0;
|
||||
|
||||
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
|
||||
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
}
|
||||
|
||||
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (empty($_POST['_uid']) || !strlen($target)) {
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$addrows = false;
|
||||
$count = 0;
|
||||
$sources = [];
|
||||
|
||||
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
|
||||
if ($mbox === $target) {
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
}
|
||||
else if ($rcmail->storage->move_message($uids, $target, $mbox)) {
|
||||
$count += is_array($uids) ? count($uids) : 1;
|
||||
$sources[] = $mbox;
|
||||
}
|
||||
else {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
// send error message
|
||||
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
|
||||
$rcmail->output->command('list_mailbox');
|
||||
}
|
||||
|
||||
self::display_server_error('errormoving', null, $target == $trash ? 'delete' : '');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message($target == $trash ? 'messagemovedtotrash' : 'messagemoved', 'confirmation');
|
||||
}
|
||||
|
||||
if (!empty($_POST['_refresh'])) {
|
||||
// FIXME: send updated message rows instead of reloading the entire list
|
||||
$rcmail->output->command('refresh_list');
|
||||
}
|
||||
else {
|
||||
$addrows = true;
|
||||
}
|
||||
|
||||
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
|
||||
|
||||
// refresh saved search set after moving some messages
|
||||
if ($search_request && $rcmail->storage->get_search_set()) {
|
||||
$_SESSION['search'] = $rcmail->storage->refresh_search();
|
||||
}
|
||||
|
||||
if (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
|
||||
if ($next = rcube_utils::get_input_string('_next_uid', rcube_utils::INPUT_GPC)) {
|
||||
$rcmail->output->command('show_message', $next);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('command', 'list');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
$mbox = $rcmail->storage->get_folder();
|
||||
$msg_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
|
||||
$exists = $rcmail->storage->count($mbox, 'EXISTS', true);
|
||||
$page_size = $rcmail->storage->get_pagesize();
|
||||
$page = $rcmail->storage->get_page();
|
||||
$pages = ceil($msg_count / $page_size);
|
||||
$nextpage_count = $old_count - $page_size * $page;
|
||||
$remaining = $msg_count - $page_size * ($page - 1);
|
||||
|
||||
// jump back one page (user removed the whole last page)
|
||||
if ($page > 1 && $remaining == 0) {
|
||||
$page -= 1;
|
||||
$rcmail->storage->set_page($page);
|
||||
$_SESSION['page'] = $page;
|
||||
$jump_back = true;
|
||||
}
|
||||
|
||||
// update unseen messages counts for all involved folders
|
||||
foreach ($sources as $source) {
|
||||
self::send_unread_count($source, true);
|
||||
}
|
||||
|
||||
self::send_unread_count($target, true);
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('messagecount', $msg_count);
|
||||
$rcmail->output->set_env('current_page', $page);
|
||||
$rcmail->output->set_env('pagecount', $pages);
|
||||
$rcmail->output->set_env('exists', $exists);
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
|
||||
|
||||
if ($threading) {
|
||||
$count = rcube_utils::get_input_string('_count', rcube_utils::INPUT_POST);
|
||||
}
|
||||
|
||||
// add new rows from next page (if any)
|
||||
if ($addrows && $count && $_POST['_uid'] != '*' && (!empty($jump_back) || $nextpage_count > 0)) {
|
||||
// #5862: Don't add more rows than it was on the next page
|
||||
$count = !empty($jump_back) ? null : min($nextpage_count, $count);
|
||||
|
||||
$a_headers = $rcmail->storage->list_messages($mbox, NULL,
|
||||
self::sort_column(), self::sort_order(), $count);
|
||||
|
||||
self::js_message_list($a_headers, false);
|
||||
}
|
||||
|
||||
// set trash folder state
|
||||
if ($mbox === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $exists);
|
||||
}
|
||||
else if ($target === $trash) {
|
||||
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash, 'EXISTS', true));
|
||||
}
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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: |
|
||||
| Updates message page navigation controls |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_pagenav extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
$index = $rcmail->storage->index(null, self::sort_column(), self::sort_order());
|
||||
$cnt = $index->count_messages();
|
||||
|
||||
if ($cnt && ($pos = $index->exists($uid, true)) !== false) {
|
||||
$prev = $pos ? $index->get_element($pos-1) : 0;
|
||||
$first = $pos ? $index->get_element('FIRST') : 0;
|
||||
$next = $pos < $cnt-1 ? $index->get_element($pos+1) : 0;
|
||||
$last = $pos < $cnt-1 ? $index->get_element('LAST') : 0;
|
||||
}
|
||||
else {
|
||||
// error, this will at least disable page navigation
|
||||
$rcmail->output->command('set_rowcount', '');
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
// Set UIDs and activate navigation buttons
|
||||
if (!empty($prev)) {
|
||||
$rcmail->output->set_env('prev_uid', $prev);
|
||||
$rcmail->output->command('enable_command', 'previousmessage', 'firstmessage', true);
|
||||
}
|
||||
|
||||
if (!empty($next)) {
|
||||
$rcmail->output->set_env('next_uid', $next);
|
||||
$rcmail->output->command('enable_command', 'nextmessage', 'lastmessage', true);
|
||||
}
|
||||
|
||||
if (!empty($first)) {
|
||||
$rcmail->output->set_env('first_uid', $first);
|
||||
}
|
||||
|
||||
if (!empty($last)) {
|
||||
$rcmail->output->set_env('last_uid', $last);
|
||||
}
|
||||
|
||||
// Don't need a real messages count value
|
||||
$rcmail->output->set_env('messagecount', 1);
|
||||
|
||||
// Set rowcount text
|
||||
$rcmail->output->command('set_rowcount', $rcmail->gettext([
|
||||
'name' => 'messagenrof',
|
||||
'vars' => ['nr' => ($pos ?? 0) + 1, 'count' => $cnt]
|
||||
]));
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?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: |
|
||||
| Mail messages search action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Benjamin Smith <defitro@gmail.com> |
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_search extends rcmail_action_mail_index
|
||||
{
|
||||
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();
|
||||
|
||||
@set_time_limit(170); // extend default max_execution_time to ~3 minutes
|
||||
|
||||
// reset list_page and old search results
|
||||
$rcmail->storage->set_page(1);
|
||||
$rcmail->storage->set_search_set(null);
|
||||
$_SESSION['page'] = 1;
|
||||
|
||||
// get search string
|
||||
$str = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GET, true);
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GET, true);
|
||||
$filter = rcube_utils::get_input_string('_filter', rcube_utils::INPUT_GET);
|
||||
$headers = rcube_utils::get_input_string('_headers', rcube_utils::INPUT_GET);
|
||||
$scope = rcube_utils::get_input_string('_scope', rcube_utils::INPUT_GET);
|
||||
$interval = rcube_utils::get_input_string('_interval', rcube_utils::INPUT_GET);
|
||||
$continue = rcube_utils::get_input_string('_continue', rcube_utils::INPUT_GET);
|
||||
|
||||
$filter = trim((string) $filter);
|
||||
$search_request = md5($mbox . $scope . $interval . $filter . $str);
|
||||
|
||||
// Parse input
|
||||
list($subject, $search) = self::search_input($str, $headers, $scope, $mbox);
|
||||
|
||||
// add list filter string
|
||||
$search_str = $filter && $filter != 'ALL' ? $filter : '';
|
||||
|
||||
if ($search_interval = self::search_interval_criteria($interval)) {
|
||||
$search_str .= ' ' . $search_interval;
|
||||
}
|
||||
|
||||
if (!empty($subject)) {
|
||||
$search_str .= str_repeat(' OR', count($subject)-1);
|
||||
foreach ($subject as $sub) {
|
||||
$search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search);
|
||||
}
|
||||
}
|
||||
|
||||
$search_str = trim($search_str);
|
||||
$sort_column = self::sort_column();
|
||||
$sort_order = self::sort_order();
|
||||
|
||||
// set message set for already stored (but incomplete) search request
|
||||
if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
|
||||
$rcmail->storage->set_search_set($_SESSION['search']);
|
||||
$search_str = $_SESSION['search'][0];
|
||||
}
|
||||
|
||||
// execute IMAP search
|
||||
if ($search_str) {
|
||||
$mboxes = [];
|
||||
|
||||
// search all, current or subfolders folders
|
||||
if ($scope == 'all') {
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail', null, true);
|
||||
// we want natural alphabetic sorting of folders in the result set
|
||||
natcasesort($mboxes);
|
||||
}
|
||||
else if ($scope == 'sub') {
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
|
||||
array_unshift($mboxes, $mbox);
|
||||
}
|
||||
|
||||
if ($scope != 'all') {
|
||||
// Remember current folder, it can change in meantime (plugins)
|
||||
// but we need it to e.g. recognize Sent folder to handle From/To column later
|
||||
$rcmail->output->set_env('mailbox', $mbox);
|
||||
}
|
||||
|
||||
$result = $rcmail->storage->search($mboxes, $search_str, RCUBE_CHARSET, $sort_column);
|
||||
}
|
||||
|
||||
// save search results in session
|
||||
if (!isset($_SESSION['search']) || !is_array($_SESSION['search'])) {
|
||||
$_SESSION['search'] = [];
|
||||
}
|
||||
|
||||
if ($search_str) {
|
||||
$_SESSION['search'] = $rcmail->storage->get_search_set();
|
||||
$_SESSION['last_text_search'] = $str;
|
||||
}
|
||||
|
||||
$_SESSION['search_request'] = $search_request;
|
||||
$_SESSION['search_scope'] = $scope;
|
||||
$_SESSION['search_interval'] = $interval;
|
||||
$_SESSION['search_filter'] = $filter;
|
||||
|
||||
// Get the headers
|
||||
if (!isset($result) || empty($result->incomplete)) {
|
||||
$result_h = $rcmail->storage->list_messages($mbox, 1, $sort_column, $sort_order);
|
||||
}
|
||||
|
||||
// Make sure we got the headers
|
||||
if (!empty($result_h)) {
|
||||
$count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');
|
||||
|
||||
self::js_message_list($result_h, false);
|
||||
|
||||
if ($search_str) {
|
||||
$all_count = $rcmail->storage->count(null, 'ALL');
|
||||
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $all_count]);
|
||||
}
|
||||
|
||||
// remember last HIGHESTMODSEQ value (if supported)
|
||||
// we need it for flag updates in check-recent
|
||||
if ($mbox !== null) {
|
||||
$data = $rcmail->storage->folder_data($mbox);
|
||||
if (!empty($data['HIGHESTMODSEQ'])) {
|
||||
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle IMAP errors (e.g. #1486905)
|
||||
else if ($err_code = $rcmail->storage->get_error_code()) {
|
||||
$count = 0;
|
||||
self::display_server_error();
|
||||
}
|
||||
// advice the client to re-send the (cross-folder) search request
|
||||
else if (!empty($result) && !empty($result->incomplete)) {
|
||||
$count = 0; // keep UI locked
|
||||
$rcmail->output->command('continue_search', $search_request);
|
||||
}
|
||||
else {
|
||||
$count = 0;
|
||||
|
||||
$rcmail->output->show_message('searchnomatch', 'notice');
|
||||
$rcmail->output->set_env('multifolder_listing', isset($result) ? !empty($result->multi) : false);
|
||||
|
||||
if (isset($result) && !empty($result->multi) && $scope == 'all') {
|
||||
$rcmail->output->command('select_folder', '');
|
||||
}
|
||||
}
|
||||
|
||||
// update message count display
|
||||
$rcmail->output->set_env('search_request', $search_str ? $search_request : '');
|
||||
$rcmail->output->set_env('search_filter', $_SESSION['search_filter']);
|
||||
$rcmail->output->set_env('messagecount', $count);
|
||||
$rcmail->output->set_env('pagecount', ceil($count / $rcmail->storage->get_pagesize()));
|
||||
$rcmail->output->set_env('exists', $mbox === null ? 0 : $rcmail->storage->count($mbox, 'EXISTS'));
|
||||
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count, 1), $mbox);
|
||||
|
||||
self::list_pagetitle();
|
||||
|
||||
// update unseen messages count
|
||||
if ($search_str === '') {
|
||||
self::send_unread_count($mbox, false, empty($result_h) ? 0 : null);
|
||||
}
|
||||
|
||||
if (isset($result) && empty($result->incomplete)) {
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, !empty($result->multi) ? 'INBOX' : $mbox));
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates BEFORE/SINCE search criteria from the specified interval
|
||||
* Interval can be: 1W, 1M, 1Y, -1W, -1M, -1Y
|
||||
*/
|
||||
public static function search_interval_criteria($interval)
|
||||
{
|
||||
if (empty($interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($interval[0] == '-') {
|
||||
$search = 'BEFORE';
|
||||
$interval = substr($interval, 1);
|
||||
}
|
||||
else {
|
||||
$search = 'SINCE';
|
||||
}
|
||||
|
||||
$date = new DateTime('now');
|
||||
$interval = new DateInterval('P' . $interval);
|
||||
|
||||
$date->sub($interval);
|
||||
|
||||
return $search . ' ' . $date->format('j-M-Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse search input.
|
||||
*
|
||||
* @param string $str Search string
|
||||
* @param string $headers Comma-separated list of headers/fields to search in
|
||||
* @param string $scope Search scope (all | base | sub)
|
||||
* @param string $mbox Folder name
|
||||
*
|
||||
* @return array Search criteria (1st element) and search value (2nd element)
|
||||
*/
|
||||
public static function search_input($str, $headers, $scope, $mbox)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$subject = [];
|
||||
$srch = null;
|
||||
$supported = ['subject', 'from', 'to', 'cc', 'bcc'];
|
||||
|
||||
// Check the search string for type of search
|
||||
if (preg_match("/^(from|to|reply-to|cc|bcc|subject):.*/i", $str, $m)) {
|
||||
list(, $srch) = explode(":", $str);
|
||||
$subject[$m[1]] = 'HEADER ' . strtoupper($m[1]);
|
||||
}
|
||||
else if (preg_match("/^body:.*/i", $str)) {
|
||||
list(, $srch) = explode(":", $str);
|
||||
$subject['body'] = 'BODY';
|
||||
}
|
||||
else if (strlen(trim($str))) {
|
||||
if ($headers) {
|
||||
foreach (explode(',', $headers) as $header) {
|
||||
switch ($header) {
|
||||
case 'text':
|
||||
// #1488208: get rid of other headers when searching by "TEXT"
|
||||
$subject = ['text' => 'TEXT'];
|
||||
break 2;
|
||||
case 'body':
|
||||
$subject['body'] = 'BODY';
|
||||
break;
|
||||
case 'replyto':
|
||||
case 'reply-to':
|
||||
$subject['reply-to'] = 'HEADER REPLY-TO';
|
||||
$subject['mail-reply-to'] = 'HEADER MAIL-REPLY-TO';
|
||||
break;
|
||||
case 'followupto':
|
||||
case 'followup-to':
|
||||
$subject['followup-to'] = 'HEADER FOLLOWUP-TO';
|
||||
$subject['mail-followup-to'] = 'HEADER MAIL-FOLLOWUP-TO';
|
||||
break;
|
||||
default:
|
||||
if (in_array_nocase($header, $supported)) {
|
||||
$subject[$header] = 'HEADER ' . strtoupper($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save search modifiers for the current folder to user prefs
|
||||
if ($scope != 'all') {
|
||||
$search_mods = self::search_mods();
|
||||
$search_mods_value = array_fill_keys(array_keys($subject), 1);
|
||||
|
||||
if (!isset($search_mods[$mbox]) || $search_mods[$mbox] != $search_mods_value) {
|
||||
$search_mods[$mbox] = $search_mods_value;
|
||||
$rcmail->user->save_prefs(['search_mods' => $search_mods]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// search in subject by default
|
||||
$subject['subject'] = 'HEADER SUBJECT';
|
||||
}
|
||||
}
|
||||
|
||||
return [$subject, isset($srch) ? trim($srch) : trim($str)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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: |
|
||||
| Search contacts from the address book widget |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_search_contacts extends rcmail_action_mail_list_contacts
|
||||
{
|
||||
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();
|
||||
$search = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GPC, true);
|
||||
$sources = $rcmail->get_address_sources();
|
||||
$search_mode = (int) $rcmail->config->get('addressbook_search_mode');
|
||||
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
|
||||
$afields = $rcmail->config->get('contactlist_fields');
|
||||
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
|
||||
$records = [];
|
||||
$search_set = [];
|
||||
$jsresult = [];
|
||||
$search_mode |= rcube_addressbook::SEARCH_GROUPS;
|
||||
|
||||
foreach ($sources as $s) {
|
||||
$source = $rcmail->get_address_book($s['id']);
|
||||
$source->set_page(1);
|
||||
$source->set_pagesize(9999);
|
||||
|
||||
// list matching groups of this source
|
||||
if ($source->groups) {
|
||||
$jsresult += self::compose_contact_groups($source, $s['id'], $search, $search_mode);
|
||||
}
|
||||
|
||||
// get contacts count
|
||||
$result = $source->search($afields, $search, $search_mode, true, true, 'email');
|
||||
|
||||
if (!$result->count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($row = $result->next()) {
|
||||
$row['sourceid'] = $s['id'];
|
||||
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
|
||||
$records[$key] = $row;
|
||||
}
|
||||
|
||||
$search_set[$s['id']] = $source->get_search_set();
|
||||
unset($result);
|
||||
}
|
||||
|
||||
$group_count = count($jsresult);
|
||||
|
||||
// sort the records
|
||||
ksort($records, SORT_LOCALE_STRING);
|
||||
|
||||
// create resultset object
|
||||
$count = count($records);
|
||||
$result = new rcube_result_set($count);
|
||||
|
||||
// select the requested page
|
||||
if ($page_size < $count) {
|
||||
$records = array_slice($records, $result->first, $page_size);
|
||||
}
|
||||
|
||||
$result->records = array_values($records);
|
||||
|
||||
if (!empty($result) && $result->count > 0) {
|
||||
// create javascript list
|
||||
while ($row = $result->next()) {
|
||||
$name = rcube_addressbook::compose_list_name($row);
|
||||
$is_group = isset($row['_type']) && $row['_type'] == 'group';
|
||||
$classname = $is_group ? 'group' : 'person';
|
||||
$keyname = $is_group ? 'contactgroup' : 'contact';
|
||||
|
||||
// add record for every email address of the contact
|
||||
// (same as in list_contacts.inc)
|
||||
$emails = rcube_addressbook::get_col_values('email', $row, true);
|
||||
|
||||
foreach ($emails as $i => $email) {
|
||||
$row_id = $row['sourceid'].'-'.$row['ID'].'-'.$i;
|
||||
|
||||
$jsresult[$row_id] = format_email_recipient($email, $name);
|
||||
|
||||
$title = rcube_addressbook::compose_search_name($row, $email, $name);
|
||||
$link_content = rcube::Q($name ?: $email);
|
||||
if ($name && count($emails) > 1) {
|
||||
$link_content .= ' ' . html::span('email', rcube::Q($email));
|
||||
}
|
||||
$link = html::a(['title' => $title], $link_content);
|
||||
|
||||
$rcmail->output->command('add_contact_row', $row_id, [$keyname => $link], $classname);
|
||||
}
|
||||
}
|
||||
|
||||
// search request ID
|
||||
$search_request = md5('composeaddr' . $search);
|
||||
|
||||
// save search settings in session
|
||||
$_SESSION['contact_search'][$search_request] = $search_set;
|
||||
$_SESSION['contact_search_params'] = ['id' => $search_request, 'data' => [$afields, $search]];
|
||||
|
||||
$rcmail->output->show_message('contactsearchsuccessful', 'confirmation', ['nr' => $result->count]);
|
||||
|
||||
$rcmail->output->set_env('search_request', $search_request);
|
||||
$rcmail->output->set_env('source', '');
|
||||
$rcmail->output->command('unselect_directory');
|
||||
}
|
||||
else if (!$group_count) {
|
||||
$rcmail->output->show_message('nocontactsfound', 'notice');
|
||||
}
|
||||
|
||||
// update env
|
||||
$rcmail->output->set_env('contactdata', $jsresult);
|
||||
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
|
||||
$rcmail->output->command('set_page_buttons');
|
||||
|
||||
// send response
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
<?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: |
|
||||
| Compose a new mail message and send it or store as draft |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_send extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// remove all scripts and act as called in frame
|
||||
$rcmail->output->reset();
|
||||
$rcmail->output->framed = true;
|
||||
|
||||
$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
|
||||
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
|
||||
|
||||
// Sanity checks
|
||||
if (!isset($COMPOSE['id'])) {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Invalid compose ID"
|
||||
], true, false
|
||||
);
|
||||
|
||||
$rcmail->output->show_message('internalerror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$saveonly = !empty($_GET['_saveonly']);
|
||||
$savedraft = !empty($_POST['_draft']) && !$saveonly;
|
||||
$SENDMAIL = new rcmail_sendmail($COMPOSE, [
|
||||
'sendmail' => true,
|
||||
'saveonly' => $saveonly,
|
||||
'savedraft' => $savedraft,
|
||||
'error_handler' => function(...$args) use ($rcmail) {
|
||||
call_user_func_array([$rcmail->output, 'show_message'], $args);
|
||||
$rcmail->output->send('iframe');
|
||||
},
|
||||
'keepformatting' => !empty($_POST['_keepformatting']),
|
||||
]);
|
||||
|
||||
if (!isset($COMPOSE['attachments'])) {
|
||||
$COMPOSE['attachments'] = [];
|
||||
}
|
||||
|
||||
// Collect input for message headers
|
||||
$headers = $SENDMAIL->headers_input();
|
||||
|
||||
$COMPOSE['param']['message-id'] = $headers['Message-ID'];
|
||||
|
||||
$message_id = $headers['Message-ID'];
|
||||
$message_charset = $SENDMAIL->options['charset'];
|
||||
$message_body = rcube_utils::get_input_string('_message', rcube_utils::INPUT_POST, true, $message_charset);
|
||||
$isHtml = (bool) rcube_utils::get_input_string('_is_html', rcube_utils::INPUT_POST);
|
||||
|
||||
// Reset message body and attachments in Mailvelope mode
|
||||
if (isset($_POST['_pgpmime'])) {
|
||||
$pgp_mime = rcube_utils::get_input_string('_pgpmime', rcube_utils::INPUT_POST);
|
||||
$isHtml = false;
|
||||
$message_body = '';
|
||||
|
||||
// clear unencrypted attachments
|
||||
if (!empty($COMPOSE['attachments'])) {
|
||||
foreach ((array) $COMPOSE['attachments'] as $attach) {
|
||||
$rcmail->plugins->exec_hook('attachment_delete', $attach);
|
||||
}
|
||||
}
|
||||
|
||||
$COMPOSE['attachments'] = [];
|
||||
}
|
||||
|
||||
if ($isHtml) {
|
||||
$bstyle = [];
|
||||
|
||||
if ($font_size = $rcmail->config->get('default_font_size')) {
|
||||
$bstyle[] = 'font-size: ' . $font_size;
|
||||
}
|
||||
if ($font_family = $rcmail->config->get('default_font')) {
|
||||
$bstyle[] = 'font-family: ' . self::font_defs($font_family);
|
||||
}
|
||||
|
||||
// append doctype and html/body wrappers
|
||||
$bstyle = !empty($bstyle) ? (" style='" . implode('; ', $bstyle) . "'") : '';
|
||||
$message_body = '<html><head>'
|
||||
. '<meta http-equiv="Content-Type" content="text/html; charset='
|
||||
. ($message_charset ?: RCUBE_CHARSET) . '" /></head>'
|
||||
. "<body" . $bstyle . ">\r\n" . $message_body;
|
||||
}
|
||||
|
||||
if (!$savedraft) {
|
||||
if ($isHtml) {
|
||||
$b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
|
||||
$pre_style = 'margin: 0; padding: 0; font-family: monospace';
|
||||
|
||||
$message_body = preg_replace(
|
||||
[
|
||||
// remove empty signature div
|
||||
'/<div id="_rc_sig">( )?<\/div>[\s\r\n]*$/',
|
||||
// replace signature's div ID (#6073)
|
||||
'/ id="_rc_sig"/',
|
||||
// add inline css for blockquotes and container
|
||||
'/<blockquote>/',
|
||||
'/<div class="pre">/',
|
||||
// convert TinyMCE's new-line sequences (#1490463)
|
||||
'/<p> <\/p>/',
|
||||
],
|
||||
[
|
||||
'',
|
||||
' id="signature"',
|
||||
'<blockquote type="cite" style="'.$b_style.'">',
|
||||
'<div class="pre" style="'.$pre_style.'">',
|
||||
'<p><br /></p>',
|
||||
],
|
||||
$message_body
|
||||
);
|
||||
|
||||
rcube_utils::preg_error([
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not format HTML!"
|
||||
], true);
|
||||
}
|
||||
|
||||
// Check spelling before send
|
||||
if (
|
||||
$rcmail->config->get('spellcheck_before_send')
|
||||
&& $rcmail->config->get('enable_spellcheck')
|
||||
&& empty($COMPOSE['spell_checked'])
|
||||
&& !empty($message_body)
|
||||
) {
|
||||
$language = rcube_utils::get_input_string('_lang', rcube_utils::INPUT_GPC);
|
||||
$message_body = str_replace("\r\n", "\n", $message_body);
|
||||
$spellchecker = new rcube_spellchecker($language);
|
||||
$spell_result = $spellchecker->check($message_body, $isHtml);
|
||||
|
||||
if ($error = $spellchecker->error()) {
|
||||
rcube::raise_error([
|
||||
'code' => 500, 'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Spellcheck error: " . $error
|
||||
],
|
||||
true, false
|
||||
);
|
||||
}
|
||||
else {
|
||||
$COMPOSE['spell_checked'] = true;
|
||||
|
||||
if (!$spell_result) {
|
||||
if ($isHtml) {
|
||||
$result['words'] = $spellchecker->get();
|
||||
$result['dictionary'] = (bool) $rcmail->config->get('spellcheck_dictionary');
|
||||
}
|
||||
else {
|
||||
$result = $spellchecker->get_xml();
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('mispellingsfound', 'error');
|
||||
$rcmail->output->command('spellcheck_resume', $result);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generic footer for all messages
|
||||
if ($footer = $SENDMAIL->generic_message_footer($isHtml)) {
|
||||
$message_body .= "\r\n" . $footer;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isHtml) {
|
||||
$message_body .= "\r\n</body></html>\r\n";
|
||||
}
|
||||
|
||||
// sort attachments to make sure the order is the same as in the UI (#1488423)
|
||||
if ($files = rcube_utils::get_input_string('_attachments', rcube_utils::INPUT_POST)) {
|
||||
$files = explode(',', $files);
|
||||
$files = array_flip($files);
|
||||
foreach ($files as $idx => $val) {
|
||||
if (!empty($COMPOSE['attachments'][$idx])) {
|
||||
$files[$idx] = $COMPOSE['attachments'][$idx];
|
||||
unset($COMPOSE['attachments'][$idx]);
|
||||
}
|
||||
}
|
||||
|
||||
$COMPOSE['attachments'] = array_merge(array_filter($files), (array) $COMPOSE['attachments']);
|
||||
}
|
||||
|
||||
// Since we can handle big messages with disk usage, we need more time to work
|
||||
@set_time_limit(360);
|
||||
|
||||
// create PEAR::Mail_mime instance, set headers, body and params
|
||||
$MAIL_MIME = $SENDMAIL->create_message($headers, $message_body, $isHtml, $COMPOSE['attachments']);
|
||||
|
||||
// add stored attachments, if any
|
||||
if (is_array($COMPOSE['attachments'])) {
|
||||
self::add_attachments($SENDMAIL, $MAIL_MIME, $COMPOSE['attachments'], $isHtml);
|
||||
}
|
||||
|
||||
// compose PGP/Mime message
|
||||
if (!empty($pgp_mime)) {
|
||||
$MAIL_MIME->addAttachment(new Mail_mimePart('Version: 1', [
|
||||
'content_type' => 'application/pgp-encrypted',
|
||||
'description' => 'PGP/MIME version identification',
|
||||
]));
|
||||
|
||||
$MAIL_MIME->addAttachment(new Mail_mimePart($pgp_mime, [
|
||||
'content_type' => 'application/octet-stream',
|
||||
'filename' => 'encrypted.asc',
|
||||
'disposition' => 'inline',
|
||||
]));
|
||||
|
||||
$MAIL_MIME->setContentType('multipart/encrypted', ['protocol' => 'application/pgp-encrypted']);
|
||||
$MAIL_MIME->setParam('preamble', 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)');
|
||||
}
|
||||
|
||||
// This hook allows to modify the message before send or save action
|
||||
$plugin = $rcmail->plugins->exec_hook('message_ready', ['message' => $MAIL_MIME]);
|
||||
$MAIL_MIME = $plugin['message'];
|
||||
|
||||
// Deliver the message over SMTP
|
||||
if (!$savedraft && !$saveonly) {
|
||||
$sent = $SENDMAIL->deliver_message($MAIL_MIME);
|
||||
}
|
||||
|
||||
// Save the message in Drafts/Sent
|
||||
$saved = $SENDMAIL->save_message($MAIL_MIME);
|
||||
|
||||
// raise error if saving failed
|
||||
if (!$saved && $savedraft) {
|
||||
self::display_server_error('errorsaving');
|
||||
// start the auto-save timer again
|
||||
$rcmail->output->command('auto_save_start');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$store_target = $SENDMAIL->options['store_target'];
|
||||
$store_folder = $SENDMAIL->options['store_folder'];
|
||||
|
||||
// delete previous saved draft
|
||||
$drafts_mbox = $rcmail->config->get('drafts_mbox');
|
||||
$old_id = rcube_utils::get_input_string('_draft_saveid', rcube_utils::INPUT_POST);
|
||||
|
||||
if ($old_id && (!empty($sent) || $saved)) {
|
||||
$deleted = $rcmail->storage->delete_message($old_id, $drafts_mbox);
|
||||
|
||||
// raise error if deletion of old draft failed
|
||||
if (!$deleted) {
|
||||
rcube::raise_error([
|
||||
'code' => 800,
|
||||
'type' => 'imap',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Could not delete message from $drafts_mbox"
|
||||
],
|
||||
true, false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($savedraft) {
|
||||
// remember new draft-uid ($saved could be an UID or true/false here)
|
||||
if ($saved && is_bool($saved)) {
|
||||
$index = $rcmail->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
|
||||
$saved = $index->max();
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$plugin = $rcmail->plugins->exec_hook('message_draftsaved', [
|
||||
'msgid' => $message_id,
|
||||
'uid' => $saved,
|
||||
'folder' => $store_target
|
||||
]);
|
||||
|
||||
// display success
|
||||
$rcmail->output->show_message(!empty($plugin['message']) ? $plugin['message'] : 'messagesaved', 'confirmation');
|
||||
|
||||
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
|
||||
$COMPOSE['param']['draft_uid'] = $plugin['uid'];
|
||||
$rcmail->output->command('set_draft_id', $plugin['uid']);
|
||||
$rcmail->output->command('compose_field_hash', true);
|
||||
}
|
||||
|
||||
// start the auto-save timer again
|
||||
$rcmail->output->command('auto_save_start');
|
||||
}
|
||||
else {
|
||||
// Collect folders which could contain the composed message,
|
||||
// we'll refresh the list if currently opened folder is one of them (#1490238)
|
||||
$folders = [];
|
||||
$save_error = false;
|
||||
|
||||
if (!$saveonly) {
|
||||
if (in_array($COMPOSE['mode'], ['reply', 'forward', 'draft'])) {
|
||||
$folders[] = $COMPOSE['mailbox'];
|
||||
}
|
||||
if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
|
||||
$folders[] = $drafts_mbox;
|
||||
}
|
||||
}
|
||||
|
||||
if ($store_folder && !$saved) {
|
||||
$params = $saveonly ? null : ['prefix' => true];
|
||||
self::display_server_error('errorsavingsent', null, null, $params);
|
||||
|
||||
if ($saveonly) {
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$save_error = true;
|
||||
}
|
||||
else {
|
||||
$rcmail->plugins->exec_hook('attachments_cleanup', ['group' => $COMPOSE_ID]);
|
||||
$rcmail->session->remove('compose_data_' . $COMPOSE_ID);
|
||||
$_SESSION['last_compose_session'] = $COMPOSE_ID;
|
||||
|
||||
$rcmail->output->command('remove_compose_data', $COMPOSE_ID);
|
||||
|
||||
if ($store_folder) {
|
||||
$folders[] = $store_target;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = $rcmail->gettext($saveonly ? 'successfullysaved' : 'messagesent');
|
||||
|
||||
$rcmail->output->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
|
||||
}
|
||||
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
public static function add_attachments($SENDMAIL, $message, $attachments, $isHtml)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
foreach ($attachments as $id => $attachment) {
|
||||
// This hook retrieves the attachment contents from the file storage backend
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_get', $attachment);
|
||||
$is_inline = false;
|
||||
$dispurl = null;
|
||||
|
||||
if ($isHtml) {
|
||||
$dispurl = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
|
||||
$message_body = $message->getHTMLBody();
|
||||
$is_inline = preg_match($dispurl, $message_body);
|
||||
}
|
||||
|
||||
$ctype = isset($attachment['mimetype']) ? $attachment['mimetype'] : '';
|
||||
$ctype = str_replace('image/pjpeg', 'image/jpeg', $ctype); // #1484914
|
||||
|
||||
// inline image
|
||||
if ($is_inline) {
|
||||
// Mail_Mime does not support many inline attachments with the same name (#1489406)
|
||||
// we'll generate cid: urls here to workaround this
|
||||
$cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
|
||||
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $SENDMAIL->options['from'], $matches)) {
|
||||
$cid .= $matches[1];
|
||||
}
|
||||
else {
|
||||
$cid .= '@localhost';
|
||||
}
|
||||
|
||||
if ($dispurl && !empty($message_body)) {
|
||||
$message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
|
||||
|
||||
rcube_utils::preg_error([
|
||||
'line' => __LINE__,
|
||||
'file' => __FILE__,
|
||||
'message' => "Could not replace an image reference!"
|
||||
], true
|
||||
);
|
||||
|
||||
$message->setHTMLBody($message_body);
|
||||
}
|
||||
|
||||
if (!empty($attachment['data'])) {
|
||||
$message->addHTMLImage($attachment['data'], $ctype, $attachment['name'], false, $cid);
|
||||
}
|
||||
else {
|
||||
$message->addHTMLImage($attachment['path'], $ctype, $attachment['name'], true, $cid);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$file = !empty($attachment['data']) ? $attachment['data'] : $attachment['path'];
|
||||
$folding = (int) $rcmail->config->get('mime_param_folding');
|
||||
|
||||
$message->addAttachment($file,
|
||||
$ctype,
|
||||
$attachment['name'],
|
||||
empty($attachment['data']),
|
||||
$ctype == 'message/rfc822' ? '8bit' : 'base64',
|
||||
'attachment',
|
||||
$attachment['charset'] ?? null,
|
||||
'', '',
|
||||
$folding ? 'quoted-printable' : null,
|
||||
$folding == 2 ? 'quoted-printable' : null,
|
||||
'', RCUBE_CHARSET
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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: |
|
||||
| Send a message disposition notification for a specific mail |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_sendmdn 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();
|
||||
|
||||
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_POST)) {
|
||||
$sent = self::send_mdn($uid, $smtp_error);
|
||||
}
|
||||
|
||||
// show either confirm or error message
|
||||
if (!empty($sent)) {
|
||||
$rcmail->output->set_env('mdn_request', false);
|
||||
$rcmail->output->show_message('receiptsent', 'confirmation');
|
||||
}
|
||||
else if (!empty($smtp_error) && is_string($smtp_error)) {
|
||||
$rcmail->output->show_message($smtp_error, 'error');
|
||||
}
|
||||
else if (!empty($smtp_error) && !empty($smtp_error['label'])) {
|
||||
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('errorsendingreceipt', 'error');
|
||||
}
|
||||
|
||||
// Redirect to 'addcontact' action to save the sender address
|
||||
if (!empty($_POST['_save'])) {
|
||||
if ($_POST['_save'] == 5) {
|
||||
$_POST['_source'] = rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
}
|
||||
|
||||
$rcmail->action = 'addcontact';
|
||||
return;
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the MDN response
|
||||
*
|
||||
* @param mixed $message Original message object (rcube_message) or UID
|
||||
* @param array|string $smtp_error SMTP error array or (deprecated) string
|
||||
*
|
||||
* @return boolean Send status
|
||||
*/
|
||||
public static function send_mdn($message, &$smtp_error)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (!is_object($message) || !is_a($message, 'rcube_message')) {
|
||||
$message = new rcube_message($message);
|
||||
}
|
||||
|
||||
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
|
||||
($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
|
||||
) {
|
||||
$charset = $message->headers->charset;
|
||||
$identity = rcmail_sendmail::identity_select($message);
|
||||
$sender = format_email_recipient($identity['email'], $identity['name']);
|
||||
$recipient = array_first(rcube_mime::decode_address_list($message->headers->mdn_to, 1, true, $charset));
|
||||
$mailto = $recipient['mailto'];
|
||||
|
||||
$compose = new Mail_mime("\r\n");
|
||||
|
||||
$compose->setParam('text_encoding', 'quoted-printable');
|
||||
$compose->setParam('html_encoding', 'quoted-printable');
|
||||
$compose->setParam('head_encoding', 'quoted-printable');
|
||||
$compose->setParam('head_charset', RCUBE_CHARSET);
|
||||
$compose->setParam('html_charset', RCUBE_CHARSET);
|
||||
$compose->setParam('text_charset', RCUBE_CHARSET);
|
||||
|
||||
// compose headers array
|
||||
$headers = [
|
||||
'Date' => $rcmail->user_date(),
|
||||
'From' => $sender,
|
||||
'To' => $message->headers->mdn_to,
|
||||
'Subject' => $rcmail->gettext('receiptread') . ': ' . $message->subject,
|
||||
'Message-ID' => $rcmail->gen_message_id($identity['email']),
|
||||
'X-Sender' => $identity['email'],
|
||||
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
|
||||
'In-Reply-To' => $message->headers->messageID,
|
||||
];
|
||||
|
||||
$report = "Final-Recipient: rfc822; {$identity['email']}\r\n"
|
||||
. "Original-Message-ID: {$message->headers->messageID}\r\n"
|
||||
. "Disposition: manual-action/MDN-sent-manually; displayed\r\n";
|
||||
|
||||
if ($message->headers->to) {
|
||||
$report .= "Original-Recipient: {$message->headers->to}\r\n";
|
||||
}
|
||||
|
||||
if ($agent = $rcmail->config->get('useragent')) {
|
||||
$headers['User-Agent'] = $agent;
|
||||
$report .= "Reporting-UA: $agent\r\n";
|
||||
}
|
||||
|
||||
$to = rcube_mime::decode_mime_string($message->headers->to, $charset);
|
||||
$date = $rcmail->format_date($message->headers->date, $rcmail->config->get('date_long'));
|
||||
$body = $rcmail->gettext("yourmessage") . "\r\n\r\n" .
|
||||
"\t" . $rcmail->gettext("to") . ": {$to}\r\n" .
|
||||
"\t" . $rcmail->gettext("subject") . ": {$message->subject}\r\n" .
|
||||
"\t" . $rcmail->gettext("date") . ": {$date}\r\n" .
|
||||
"\r\n" . $rcmail->gettext("receiptnote");
|
||||
|
||||
$compose->headers(array_filter($headers));
|
||||
$compose->setContentType('multipart/report', ['report-type'=> 'disposition-notification']);
|
||||
$compose->setTXTBody(rcube_mime::wordwrap($body, 75, "\r\n"));
|
||||
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
|
||||
|
||||
// SMTP options
|
||||
$options = ['mdn_use_from' => (bool) $rcmail->config->get('mdn_use_from')];
|
||||
|
||||
$sent = $rcmail->deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options, true);
|
||||
|
||||
if ($sent) {
|
||||
$rcmail->storage->set_flag($message->uid, 'MDNSENT');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
<?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: |
|
||||
| Display a mail message similar as a usual mail application does |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_show extends rcmail_action_mail_index
|
||||
{
|
||||
protected static $MESSAGE;
|
||||
protected static $CLIENT_MIMETYPES = [];
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
self::$PRINT_MODE = $rcmail->action == 'print';
|
||||
|
||||
// Read browser capabilities and store them in session
|
||||
if ($caps = rcube_utils::get_input_string('_caps', rcube_utils::INPUT_GET)) {
|
||||
$browser_caps = [];
|
||||
foreach (explode(',', $caps) as $cap) {
|
||||
$cap = explode('=', $cap);
|
||||
$browser_caps[$cap[0]] = $cap[1];
|
||||
}
|
||||
|
||||
$_SESSION['browser_caps'] = $browser_caps;
|
||||
}
|
||||
|
||||
$msg_id = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
$uid = preg_replace('/\.[0-9.]+$/', '', $msg_id);
|
||||
$mbox_name = $rcmail->storage->get_folder();
|
||||
|
||||
// similar code as in program/steps/mail/get.inc
|
||||
if ($uid) {
|
||||
// set message format (need to be done before rcube_message construction)
|
||||
if (!empty($_GET['_format'])) {
|
||||
$prefer_html = $_GET['_format'] == 'html';
|
||||
$rcmail->config->set('prefer_html', $prefer_html);
|
||||
$_SESSION['msg_formats'][$mbox_name.':'.$uid] = $prefer_html;
|
||||
}
|
||||
else if (isset($_SESSION['msg_formats'][$mbox_name.':'.$uid])) {
|
||||
$rcmail->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$uid]);
|
||||
}
|
||||
|
||||
$MESSAGE = new rcube_message($msg_id, $mbox_name, !empty($_GET['_safe']));
|
||||
|
||||
self::$MESSAGE = $MESSAGE;
|
||||
|
||||
// if message not found (wrong UID)...
|
||||
if (empty($MESSAGE->headers)) {
|
||||
self::message_error();
|
||||
}
|
||||
|
||||
self::$CLIENT_MIMETYPES = self::supported_mimetypes();
|
||||
|
||||
// show images?
|
||||
self::check_safe($MESSAGE);
|
||||
|
||||
// set message charset as default
|
||||
if (!empty($MESSAGE->headers->charset)) {
|
||||
$rcmail->storage->set_charset($MESSAGE->headers->charset);
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['writeable_abook'])) {
|
||||
$_SESSION['writeable_abook'] = $rcmail->get_address_sources(true) ? true : false;
|
||||
}
|
||||
|
||||
$rcmail->output->set_pagetitle(abbreviate_string($MESSAGE->subject, 128, '...', true));
|
||||
|
||||
// set environment
|
||||
$rcmail->output->set_env('uid', $msg_id);
|
||||
$rcmail->output->set_env('safemode', $MESSAGE->is_safe);
|
||||
$rcmail->output->set_env('message_context', $MESSAGE->context);
|
||||
$rcmail->output->set_env('message_flags', array_keys(array_change_key_case((array) $MESSAGE->headers->flags)));
|
||||
$rcmail->output->set_env('sender', !empty($MESSAGE->sender) ? $MESSAGE->sender['string'] : '');
|
||||
$rcmail->output->set_env('mailbox', $mbox_name);
|
||||
$rcmail->output->set_env('username', $rcmail->get_user_name());
|
||||
$rcmail->output->set_env('permaurl', $rcmail->url(['_action' => 'show', '_uid' => $msg_id, '_mbox' => $mbox_name]));
|
||||
$rcmail->output->set_env('has_writeable_addressbook', $_SESSION['writeable_abook']);
|
||||
$rcmail->output->set_env('delimiter', $rcmail->storage->get_hierarchy_delimiter());
|
||||
$rcmail->output->set_env('mimetypes', self::$CLIENT_MIMETYPES);
|
||||
|
||||
if ($MESSAGE->headers->get('list-post', false)) {
|
||||
$rcmail->output->set_env('list_post', true);
|
||||
}
|
||||
|
||||
// set configuration
|
||||
self::set_env_config(['delete_junk', 'flag_for_deletion', 'read_when_deleted',
|
||||
'skip_deleted', 'display_next', 'forward_attachment', 'mailvelope_main_keyring']);
|
||||
|
||||
// set special folders
|
||||
foreach (['drafts', 'trash', 'junk'] as $mbox) {
|
||||
if ($folder = $rcmail->config->get($mbox . '_mbox')) {
|
||||
$rcmail->output->set_env($mbox . '_mailbox', $folder);
|
||||
}
|
||||
}
|
||||
|
||||
if ($MESSAGE->has_html_part()) {
|
||||
$prefer_html = $rcmail->config->get('prefer_html');
|
||||
$rcmail->output->set_env('optional_format', $prefer_html ? 'text' : 'html');
|
||||
}
|
||||
|
||||
$rcmail->output->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
|
||||
'movingmessage', 'deletingmessage', 'markingmessage', 'replyall', 'replylist',
|
||||
'bounce', 'bouncemsg', 'sendingmessage');
|
||||
|
||||
// check for unset disposition notification
|
||||
self::mdn_request_handler($MESSAGE);
|
||||
|
||||
if (empty($MESSAGE->headers->flags['SEEN']) && $MESSAGE->context === null) {
|
||||
$v = intval($rcmail->config->get('mail_read_time'));
|
||||
if ($v > 0) {
|
||||
$rcmail->output->set_env('mail_read_time', $v);
|
||||
}
|
||||
else if ($v == 0) {
|
||||
$rcmail->output->command('set_unread_message', $MESSAGE->uid, $mbox_name);
|
||||
$rcmail->plugins->exec_hook('message_read', [
|
||||
'uid' => $MESSAGE->uid,
|
||||
'mailbox' => $mbox_name,
|
||||
'message' => $MESSAGE,
|
||||
]);
|
||||
|
||||
$set_seen_flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'mailboxname' => [$this, 'mailbox_name_display'],
|
||||
'messageattachments' => [$this, 'message_attachments'],
|
||||
'messageobjects' => [$this, 'message_objects'],
|
||||
'messagesummary' => [$this, 'message_summary'],
|
||||
'messageheaders' => [$this, 'message_headers'],
|
||||
'messagefullheaders' => [$this, 'message_full_headers'],
|
||||
'messagebody' => [$this, 'message_body'],
|
||||
'contactphoto' => [$this, 'message_contactphoto'],
|
||||
]);
|
||||
|
||||
if ($rcmail->action == 'print' && $rcmail->output->template_exists('messageprint')) {
|
||||
$rcmail->output->send('messageprint', false);
|
||||
}
|
||||
else if ($rcmail->action == 'preview' && $rcmail->output->template_exists('messagepreview')) {
|
||||
$rcmail->output->send('messagepreview', false);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->send('message', false);
|
||||
}
|
||||
|
||||
// mark message as read
|
||||
if (!empty($set_seen_flag)) {
|
||||
if ($rcmail->storage->set_flag(self::$MESSAGE->uid, 'SEEN', $mbox_name)) {
|
||||
if ($count = self::get_unseen_count($mbox_name)) {
|
||||
self::set_unseen_count($mbox_name, $count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'messageattachments'.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message attachments list
|
||||
*/
|
||||
public static function message_attachments($attrib)
|
||||
{
|
||||
if (empty(self::$MESSAGE->attachments)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$out =
|
||||
$ol = '';
|
||||
$attachments = [];
|
||||
|
||||
foreach (self::$MESSAGE->attachments as $attach_prop) {
|
||||
$filename = self::attachment_name($attach_prop, true);
|
||||
$filesize = self::message_part_size($attach_prop);
|
||||
$mimetype = rcube_mime::fix_mimetype($attach_prop->mimetype);
|
||||
$class = rcube_utils::file2class($mimetype, $filename);
|
||||
$id = 'attach' . $attach_prop->mime_id;
|
||||
|
||||
if ($mimetype == 'application/octet-stream' && ($type = rcube_mime::file_ext_type($filename))) {
|
||||
$mimetype = $type;
|
||||
}
|
||||
|
||||
// Skip inline images
|
||||
if (strpos($mimetype, 'image/') === 0 && !self::is_attachment(self::$MESSAGE, $attach_prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($attrib['maxlength']) && mb_strlen($filename) > $attrib['maxlength']) {
|
||||
$title = $filename;
|
||||
$filename = abbreviate_string($filename, $attrib['maxlength']);
|
||||
}
|
||||
else {
|
||||
$title = '';
|
||||
}
|
||||
|
||||
$item = html::span('attachment-name', rcube::Q($filename))
|
||||
. html::span('attachment-size', '(' . rcube::Q($filesize) . ')');
|
||||
|
||||
$li_class = $class;
|
||||
|
||||
if (!self::$PRINT_MODE) {
|
||||
$link_attrs = [
|
||||
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
|
||||
'onclick' => sprintf('%s.command(\'load-attachment\',\'%s\',this); return false',
|
||||
rcmail_output::JS_OBJECT_NAME, $attach_prop->mime_id),
|
||||
'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
|
||||
'title' => $title,
|
||||
'class' => 'filename',
|
||||
];
|
||||
|
||||
if ($mimetype != 'message/rfc822' && empty($attach_prop->size)) {
|
||||
$li_class .= ' no-menu';
|
||||
$link_attrs['onclick'] = sprintf('%s.alert_dialog(%s.get_label(\'emptyattachment\')); return false',
|
||||
rcmail_output::JS_OBJECT_NAME, rcmail_output::JS_OBJECT_NAME);
|
||||
$rcmail->output->add_label('emptyattachment');
|
||||
}
|
||||
|
||||
$item = html::a($link_attrs, $item);
|
||||
$attachments[$attach_prop->mime_id] = $mimetype;
|
||||
}
|
||||
|
||||
$ol .= html::tag('li', ['class' => $li_class, 'id' => $id], $item);
|
||||
}
|
||||
|
||||
$out = html::tag('ul', $attrib, $ol, html::$common_attrib);
|
||||
|
||||
$rcmail->output->set_env('attachments', $attachments);
|
||||
$rcmail->output->add_gui_object('attachments', $attrib['id']);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function remote_objects_msg()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$attrib['id'] = 'remote-objects-message';
|
||||
$attrib['class'] = 'notice';
|
||||
$attrib['style'] = 'display: none';
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('blockedresources')));
|
||||
|
||||
$buttons = html::a([
|
||||
'href' => "#loadremote",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote')"
|
||||
],
|
||||
rcube::Q($rcmail->gettext('allow'))
|
||||
);
|
||||
|
||||
// add link to save sender in addressbook and reload message
|
||||
$show_images = $rcmail->config->get('show_images');
|
||||
if (!empty(self::$MESSAGE->sender['mailto']) && ($show_images == 1 || $show_images == 3)) {
|
||||
$arg = $show_images == 3 ? rcube_addressbook::TYPE_TRUSTED_SENDER : 'true';
|
||||
$buttons .= ' ' . html::a([
|
||||
'href' => "#loadremotealways",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote', $arg)",
|
||||
'style' => "white-space:nowrap"
|
||||
],
|
||||
rcube::Q($rcmail->gettext(['name' => 'alwaysallow', 'vars' => ['sender' => self::$MESSAGE->sender['mailto']]]))
|
||||
);
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('remoteobjectsmsg', $attrib['id']);
|
||||
|
||||
return html::div($attrib, $msg . ' ' . html::span('boxbuttons', $buttons));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning whenever a suspicious email address has been found in the message.
|
||||
*
|
||||
* @return string HTML content of the warning element
|
||||
*/
|
||||
public static function suspicious_content_warning()
|
||||
{
|
||||
if (empty(self::$SUSPICIOUS_EMAIL)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$attrib = [
|
||||
'id' => 'suspicious-content-message',
|
||||
'class' => 'notice',
|
||||
];
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('suspiciousemail')));
|
||||
|
||||
return html::div($attrib, $msg);
|
||||
}
|
||||
|
||||
public static function message_buttons()
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$delim = $rcmail->storage->get_hierarchy_delimiter();
|
||||
$dbox = $rcmail->config->get('drafts_mbox');
|
||||
|
||||
// the message is not a draft
|
||||
if (!empty(self::$MESSAGE->context)
|
||||
|| (
|
||||
!empty(self::$MESSAGE->folder)
|
||||
&& (self::$MESSAGE->folder != $dbox && strpos(self::$MESSAGE->folder, $dbox.$delim) !== 0)
|
||||
)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attrib['id'] = 'message-buttons';
|
||||
$attrib['class'] = 'information notice';
|
||||
|
||||
$msg = html::span(null, rcube::Q($rcmail->gettext('isdraft')))
|
||||
. ' '
|
||||
. html::a([
|
||||
'href' => "#edit",
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME.".command('edit')"
|
||||
],
|
||||
rcube::Q($rcmail->gettext('edit'))
|
||||
);
|
||||
|
||||
return html::div($attrib, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'messageobjects' that contains
|
||||
* warning/info boxes, buttons, etc. related to the displayed message.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message objects
|
||||
*/
|
||||
public static function message_objects($attrib)
|
||||
{
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'message-objects';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$content = [
|
||||
self::message_buttons(),
|
||||
self::remote_objects_msg(),
|
||||
self::suspicious_content_warning(),
|
||||
];
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_objects',
|
||||
['content' => $content, 'message' => self::$MESSAGE]);
|
||||
|
||||
$content = implode("\n", $plugin['content']);
|
||||
|
||||
return html::div($attrib, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the template object 'contactphoto'.
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content for the IMG tag
|
||||
*/
|
||||
public static function message_contactphoto($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$error_handler = false;
|
||||
$placeholder = 'data:image/gif;base64,' . rcmail_output::BLANK_GIF;
|
||||
|
||||
if (!empty($attrib['placeholder'])) {
|
||||
$placeholder = $rcmail->output->abs_url($attrib['placeholder'], true);
|
||||
$placeholder = $rcmail->output->asset_url($placeholder);
|
||||
|
||||
// set error handler on <img>
|
||||
$error_handler = true;
|
||||
$attrib['onerror'] = "this.onerror = null; this.src = '$placeholder';";
|
||||
}
|
||||
|
||||
if (!empty(self::$MESSAGE->sender)) {
|
||||
$photo_img = $rcmail->url([
|
||||
'_task' => 'addressbook',
|
||||
'_action' => 'photo',
|
||||
'_email' => self::$MESSAGE->sender['mailto'],
|
||||
'_error' => $error_handler ? 1 : null,
|
||||
'_bgcolor' => $attrib['bg-color'] ?? null
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$photo_img = $placeholder;
|
||||
}
|
||||
|
||||
return html::img(['src' => $photo_img, 'alt' => $rcmail->gettext('contactphoto')] + $attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns table with message headers
|
||||
*/
|
||||
public static function message_headers($attrib, $headers = null)
|
||||
{
|
||||
static $sa_attrib;
|
||||
|
||||
// keep header table attrib
|
||||
if (is_array($attrib) && !$sa_attrib && empty($attrib['valueof'])) {
|
||||
$sa_attrib = $attrib;
|
||||
}
|
||||
else if (!is_array($attrib) && is_array($sa_attrib)) {
|
||||
$attrib = $sa_attrib;
|
||||
}
|
||||
|
||||
if (!isset(self::$MESSAGE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// get associative array of headers object
|
||||
if (!$headers) {
|
||||
$headers_obj = self::$MESSAGE->headers;
|
||||
$headers = get_object_vars(self::$MESSAGE->headers);
|
||||
}
|
||||
else if (is_object($headers)) {
|
||||
$headers_obj = $headers;
|
||||
$headers = get_object_vars($headers_obj);
|
||||
}
|
||||
else {
|
||||
$headers_obj = rcube_message_header::from_array($headers);
|
||||
}
|
||||
|
||||
// show these headers
|
||||
$standard_headers = ['subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
|
||||
'mail-reply-to', 'mail-followup-to', 'date', 'priority'];
|
||||
$exclude_headers = !empty($attrib['exclude']) ? explode(',', $attrib['exclude']) : [];
|
||||
$output_headers = [];
|
||||
|
||||
$attr_max = $attrib['max'] ?? null;
|
||||
$attr_addicon = $attrib['addicon'] ?? null;
|
||||
$charset = !empty($headers['charset']) ? $headers['charset'] : null;
|
||||
|
||||
foreach ($standard_headers as $hkey) {
|
||||
$value = null;
|
||||
if (!empty($headers[$hkey])) {
|
||||
$value = $headers[$hkey];
|
||||
}
|
||||
else if (!empty($headers['others'][$hkey])) {
|
||||
$value = $headers['others'][$hkey];
|
||||
}
|
||||
else if (empty($attrib['valueof'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($hkey, $exclude_headers)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ishtml = false;
|
||||
$header_title = $rcmail->gettext(preg_replace('/(^mail-|-)/', '', $hkey));
|
||||
$header_value = null;
|
||||
|
||||
if ($hkey == 'date') {
|
||||
$header_value = $rcmail->format_date($value,
|
||||
self::$PRINT_MODE ? $rcmail->config->get('date_long', 'x') : null);
|
||||
}
|
||||
else if ($hkey == 'priority') {
|
||||
$header_value = html::span('prio' . $value, rcube::Q(self::localized_priority($value)));
|
||||
$ishtml = true;
|
||||
}
|
||||
else if ($hkey == 'replyto') {
|
||||
if ($value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'mail-reply-to') {
|
||||
if ((!isset($headers['replyto']) || $value != $headers['replyto']) && $value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'sender') {
|
||||
if ($value != $headers['from']) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
}
|
||||
else if ($hkey == 'mail-followup-to') {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
else if (in_array($hkey, ['from', 'to', 'cc', 'bcc'])) {
|
||||
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
|
||||
$ishtml = true;
|
||||
}
|
||||
else if ($hkey == 'subject' && empty($value)) {
|
||||
$header_value = $rcmail->gettext('nosubject');
|
||||
}
|
||||
else {
|
||||
$value = is_array($value) ? implode(' ', $value) : $value;
|
||||
$header_value = trim(rcube_mime::decode_header($value, $charset));
|
||||
}
|
||||
|
||||
if (empty($header_value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$output_headers[$hkey] = [
|
||||
'title' => $header_title,
|
||||
'value' => $header_value,
|
||||
'raw' => $value,
|
||||
'html' => $ishtml,
|
||||
];
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_headers_output', [
|
||||
'output' => $output_headers,
|
||||
'headers' => $headers_obj,
|
||||
'exclude' => $exclude_headers, // readonly
|
||||
'folder' => self::$MESSAGE->folder, // readonly
|
||||
'uid' => self::$MESSAGE->uid, // readonly
|
||||
]);
|
||||
|
||||
// single header value is requested
|
||||
if (!empty($attrib['valueof'])) {
|
||||
if (empty($plugin['output'][$attrib['valueof']])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$row = $plugin['output'][$attrib['valueof']];
|
||||
return !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
|
||||
}
|
||||
|
||||
// compose html table
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
foreach ($plugin['output'] as $hkey => $row) {
|
||||
$val = !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
|
||||
|
||||
$table->add(['class' => 'header-title'], rcube::SQ($row['title']));
|
||||
$table->add(['class' => 'header ' . $hkey], $val);
|
||||
}
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns element with "From|To <sender|recipient> on <date>"
|
||||
*/
|
||||
public static function message_summary($attrib)
|
||||
{
|
||||
if (!isset(self::$MESSAGE) || empty(self::$MESSAGE->headers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$header = self::$MESSAGE->context ? 'from' : self::message_list_smart_column_name();
|
||||
$label = 'shortheader' . $header;
|
||||
$date = $rcmail->format_date(self::$MESSAGE->headers->date, $rcmail->config->get('date_long', 'x'));
|
||||
$user = self::$MESSAGE->headers->$header;
|
||||
|
||||
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->cc)) {
|
||||
$user = self::$MESSAGE->headers->cc;
|
||||
}
|
||||
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->bcc)) {
|
||||
$user = self::$MESSAGE->headers->bcc;
|
||||
}
|
||||
|
||||
$vars[$header] = self::address_string($user, 1, true, $attrib['addicon'], self::$MESSAGE->headers->charset);
|
||||
$vars['date'] = html::span('text-nowrap', $date);
|
||||
|
||||
if (empty($user)) {
|
||||
$label = 'shortheaderdate';
|
||||
}
|
||||
|
||||
$out = html::span(null, $rcmail->gettext(['name' => $label, 'vars' => $vars]));
|
||||
|
||||
return html::div($attrib, $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Priority header value into a localized string
|
||||
*/
|
||||
public static function localized_priority($value)
|
||||
{
|
||||
$labels_map = [
|
||||
'1' => 'highest',
|
||||
'2' => 'high',
|
||||
'3' => 'normal',
|
||||
'4' => 'low',
|
||||
'5' => 'lowest',
|
||||
];
|
||||
|
||||
if ($value && !empty($labels_map[$value])) {
|
||||
return rcmail::get_instance()->gettext($labels_map[$value]);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block to show full message headers
|
||||
*/
|
||||
public static function message_full_headers($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$html = html::div(['id' => "all-headers", 'class' => "all", 'style' => 'display:none'],
|
||||
html::div(['id' => 'headers-source'], ''));
|
||||
|
||||
$html .= html::div([
|
||||
'class' => "more-headers show-headers",
|
||||
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('show-headers','',this)",
|
||||
'title' => $rcmail->gettext('togglefullheaders')
|
||||
], '');
|
||||
|
||||
$rcmail->output->add_gui_object('all_headers_row', 'all-headers');
|
||||
$rcmail->output->add_gui_object('all_headers_box', 'headers-source');
|
||||
|
||||
return html::div($attrib, $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'messagebody' GUI object
|
||||
*
|
||||
* @param array $attrib Named parameters
|
||||
*
|
||||
* @return string HTML content showing the message body
|
||||
*/
|
||||
public static function message_body($attrib)
|
||||
{
|
||||
if (
|
||||
empty(self::$MESSAGE)
|
||||
|| (!is_array(self::$MESSAGE->parts) && empty(self::$MESSAGE->body))
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmailMsgBody';
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$safe_mode = self::$MESSAGE->is_safe || !empty($_GET['_safe']);
|
||||
$out = '';
|
||||
$part_no = 0;
|
||||
|
||||
$header_attrib = [];
|
||||
foreach ($attrib as $attr => $value) {
|
||||
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs)) {
|
||||
$header_attrib[$regs[1]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty(self::$MESSAGE->parts)) {
|
||||
foreach (self::$MESSAGE->parts as $part) {
|
||||
if ($part->type == 'headers') {
|
||||
$out .= html::div('message-partheaders', self::message_headers(count($header_attrib) ? $header_attrib : null, $part->headers));
|
||||
}
|
||||
else if ($part->type == 'content') {
|
||||
// unsupported (e.g. encrypted)
|
||||
if (!empty($part->realtype)) {
|
||||
if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
|
||||
if (
|
||||
!empty($_SESSION['browser_caps']['pgpmime'])
|
||||
&& ($pgp_mime_part = self::$MESSAGE->get_multipart_encrypted_part())
|
||||
) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('externalmessagedecryption'));
|
||||
$rcmail->output->set_env('pgp_mime_part', $pgp_mime_part->mime_id);
|
||||
$rcmail->output->set_env('pgp_mime_container', '#' . $attrib['id']);
|
||||
$rcmail->output->add_label('loadingdata');
|
||||
}
|
||||
|
||||
if (!self::$MESSAGE->encrypted_part) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('encryptedmessage'));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (!$part->size) {
|
||||
continue;
|
||||
}
|
||||
// Check if we have enough memory to handle the message in it
|
||||
// #1487424: we need up to 10x more memory than the body
|
||||
else if (!rcube_utils::mem_check($part->size * 10)) {
|
||||
$out .= self::part_too_big_message(self::$MESSAGE, $part->mime_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// fetch part body
|
||||
$body = self::$MESSAGE->get_part_body($part->mime_id, true);
|
||||
|
||||
// message is cached but not exists (#1485443), or other error
|
||||
if ($body === false) {
|
||||
// Don't bail out if it is only one-of-many part of the message (#6854)
|
||||
if (strlen($out)) {
|
||||
$out .= html::span('part-notice', $rcmail->gettext('messageopenerror'));
|
||||
continue;
|
||||
}
|
||||
|
||||
self::message_error();
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
|
||||
['part' => $part, 'prefix' => '', 'message' => self::$MESSAGE]);
|
||||
|
||||
// Set attributes of the part container
|
||||
$container_class = $part->ctype_secondary == 'html' ? 'message-htmlpart' : 'message-part';
|
||||
$container_id = $container_class . (++$part_no);
|
||||
$container_attrib = ['class' => $container_class, 'id' => $container_id];
|
||||
|
||||
$body_args = [
|
||||
'safe' => $safe_mode,
|
||||
'plain' => !$rcmail->config->get('prefer_html'),
|
||||
'css_prefix' => 'v' . $part_no,
|
||||
'body_class' => 'rcmBody',
|
||||
'container_id' => $container_id,
|
||||
'container_attrib' => $container_attrib,
|
||||
];
|
||||
|
||||
// Parse the part content for display
|
||||
$body = self::print_body($body, $part, $body_args);
|
||||
|
||||
// check if the message body is PGP encrypted
|
||||
if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) {
|
||||
$rcmail->output->set_env('is_pgp_content', '#' . $container_id);
|
||||
}
|
||||
|
||||
if ($part->ctype_secondary == 'html') {
|
||||
$body = self::html4inline($body, $body_args);
|
||||
}
|
||||
|
||||
$out .= html::div($body_args['container_attrib'], $plugin['prefix'] . $body);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Check if we have enough memory to handle the message in it
|
||||
// #1487424: we need up to 10x more memory than the body
|
||||
if (isset(self::$MESSAGE->body) && !rcube_utils::mem_check(strlen(self::$MESSAGE->body) * 10)) {
|
||||
$out .= self::part_too_big_message(self::$MESSAGE, 0);
|
||||
}
|
||||
else {
|
||||
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
|
||||
['part' => self::$MESSAGE, 'prefix' => '']);
|
||||
|
||||
$out .= html::div('message-part',
|
||||
$plugin['prefix'] . self::plain_body(self::$MESSAGE->body));
|
||||
}
|
||||
}
|
||||
|
||||
// list images after mail body
|
||||
if ($rcmail->config->get('inline_images', true) && !empty(self::$MESSAGE->attachments)) {
|
||||
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
|
||||
$show_label = rcube::Q($rcmail->gettext('showattachment'));
|
||||
$download_label = rcube::Q($rcmail->gettext('download'));
|
||||
|
||||
foreach (self::$MESSAGE->attachments as $attach_prop) {
|
||||
// Content-Type: image/*...
|
||||
if ($mimetype = self::part_image_type($attach_prop)) {
|
||||
// Skip inline images
|
||||
if (!self::is_attachment(self::$MESSAGE, $attach_prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// display thumbnails
|
||||
if ($thumbnail_size) {
|
||||
$supported = in_array($mimetype, self::$CLIENT_MIMETYPES);
|
||||
$show_link_attr = [
|
||||
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
|
||||
'onclick' => sprintf(
|
||||
'%s.command(\'load-attachment\',\'%s\',this); return false',
|
||||
rcmail_output::JS_OBJECT_NAME,
|
||||
$attach_prop->mime_id
|
||||
)
|
||||
];
|
||||
$download_link_attr = [
|
||||
'href' => $show_link_attr['href'] . '&_download=1',
|
||||
];
|
||||
$show_link = html::a($show_link_attr + ['class' => 'open'], $show_label);
|
||||
$download_link = html::a($download_link_attr + ['class' => 'download'], $download_label);
|
||||
|
||||
$out .= html::p(['class' => 'image-attachment', 'style' => $supported ? '' : 'display:none'],
|
||||
html::a($show_link_attr + ['class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)],
|
||||
html::img([
|
||||
'class' => 'image-thumbnail',
|
||||
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image') . '&_thumb=1',
|
||||
'title' => $attach_prop->filename,
|
||||
'alt' => $attach_prop->filename,
|
||||
'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
|
||||
'onload' => $supported ? '' : '$(this).parents(\'p.image-attachment\').show()',
|
||||
])
|
||||
) .
|
||||
html::span('image-filename', rcube::Q($attach_prop->filename)) .
|
||||
html::span('image-filesize', rcube::Q(self::message_part_size($attach_prop))) .
|
||||
html::span('attachment-links', ($supported ? $show_link . ' ' : '') . $download_link) .
|
||||
html::br(['style' => 'clear:both'])
|
||||
);
|
||||
}
|
||||
else {
|
||||
$out .= html::tag('fieldset', 'image-attachment',
|
||||
html::tag('legend', 'image-filename', rcube::Q($attach_prop->filename)) .
|
||||
html::p(['align' => 'center'],
|
||||
html::img([
|
||||
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image'),
|
||||
'title' => $attach_prop->filename,
|
||||
'alt' => $attach_prop->filename,
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tell client that there are blocked remote objects
|
||||
if (self::$REMOTE_OBJECTS && !$safe_mode) {
|
||||
$rcmail->output->set_env('blockedobjects', true);
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('messagebody', $attrib['id']);
|
||||
|
||||
return html::div($attrib, $out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a HTML notice element for too big message parts
|
||||
*
|
||||
* @param rcube_message $message Email message object
|
||||
* @param string $part_id Message part identifier
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
public static function part_too_big_message($message, $part_id)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$token = $rcmail->get_request_token();
|
||||
$url = $rcmail->url([
|
||||
'task' => 'mail',
|
||||
'action' => 'get',
|
||||
'download' => 1,
|
||||
'uid' => $message->uid,
|
||||
'part' => $part_id,
|
||||
'mbox' => $message->folder,
|
||||
'token' => $token,
|
||||
]);
|
||||
|
||||
return html::span('part-notice', $rcmail->gettext('messagetoobig')
|
||||
. ' ' . html::a($url, $rcmail->gettext('download')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle disposition notification requests
|
||||
*
|
||||
* @param rcube_message $message Email message object
|
||||
*/
|
||||
public static function mdn_request_handler($message)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($message->headers->mdn_to
|
||||
&& $message->context === null
|
||||
&& !empty($message->sender['mailto'])
|
||||
&& empty($message->headers->flags['MDNSENT'])
|
||||
&& empty($message->headers->flags['SEEN'])
|
||||
&& ($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
|
||||
&& $message->folder != $rcmail->config->get('drafts_mbox')
|
||||
&& $message->folder != $rcmail->config->get('sent_mbox')
|
||||
) {
|
||||
$mdn_cfg = intval($rcmail->config->get('mdn_requests'));
|
||||
$exists = $mdn_cfg == 1;
|
||||
|
||||
// Check sender existence in contacts
|
||||
// 3 and 4 = my contacts, 5 and 6 = trusted senders
|
||||
if ($mdn_cfg == 3 || $mdn_cfg == 4 || $mdn_cfg == 5 || $mdn_cfg == 6) {
|
||||
$type = rcube_addressbook::TYPE_TRUSTED_SENDER;
|
||||
|
||||
if ($mdn_cfg == 3 || $mdn_cfg == 4) {
|
||||
$type |= rcube_addressbook::TYPE_WRITEABLE | rcube_addressbook::TYPE_RECIPIENT;
|
||||
}
|
||||
|
||||
if ($rcmail->contact_exists($message->sender['mailto'], $type)) {
|
||||
$exists = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
// Send MDN
|
||||
if (rcmail_action_mail_sendmdn::send_mdn($message, $smtp_error)) {
|
||||
$rcmail->output->show_message('receiptsent', 'confirmation');
|
||||
}
|
||||
else if ($smtp_error && is_string($smtp_error)) {
|
||||
$rcmail->output->show_message($smtp_error, 'error');
|
||||
}
|
||||
else if ($smtp_error && !empty($smtp_error['label'])) {
|
||||
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('errorsendingreceipt', 'error');
|
||||
}
|
||||
}
|
||||
else if ($mdn_cfg != 2 && $mdn_cfg != 4 && $mdn_cfg != 6) {
|
||||
// Ask the user
|
||||
$rcmail->output->add_label('sendreceipt', 'mdnrequest', 'send', 'sendalwaysto', 'ignore');
|
||||
$rcmail->output->set_env('mdn_request_save', $mdn_cfg == 3 || $mdn_cfg == 5 ? $mdn_cfg : 0);
|
||||
$rcmail->output->set_env('mdn_request_sender', $message->sender);
|
||||
$rcmail->output->set_env('mdn_request', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the message part is a normal attachment
|
||||
*
|
||||
* @param rcube_message $message Message object
|
||||
* @param rcube_message_part $part Message part
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_attachment($message, $part)
|
||||
{
|
||||
// Inline attachment with Content-Id specified
|
||||
if (!empty($part->content_id) && $part->disposition == 'inline') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any image attached to multipart/related message (#7184)
|
||||
$parent_id = preg_replace('/\.[0-9]+$/', '', $part->mime_id);
|
||||
$parent = $message->mime_parts[$parent_id] ?? null;
|
||||
|
||||
if ($parent && $parent->mimetype == 'multipart/related') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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: |
|
||||
| Display a mail message similar as a usual mail application does |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_mail_viewsource extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (!empty($_GET['_save'])) {
|
||||
$rcmail->request_security_check(rcube_utils::INPUT_GET);
|
||||
}
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
// similar code as in program/steps/mail/get.inc
|
||||
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET)) {
|
||||
if ($pos = strpos($uid, '.')) {
|
||||
$message = new rcube_message($uid);
|
||||
$headers = $message->headers;
|
||||
$part_id = substr($uid, $pos + 1);
|
||||
}
|
||||
else {
|
||||
$headers = $rcmail->storage->get_message_headers($uid);
|
||||
}
|
||||
|
||||
$charset = $headers->charset ?: $rcmail->config->get('default_charset', RCUBE_CHARSET);
|
||||
|
||||
if (!empty($_GET['_save'])) {
|
||||
$subject = rcube_mime::decode_header($headers->subject, $headers->charset);
|
||||
$filename = self::filename_from_subject(mb_substr($subject, 0, 128));
|
||||
$filename = ($filename ?: $uid) . '.eml';
|
||||
|
||||
$rcmail->output->download_headers($filename, [
|
||||
'length' => $headers->size,
|
||||
'type' => 'text/plain',
|
||||
'type_charset' => $charset,
|
||||
]);
|
||||
}
|
||||
else {
|
||||
// Make sure it works in an iframe (#9084)
|
||||
$rcmail->output->page_headers();
|
||||
|
||||
header("Content-Type: text/plain; charset={$charset}");
|
||||
}
|
||||
|
||||
if (isset($part_id) && isset($message)) {
|
||||
$message->get_part_body($part_id, empty($_GET['_save']), 0, -1);
|
||||
}
|
||||
else {
|
||||
$rcmail->storage->print_raw_body($uid, empty($_GET['_save']));
|
||||
}
|
||||
}
|
||||
else {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Message UID $uid not found"
|
||||
],
|
||||
true, true
|
||||
);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert message subject into filename
|
||||
*/
|
||||
public static function filename_from_subject($str)
|
||||
{
|
||||
$str = preg_replace('/[:\t\n\r\0\x0B\/]+\s*/', ' ', $str);
|
||||
|
||||
return trim($str, " \t\n\r\0\x0B./_");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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: |
|
||||
| Display license information about program and enabled plugins |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_about 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();
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext('about'));
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'supportlink' => [$this, 'supportlink'],
|
||||
'pluginlist' => [$this, 'plugins_list'],
|
||||
'copyright' => function() {
|
||||
return 'Copyright © 2005-2022, The Roundcube Dev Team';
|
||||
},
|
||||
'license' => function() {
|
||||
return 'This program is free software; you can redistribute it and/or modify it under the terms '
|
||||
. 'of the <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a> '
|
||||
. 'as published by the Free Software Foundation, either version 3 of the License, '
|
||||
. 'or (at your option) any later version.<br/>'
|
||||
. 'Some <a href="https://roundcube.net/license" target="_blank">exceptions</a> '
|
||||
. 'for skins & plugins apply.';
|
||||
},
|
||||
]);
|
||||
|
||||
$rcmail->output->send('about');
|
||||
}
|
||||
|
||||
public static function supportlink($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($url = $rcmail->config->get('support_url')) {
|
||||
$label = !empty($attrib['label']) ? $attrib['label'] : 'support';
|
||||
$attrib['href'] = $url;
|
||||
|
||||
return html::a($attrib, $rcmail->gettext($label));
|
||||
}
|
||||
}
|
||||
|
||||
public static function plugins_list($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmpluginlist';
|
||||
}
|
||||
|
||||
$plugins = array_filter($rcmail->plugins->active_plugins);
|
||||
$plugin_info = [];
|
||||
|
||||
foreach ($plugins as $name) {
|
||||
if ($info = $rcmail->plugins->get_info($name)) {
|
||||
$plugin_info[$name] = $info;
|
||||
}
|
||||
}
|
||||
|
||||
// load info from required plugins, too
|
||||
foreach ($plugin_info as $name => $info) {
|
||||
if (!empty($info['require']) && is_array($info['require'])) {
|
||||
foreach ($info['require'] as $req_name) {
|
||||
if (!isset($plugin_info[$req_name]) && ($req_info = $rcmail->plugins->get_info($req_name))) {
|
||||
$plugin_info[$req_name] = $req_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($plugin_info)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ksort($plugin_info, SORT_LOCALE_STRING);
|
||||
|
||||
$table = new html_table($attrib);
|
||||
|
||||
// add table header
|
||||
$table->add_header('name', $rcmail->gettext('plugin'));
|
||||
$table->add_header('version', $rcmail->gettext('version'));
|
||||
$table->add_header('license', $rcmail->gettext('license'));
|
||||
$table->add_header('source', $rcmail->gettext('source'));
|
||||
|
||||
foreach ($plugin_info as $name => $data) {
|
||||
$uri = !empty($data['src_uri']) ? $data['src_uri'] : ($data['uri'] ?? '');
|
||||
if ($uri && stripos($uri, 'http') !== 0) {
|
||||
$uri = 'http://' . $uri;
|
||||
}
|
||||
|
||||
if ($uri) {
|
||||
$uri = html::a([
|
||||
'target' => '_blank',
|
||||
'href' => rcube::Q($uri)
|
||||
],
|
||||
rcube::Q($rcmail->gettext('download'))
|
||||
);
|
||||
}
|
||||
|
||||
$license = isset($data['license']) ? $data['license'] : '';
|
||||
|
||||
if (!empty($data['license_uri'])) {
|
||||
$license = html::a([
|
||||
'target' => '_blank',
|
||||
'href' => rcube::Q($data['license_uri'])
|
||||
],
|
||||
rcube::Q($data['license'])
|
||||
);
|
||||
}
|
||||
else {
|
||||
$license = rcube::Q($license);
|
||||
}
|
||||
|
||||
$table->add_row();
|
||||
$table->add('name', rcube::Q(!empty($data['name']) ? $data['name'] : $name));
|
||||
$table->add('version', !empty($data['version']) ? rcube::Q($data['version']) : '');
|
||||
$table->add('license', $license);
|
||||
$table->add('source', $uri);
|
||||
}
|
||||
|
||||
return $table->show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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 to create a folder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_create extends rcmail_action_settings_folder_edit
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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 to delete a folder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (strlen($mbox)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('folder_delete', ['name' => $mbox]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$deleted = $storage->delete_folder($plugin['name']);
|
||||
}
|
||||
else {
|
||||
$deleted = $plugin['result'];
|
||||
}
|
||||
|
||||
// #1488692: update session
|
||||
if ($deleted && isset($_SESSION['mbox']) && $_SESSION['mbox'] === $mbox) {
|
||||
$rcmail->session->remove('mbox');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($deleted)) {
|
||||
// Remove folder and subfolders rows
|
||||
$rcmail->output->command('remove_folder_row', $mbox);
|
||||
$rcmail->output->show_message('folderdeleted', 'confirmation');
|
||||
// Clear content frame
|
||||
$rcmail->output->command('subscription_select');
|
||||
$rcmail->output->command('set_quota', self::quota_content());
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
<?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 to edit a folder |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_edit extends rcmail_action_settings_folders
|
||||
{
|
||||
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();
|
||||
|
||||
$rcmail->output->add_handlers([
|
||||
'folderdetails' => [$this, 'folder_form'],
|
||||
]);
|
||||
|
||||
$rcmail->output->add_label('nonamewarning');
|
||||
|
||||
$rcmail->output->send('folderedit');
|
||||
}
|
||||
|
||||
public static function folder_form($attrib)
|
||||
{
|
||||
// WARNING: folder names in UI are encoded with RCUBE_CHARSET
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
|
||||
// edited folder name (empty in create-folder mode)
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GPC, true);
|
||||
|
||||
// predefined path for new folder
|
||||
$parent = rcube_utils::get_input_string('_path', rcube_utils::INPUT_GPC, true);
|
||||
|
||||
$threading_supported = $storage->get_capability('THREAD');
|
||||
$dual_use_supported = $storage->get_capability(rcube_storage::DUAL_USE_FOLDERS);
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
|
||||
// Get mailbox parameters
|
||||
if (strlen($mbox)) {
|
||||
$options = self::folder_options($mbox);
|
||||
$namespace = $storage->get_namespace();
|
||||
|
||||
$path = explode($delimiter, $mbox);
|
||||
$folder = array_pop($path);
|
||||
$path = implode($delimiter, $path);
|
||||
$folder = rcube_charset::convert($folder, 'UTF7-IMAP');
|
||||
|
||||
$hidden_fields = ['name' => '_mbox', 'value' => $mbox];
|
||||
}
|
||||
else {
|
||||
$options = [];
|
||||
$path = $parent;
|
||||
$folder = '';
|
||||
$hidden_fields = [];
|
||||
|
||||
// allow creating subfolders of INBOX folder
|
||||
if ($path == 'INBOX') {
|
||||
$path = $storage->mod_folder($path, 'in');
|
||||
}
|
||||
}
|
||||
|
||||
// remove personal namespace prefix
|
||||
$path_id = null;
|
||||
if (strlen($path)) {
|
||||
$path_id = $path;
|
||||
$path = $storage->mod_folder($path . $delimiter);
|
||||
if (($path[strlen($path)-1] ?? '') == $delimiter) {
|
||||
$path = substr($path, 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
$form = [];
|
||||
|
||||
// General tab
|
||||
$form['props'] = [
|
||||
'name' => $rcmail->gettext('properties'),
|
||||
];
|
||||
|
||||
// Location (name)
|
||||
if (!empty($options['protected'])) {
|
||||
$foldername = str_replace($delimiter, ' » ', rcube::Q(self::localize_foldername($mbox, false, true)));
|
||||
}
|
||||
else if (!empty($options['norename'])) {
|
||||
$foldername = rcube::Q($folder);
|
||||
}
|
||||
else {
|
||||
if (isset($_POST['_name'])) {
|
||||
$folder = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true));
|
||||
}
|
||||
|
||||
$foldername = new html_inputfield(['name' => '_name', 'id' => '_name', 'size' => 30, 'class' => 'form-control']);
|
||||
$foldername = '<span class="input-group">' . $foldername->show($folder);
|
||||
|
||||
if (!empty($options['special']) && ($sname = self::localize_foldername($mbox, false, true)) != $folder) {
|
||||
$foldername .= ' <span class="input-group-append"><span class="input-group-text">(' . rcube::Q($sname) .')</span></span>';
|
||||
}
|
||||
|
||||
$foldername .= '</span>';
|
||||
}
|
||||
|
||||
$form['props']['fieldsets']['location'] = [
|
||||
'name' => $rcmail->gettext('location'),
|
||||
'content' => [
|
||||
'name' => [
|
||||
'label' => $rcmail->gettext('foldername'),
|
||||
'value' => $foldername,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($options) && (!empty($options['norename']) || !empty($options['protected']))) {
|
||||
// prevent user from moving folder
|
||||
$hidden_path = new html_hiddenfield(['name' => '_parent', 'value' => $path]);
|
||||
$form['props']['fieldsets']['location']['content']['name']['value'] .= $hidden_path->show();
|
||||
}
|
||||
else {
|
||||
$selected = $_POST['_parent'] ?? $path_id;
|
||||
$exceptions = [$mbox];
|
||||
|
||||
// Exclude 'prefix' namespace from parent folders list (#1488349)
|
||||
// If INBOX. namespace exists, folders created as INBOX subfolders
|
||||
// will be listed at the same level - selecting INBOX as a parent does nothing
|
||||
if ($prefix = $storage->get_namespace('prefix')) {
|
||||
$exceptions[] = substr($prefix, 0, -1);
|
||||
}
|
||||
|
||||
$select = self::folder_selector([
|
||||
'id' => '_parent',
|
||||
'name' => '_parent',
|
||||
'noselection' => '---',
|
||||
'maxlength' => 150,
|
||||
'unsubscribed' => true,
|
||||
'skip_noinferiors' => true,
|
||||
'exceptions' => $exceptions,
|
||||
'additional' => is_string($selected) && strlen($selected) ? [$selected] : null,
|
||||
]);
|
||||
|
||||
$form['props']['fieldsets']['location']['content']['parent'] = [
|
||||
'label' => $rcmail->gettext('parentfolder'),
|
||||
'value' => $select->show($selected),
|
||||
];
|
||||
}
|
||||
|
||||
// Settings
|
||||
$form['props']['fieldsets']['settings'] = [
|
||||
'name' => $rcmail->gettext('settings'),
|
||||
];
|
||||
|
||||
// For servers that do not support both sub-folders and messages in a folder
|
||||
if (!$dual_use_supported) {
|
||||
if (!strlen($mbox)) {
|
||||
$select = new html_select(['name' => '_type', 'id' => '_type']);
|
||||
$select->add($rcmail->gettext('dualusemail'), 'mail');
|
||||
$select->add($rcmail->gettext('dualusefolder'), 'folder');
|
||||
|
||||
$value = rcube_utils::get_input_string('_type', rcube_utils::INPUT_POST);
|
||||
$value = $select->show($value ?: 'mail');
|
||||
}
|
||||
else {
|
||||
$value = $options['noselect'] ? 'folder' : 'mail';
|
||||
$value = $rcmail->gettext('dualuse' . $value);
|
||||
}
|
||||
|
||||
$form['props']['fieldsets']['settings']['content']['type'] = [
|
||||
'label' => $rcmail->gettext('dualuselabel'),
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
// Settings: threading
|
||||
if ($threading_supported && ($mbox == 'INBOX' || (empty($options['noselect']) && empty($options['is_root'])))) {
|
||||
$value = 0;
|
||||
$select = new html_select(['name' => '_viewmode', 'id' => '_viewmode']);
|
||||
|
||||
$select->add($rcmail->gettext('list'), 0);
|
||||
$select->add($rcmail->gettext('threads'), 1);
|
||||
|
||||
if (isset($_POST['_viewmode'])) {
|
||||
$value = (int) $_POST['_viewmode'];
|
||||
}
|
||||
else if (strlen($mbox)) {
|
||||
$a_threaded = $rcmail->config->get('message_threading', []);
|
||||
$default_mode = $rcmail->config->get('default_list_mode', 'list');
|
||||
|
||||
$value = (int) ($a_threaded[$mbox] ?? $default_mode == 'threads');
|
||||
}
|
||||
|
||||
$form['props']['fieldsets']['settings']['content']['viewmode'] = [
|
||||
'label' => $rcmail->gettext('listmode'),
|
||||
'value' => $select->show($value),
|
||||
];
|
||||
}
|
||||
|
||||
$msgcount = 0;
|
||||
|
||||
// Information (count, size) - Edit mode
|
||||
if (strlen($mbox)) {
|
||||
// Number of messages
|
||||
$form['props']['fieldsets']['info'] = [
|
||||
'name' => $rcmail->gettext('info'),
|
||||
'content' => []
|
||||
];
|
||||
|
||||
if ((!$options['noselect'] && !$options['is_root']) || $mbox == 'INBOX') {
|
||||
$msgcount = (int) $storage->count($mbox, 'ALL', true, false);
|
||||
|
||||
if ($msgcount) {
|
||||
// Get the size on servers with supposed-to-be-fast method for that
|
||||
if ($storage->get_capability('STATUS=SIZE')) {
|
||||
$size = $storage->folder_size($mbox);
|
||||
if ($size !== false) {
|
||||
$size = self::show_bytes($size);
|
||||
}
|
||||
}
|
||||
|
||||
// create link with folder-size command
|
||||
if (!isset($size) || $size === false) {
|
||||
$onclick = sprintf("return %s.command('folder-size', '%s', this)",
|
||||
rcmail_output::JS_OBJECT_NAME, rcube::JQ($mbox));
|
||||
|
||||
$attr = ['href' => '#', 'onclick' => $onclick, 'id' => 'folder-size'];
|
||||
$size = html::a($attr, $rcmail->gettext('getfoldersize'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// no messages -> zero size
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
$form['props']['fieldsets']['info']['content']['count'] = [
|
||||
'label' => $rcmail->gettext('messagecount'),
|
||||
'value' => $msgcount
|
||||
];
|
||||
$form['props']['fieldsets']['info']['content']['size'] = [
|
||||
'label' => $rcmail->gettext('size'),
|
||||
'value' => $size,
|
||||
];
|
||||
}
|
||||
|
||||
// show folder type only if we have non-private namespaces
|
||||
if (!empty($namespace['shared']) || !empty($namespace['others'])) {
|
||||
$form['props']['fieldsets']['info']['content']['foldertype'] = [
|
||||
'label' => $rcmail->gettext('foldertype'),
|
||||
'value' => $rcmail->gettext($options['namespace'] . 'folder')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Allow plugins to modify folder form content
|
||||
$plugin = $rcmail->plugins->exec_hook('folder_form', [
|
||||
'form' => $form,
|
||||
'options' => $options,
|
||||
'name' => $mbox,
|
||||
'parent_name' => $parent
|
||||
]);
|
||||
|
||||
$form = $plugin['form'];
|
||||
|
||||
// Set form tags and hidden fields
|
||||
list($form_start, $form_end) = self::get_form_tags($attrib, 'save-folder', null, $hidden_fields);
|
||||
|
||||
unset($attrib['form'], $attrib['id']);
|
||||
|
||||
// return the complete edit form as table
|
||||
$out = "$form_start\n";
|
||||
|
||||
// Create form output
|
||||
foreach ($form as $idx => $tab) {
|
||||
if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) {
|
||||
$content = '';
|
||||
foreach ($tab['fieldsets'] as $fieldset) {
|
||||
$subcontent = self::get_form_part($fieldset, $attrib);
|
||||
if ($subcontent) {
|
||||
$subcontent = html::tag('legend', null, rcube::Q($fieldset['name'])) . $subcontent;
|
||||
$content .= html::tag('fieldset', null, $subcontent) ."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$content = self::get_form_part($tab, $attrib);
|
||||
}
|
||||
|
||||
if ($idx != 'props') {
|
||||
$out .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) ."\n";
|
||||
}
|
||||
else {
|
||||
$out .= $content ."\n";
|
||||
}
|
||||
}
|
||||
|
||||
$out .= "\n$form_end";
|
||||
|
||||
$rcmail->output->set_env('messagecount', $msgcount);
|
||||
$rcmail->output->set_env('folder', $mbox);
|
||||
|
||||
if ($mbox !== null && empty($_POST)) {
|
||||
$rcmail->output->command('parent.set_quota', self::quota_content(null, $mbox));
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function get_form_part($form, $attrib = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$content = '';
|
||||
|
||||
if (!empty($form['content']) && is_array($form['content'])) {
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
foreach ($form['content'] as $col => $colprop) {
|
||||
$colprop['id'] = '_' . $col;
|
||||
$label = !empty($colprop['label']) ? $colprop['label'] : $rcmail->gettext($col);
|
||||
|
||||
$table->add('title', html::label($colprop['id'], rcube::Q($label)));
|
||||
$table->add(null, $colprop['value']);
|
||||
}
|
||||
|
||||
$content = $table->show($attrib);
|
||||
}
|
||||
else if (isset($form['content'])) {
|
||||
$content = $form['content'];
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 of folder purge |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_purge 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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$trash_mbox = $rcmail->config->get('trash_mbox');
|
||||
$trash_regexp = '/^' . preg_quote($trash_mbox . $delimiter, '/') . '/';
|
||||
|
||||
// we should only be purging trash (or their subfolders)
|
||||
if (!strlen($trash_mbox) || $mbox === $trash_mbox || preg_match($trash_regexp, $mbox)) {
|
||||
$success = $storage->delete_message('*', $mbox);
|
||||
$delete = true;
|
||||
}
|
||||
// move to Trash
|
||||
else {
|
||||
$success = $storage->move_message('1:*', $trash_mbox, $mbox);
|
||||
$delete = false;
|
||||
}
|
||||
|
||||
if (!empty($success)) {
|
||||
$rcmail->output->set_env('messagecount', 0);
|
||||
|
||||
if ($delete) {
|
||||
$rcmail->output->show_message('folderpurged', 'confirmation');
|
||||
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message('messagemoved', 'confirmation');
|
||||
}
|
||||
|
||||
$_SESSION['unseen_count'][$mbox] = 0;
|
||||
$rcmail->output->command('show_folder', $mbox, null, true);
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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 of folder rename |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_rename extends rcmail_action_settings_folders
|
||||
{
|
||||
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();
|
||||
$name = trim(rcube_utils::get_input_string('_folder_newname', rcube_utils::INPUT_POST, true));
|
||||
$oldname = rcube_utils::get_input_string('_folder_oldname', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (strlen($name) && strlen($oldname)) {
|
||||
$rename = self::rename_folder($oldname, $name);
|
||||
}
|
||||
|
||||
if (!empty($rename)) {
|
||||
self::update_folder_row($name, $oldname);
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
public static function rename_folder($oldname, $newname)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('folder_rename', [
|
||||
'oldname' => $oldname, 'newname' => $newname]);
|
||||
|
||||
if (empty($plugin['abort'])) {
|
||||
$renamed = $storage->rename_folder($oldname, $newname);
|
||||
}
|
||||
else {
|
||||
$renamed = $plugin['result'];
|
||||
}
|
||||
|
||||
// update per-folder options for modified folder and its subfolders
|
||||
if ($renamed) {
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$a_threaded = (array) $rcmail->config->get('message_threading', []);
|
||||
$oldprefix = '/^' . preg_quote($oldname . $delimiter, '/') . '/';
|
||||
|
||||
foreach ($a_threaded as $key => $val) {
|
||||
if ($key == $oldname) {
|
||||
unset($a_threaded[$key]);
|
||||
$a_threaded[$newname] = $val;
|
||||
}
|
||||
else if (preg_match($oldprefix, $key)) {
|
||||
unset($a_threaded[$key]);
|
||||
$a_threaded[preg_replace($oldprefix, $newname . $delimiter, $key)] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
|
||||
|
||||
// #1488692: update session
|
||||
if (isset($_SESSION['mbox']) && $_SESSION['mbox'] === $oldname) {
|
||||
$_SESSION['mbox'] = $newname;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?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: |
|
||||
| Handler for saving the create/edit folder form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_save extends rcmail_action_settings_folder_edit
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
// WARNING: folder names in UI are encoded with RCUBE_CHARSET
|
||||
|
||||
$name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true));
|
||||
$path = rcube_utils::get_input_string('_parent', rcube_utils::INPUT_POST, true);
|
||||
$old_imap = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
$type = rcube_utils::get_input_string('_type', rcube_utils::INPUT_POST);
|
||||
$name_imap = rcube_charset::convert($name, RCUBE_CHARSET, 'UTF7-IMAP');
|
||||
// $path is in UTF7-IMAP already
|
||||
|
||||
// init IMAP connection
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$options = strlen($old_imap) ? self::folder_options($old_imap) : [];
|
||||
$char = null;
|
||||
|
||||
// Folder name checks
|
||||
if (!empty($options['protected']) || !empty($options['norename'])) {
|
||||
// do nothing
|
||||
}
|
||||
else if (!strlen($name)) {
|
||||
$error = $rcmail->gettext('namecannotbeempty');
|
||||
}
|
||||
else if (mb_strlen($name) > 128) {
|
||||
$error = $rcmail->gettext('nametoolong');
|
||||
}
|
||||
else if ($name[0] == '.' && $rcmail->config->get('imap_skip_hidden_folders')) {
|
||||
$error = $rcmail->gettext('namedotforbidden');
|
||||
}
|
||||
else if (!$storage->folder_validate($name, $char)) {
|
||||
$error = $rcmail->gettext('forbiddencharacter') . " ($char)";
|
||||
}
|
||||
|
||||
if (!empty($error)) {
|
||||
$rcmail->output->command('display_message', $error, 'error');
|
||||
}
|
||||
else {
|
||||
if (!empty($options['protected']) || !empty($options['norename'])) {
|
||||
$name_imap = $old_imap;
|
||||
}
|
||||
else if (strlen($path)) {
|
||||
$name_imap = $path . $delimiter . $name_imap;
|
||||
}
|
||||
else {
|
||||
$name_imap = $storage->mod_folder($name_imap, 'in');
|
||||
}
|
||||
}
|
||||
|
||||
$dual_use_supported = $storage->get_capability(rcube_storage::DUAL_USE_FOLDERS);
|
||||
$acl_supported = $storage->get_capability('ACL');
|
||||
|
||||
// Check access rights to the parent folder
|
||||
if (empty($error) && $acl_supported && strlen($path) && (!strlen($old_imap) || $old_imap != $name_imap)) {
|
||||
$parent_opts = $storage->folder_info($path);
|
||||
if ($parent_opts['namespace'] != 'personal'
|
||||
&& (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights'])))
|
||||
) {
|
||||
$error = $rcmail->gettext('parentnotwritable');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($error)) {
|
||||
$rcmail->output->command('display_message', $error, 'error');
|
||||
$folder = null;
|
||||
}
|
||||
else {
|
||||
$folder = [
|
||||
'name' => $name_imap,
|
||||
'oldname' => $old_imap,
|
||||
'class' => '',
|
||||
'options' => $options,
|
||||
'settings' => [
|
||||
// List view mode: 0-list, 1-threads
|
||||
'view_mode' => (int) rcube_utils::get_input_string('_viewmode', rcube_utils::INPUT_POST),
|
||||
'sort_column' => rcube_utils::get_input_string('_sortcol', rcube_utils::INPUT_POST),
|
||||
'sort_order' => rcube_utils::get_input_string('_sortord', rcube_utils::INPUT_POST),
|
||||
],
|
||||
'subscribe' => false,
|
||||
'noselect' => false,
|
||||
];
|
||||
}
|
||||
|
||||
// create a new mailbox
|
||||
if (empty($error) && !strlen($old_imap)) {
|
||||
$folder['subscribe'] = true;
|
||||
|
||||
// Server does not support both sub-folders and messages in a folder
|
||||
// For folders that are supposed to contain other folders we will:
|
||||
// - disable subscription
|
||||
// - add a separator at the end to make them \NoSelect
|
||||
if (!$dual_use_supported && $type == 'folder') {
|
||||
$folder['subscribe'] = false;
|
||||
$folder['noselect'] = true;
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('folder_create', ['record' => $folder]);
|
||||
|
||||
$folder = $plugin['record'];
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
$created = $storage->create_folder($folder['name'], $folder['subscribe'], null, $folder['noselect']);
|
||||
}
|
||||
else {
|
||||
$created = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($created) {
|
||||
// Save folder settings
|
||||
if (isset($_POST['_viewmode'])) {
|
||||
$a_threaded = (array) $rcmail->config->get('message_threading', []);
|
||||
|
||||
$a_threaded[$folder['name']] = (bool) $_POST['_viewmode'];
|
||||
|
||||
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
|
||||
}
|
||||
|
||||
self::update_folder_row($folder['name'], null, $folder['subscribe'], $folder['class']);
|
||||
|
||||
$rcmail->output->show_message('foldercreated', 'confirmation');
|
||||
// reset folder preview frame
|
||||
$rcmail->output->command('subscription_select');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
else {
|
||||
// show error message
|
||||
if (!empty($plugin['message'])) {
|
||||
$rcmail->output->show_message($plugin['message'], 'error', null, false);
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
}
|
||||
}
|
||||
// update a mailbox
|
||||
else if (empty($error)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('folder_update', ['record' => $folder]);
|
||||
|
||||
$folder = $plugin['record'];
|
||||
$rename = $folder['oldname'] != $folder['name'];
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
if ($rename) {
|
||||
$updated = $storage->rename_folder($folder['oldname'], $folder['name']);
|
||||
}
|
||||
else {
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$updated = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
// Update folder settings,
|
||||
if (isset($_POST['_viewmode'])) {
|
||||
$a_threaded = (array) $rcmail->config->get('message_threading', []);
|
||||
|
||||
// In case of name change update names of children in settings
|
||||
if ($rename) {
|
||||
$oldprefix = '/^' . preg_quote($folder['oldname'] . $delimiter, '/') . '/';
|
||||
foreach ($a_threaded as $key => $val) {
|
||||
if ($key == $folder['oldname']) {
|
||||
unset($a_threaded[$key]);
|
||||
}
|
||||
else if (preg_match($oldprefix, $key)) {
|
||||
unset($a_threaded[$key]);
|
||||
$a_threaded[preg_replace($oldprefix, $folder['name'].$delimiter, $key)] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$a_threaded[$folder['name']] = (bool) $_POST['_viewmode'];
|
||||
|
||||
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('folderupdated', 'confirmation');
|
||||
$rcmail->output->set_env('folder', $folder['name']);
|
||||
|
||||
if ($rename) {
|
||||
// #1488692: update session
|
||||
if (isset($_SESSION['mbox']) && $_SESSION['mbox'] === $folder['oldname']) {
|
||||
$_SESSION['mbox'] = $folder['name'];
|
||||
}
|
||||
self::update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']);
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
else if (!empty($folder['class'])) {
|
||||
self::update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// show error message
|
||||
if (!empty($plugin['message'])) {
|
||||
$rcmail->output->show_message($plugin['message'], 'error', null, false);
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->overwrite_action('edit-folder');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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 of getting folder size |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_size 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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$name = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
$size = $storage->folder_size($name);
|
||||
|
||||
// @TODO: check quota and show percentage usage of specified mailbox?
|
||||
|
||||
if ($size !== false) {
|
||||
$rcmail->output->command('folder_size_update', self::show_bytes($size));
|
||||
}
|
||||
else {
|
||||
self::display_server_error();
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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: |
|
||||
| Handler for folder subscribe action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_subscribe 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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (strlen($mbox)) {
|
||||
$result = $storage->subscribe([$mbox]);
|
||||
|
||||
// Handle virtual (non-existing) folders
|
||||
if (
|
||||
!$result
|
||||
&& $storage->get_error_code() == -1
|
||||
&& $storage->get_response_code() == rcube_storage::TRYCREATE
|
||||
) {
|
||||
$result = $storage->create_folder($mbox, true);
|
||||
if ($result) {
|
||||
// @TODO: remove 'virtual' class of folder's row
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($result)) {
|
||||
// Handle subscription of protected folder (#1487656)
|
||||
if ($rcmail->config->get('protect_default_folders') && $storage->is_special_folder($mbox)) {
|
||||
$rcmail->output->command('disable_subscription', $mbox);
|
||||
}
|
||||
|
||||
$rcmail->output->show_message('foldersubscribed', 'confirmation');
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
$rcmail->output->command('reset_subscription', $mbox, false);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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: |
|
||||
| Handler for folder unsubscribe action |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folder_unsubscribe 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();
|
||||
$storage = $rcmail->get_storage();
|
||||
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
|
||||
|
||||
if (strlen($mbox)) {
|
||||
$result = $storage->unsubscribe([$mbox]);
|
||||
}
|
||||
|
||||
if (!empty($result)) {
|
||||
$rcmail->output->show_message('folderunsubscribed', 'confirmation');
|
||||
}
|
||||
else {
|
||||
self::display_server_error('errorsaving');
|
||||
$rcmail->output->command('reset_subscription', $mbox, true);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?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 of folders listing |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_folders extends rcmail_action_settings_index
|
||||
{
|
||||
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();
|
||||
$storage = $rcmail->get_storage();
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext('folders'));
|
||||
$rcmail->output->set_env('prefix_ns', $storage->get_namespace('prefix'));
|
||||
$rcmail->output->set_env('quota', (bool) $storage->get_capability('QUOTA'));
|
||||
$rcmail->output->include_script('treelist.js');
|
||||
|
||||
// add some labels to client
|
||||
$rcmail->output->add_label('deletefolderconfirm', 'purgefolderconfirm', 'movefolderconfirm',
|
||||
'folderdeleting', 'foldermoving', 'foldersubscribing', 'folderunsubscribing',
|
||||
'move', 'quota');
|
||||
|
||||
// register UI objects
|
||||
$rcmail->output->add_handlers([
|
||||
'foldersubscription' => [$this, 'folder_subscriptions'],
|
||||
'folderfilter' => [$this, 'folder_filter'],
|
||||
'quotadisplay' => [$rcmail, 'quota_display'],
|
||||
'searchform' => [$rcmail->output, 'search_form'],
|
||||
]);
|
||||
|
||||
$rcmail->output->send('folders');
|
||||
}
|
||||
|
||||
// build table with all folders listed by server
|
||||
public static function folder_subscriptions($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmSubscriptionlist';
|
||||
}
|
||||
|
||||
// get folders from server
|
||||
$storage->clear_cache('mailboxes', true);
|
||||
|
||||
$a_unsubscribed = $storage->list_folders();
|
||||
$a_subscribed = $storage->list_folders_subscribed('', '*', null, null, true); // unsorted
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$namespace = $storage->get_namespace();
|
||||
$special_folders = array_flip(array_merge(['inbox' => 'INBOX'], $storage->get_special_folders()));
|
||||
$protect_default = $rcmail->config->get('protect_default_folders');
|
||||
$seen = [];
|
||||
$list_folders = [];
|
||||
|
||||
// pre-process folders list
|
||||
foreach ($a_unsubscribed as $i => $folder) {
|
||||
$folder_id = $folder;
|
||||
$folder = $storage->mod_folder($folder);
|
||||
$foldersplit = explode($delimiter, $folder);
|
||||
$name = rcube_charset::convert(array_pop($foldersplit), 'UTF7-IMAP');
|
||||
$is_special = isset($special_folders[$folder_id]);
|
||||
$parent_folder = $is_special ? '' : join($delimiter, $foldersplit);
|
||||
$level = $is_special ? 0 : count($foldersplit);
|
||||
|
||||
// add any necessary "virtual" parent folders
|
||||
if ($parent_folder && empty($seen[$parent_folder])) {
|
||||
for ($i = 1; $i <= $level; $i++) {
|
||||
$ancestor_folder = join($delimiter, array_slice($foldersplit, 0, $i));
|
||||
if ($ancestor_folder) {
|
||||
if (empty($seen[$ancestor_folder])) {
|
||||
$seen[$ancestor_folder] = true;
|
||||
$ancestor_name = rcube_charset::convert($foldersplit[$i-1], 'UTF7-IMAP');
|
||||
$list_folders[] = [
|
||||
'id' => $ancestor_folder,
|
||||
'name' => $ancestor_name,
|
||||
'level' => $i-1,
|
||||
'virtual' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle properly INBOX.INBOX situation
|
||||
if (isset($seen[$folder])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[$folder] = true;
|
||||
|
||||
$list_folders[] = [
|
||||
'id' => $folder_id,
|
||||
'name' => $name,
|
||||
'level' => $level,
|
||||
];
|
||||
}
|
||||
|
||||
unset($seen);
|
||||
|
||||
$checkbox_subscribe = new html_checkbox([
|
||||
'name' => '_subscribed[]',
|
||||
'title' => $rcmail->gettext('changesubscription'),
|
||||
'onclick' => rcmail_output::JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)",
|
||||
]);
|
||||
|
||||
$js_folders = [];
|
||||
$folders = [];
|
||||
$collapsed = (string) $rcmail->config->get('collapsed_folders');
|
||||
|
||||
// create list of available folders
|
||||
foreach ($list_folders as $i => $folder) {
|
||||
$sub_key = array_search($folder['id'], $a_subscribed);
|
||||
$is_subscribed = $sub_key !== false;
|
||||
$is_special = isset($special_folders[$folder['id']]);
|
||||
$is_protected = $folder['id'] == 'INBOX' || ($protect_default && $is_special);
|
||||
$noselect = false;
|
||||
$classes = [];
|
||||
|
||||
$folder_utf8 = rcube_charset::convert($folder['id'], 'UTF7-IMAP');
|
||||
$display_folder = rcube::Q($is_special ? self::localize_foldername($folder['id'], false, true) : $folder['name']);
|
||||
|
||||
if (!empty($folder['virtual'])) {
|
||||
$classes[] = 'virtual';
|
||||
}
|
||||
|
||||
// Check \Noselect flag (of existing folder)
|
||||
if (!$is_protected && in_array($folder['id'], $a_unsubscribed)) {
|
||||
$attrs = $storage->folder_attributes($folder['id']);
|
||||
$noselect = in_array_nocase('\\Noselect', $attrs);
|
||||
}
|
||||
|
||||
$is_disabled = (($is_protected && $is_subscribed) || $noselect);
|
||||
|
||||
// Below we will disable subscription option for "virtual" folders
|
||||
// according to namespaces, but only if they aren't already subscribed.
|
||||
// User should be able to unsubscribe from the folder
|
||||
// even if it doesn't exists or is not accessible (OTRS:1000059)
|
||||
if (!$is_subscribed && !$is_disabled && !empty($namespace) && !empty($folder['virtual'])) {
|
||||
// check if the folder is a namespace prefix, then disable subscription option on it
|
||||
if (!$is_disabled && $folder['level'] == 0) {
|
||||
$fname = $folder['id'] . $delimiter;
|
||||
foreach ($namespace as $ns) {
|
||||
if (is_array($ns)) {
|
||||
foreach ($ns as $item) {
|
||||
if ($item[0] === $fname) {
|
||||
$is_disabled = true;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if the folder is an other users virtual-root folder, then disable subscription option on it
|
||||
if (!$is_disabled && $folder['level'] == 1 && !empty($namespace['other'])) {
|
||||
$parts = explode($delimiter, $folder['id']);
|
||||
$fname = $parts[0] . $delimiter;
|
||||
foreach ($namespace['other'] as $item) {
|
||||
if ($item[0] === $fname) {
|
||||
$is_disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check if the folder is shared, then disable subscription option on it (if not subscribed already)
|
||||
if (!$is_disabled) {
|
||||
$tmp_ns = array_merge((array)$namespace['other'], (array)$namespace['shared']);
|
||||
foreach ($tmp_ns as $item) {
|
||||
if (strlen($item[0]) && strpos($folder['id'], $item[0]) === 0) {
|
||||
$is_disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$is_collapsed = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
|
||||
$folder_id = rcube_utils::html_identifier($folder['id'], true);
|
||||
|
||||
if ($folder_class = self::folder_classname($folder['id'])) {
|
||||
$classes[] = $folder_class;
|
||||
}
|
||||
|
||||
$folders[$folder['id']] = [
|
||||
'idx' => $folder_id,
|
||||
'folder_imap' => $folder['id'],
|
||||
'folder' => $folder_utf8,
|
||||
'display' => $display_folder,
|
||||
'protected' => $is_protected || !empty($folder['virtual']),
|
||||
'class' => join(' ', $classes),
|
||||
'subscribed' => $is_subscribed,
|
||||
'level' => $folder['level'],
|
||||
'collapsed' => $is_collapsed,
|
||||
'content' => html::a(['href' => '#'], $display_folder)
|
||||
. $checkbox_subscribe->show(($is_subscribed ? $folder['id'] : ''),
|
||||
['value' => $folder['id'], 'disabled' => $is_disabled ? 'disabled' : ''])
|
||||
];
|
||||
}
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('folders_list', ['list' => $folders]);
|
||||
|
||||
// add drop-target representing 'root'
|
||||
$root = [
|
||||
'idx' => rcube_utils::html_identifier('*', true),
|
||||
'folder_imap' => '*',
|
||||
'folder' => '',
|
||||
'display' => '',
|
||||
'protected' => true,
|
||||
'class' => 'root',
|
||||
'content' => '<span> </span>',
|
||||
];
|
||||
|
||||
$folders = [];
|
||||
$plugin['list'] = array_values($plugin['list']);
|
||||
|
||||
array_unshift($plugin['list'], $root);
|
||||
|
||||
for ($i = 0, $length = count($plugin['list']); $i<$length; $i++) {
|
||||
$folders[] = self::folder_tree_element($plugin['list'], $i, $js_folders);
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('subscriptionlist', $attrib['id']);
|
||||
$rcmail->output->set_env('subscriptionrows', $js_folders);
|
||||
$rcmail->output->set_env('defaultfolders', array_keys($special_folders));
|
||||
$rcmail->output->set_env('collapsed_folders', $collapsed);
|
||||
$rcmail->output->set_env('delimiter', $delimiter);
|
||||
|
||||
return html::tag('ul', $attrib, implode('', $folders), html::$common_attrib);
|
||||
}
|
||||
|
||||
public static function folder_tree_element($folders, &$key, &$js_folders)
|
||||
{
|
||||
$data = $folders[$key];
|
||||
$idx = 'rcmli' . $data['idx'];
|
||||
|
||||
$js_folders[$data['folder_imap']] = [$data['folder'], $data['display'], $data['protected']];
|
||||
$content = $data['content'];
|
||||
$attribs = [
|
||||
'id' => $idx,
|
||||
'class' => trim($data['class'] . ' mailbox')
|
||||
];
|
||||
|
||||
if (!isset($data['level'])) {
|
||||
$data['level'] = 0;
|
||||
}
|
||||
|
||||
$children = [];
|
||||
while (!empty($folders[$key+1]) && ($folders[$key+1]['level'] > $data['level'])) {
|
||||
$key++;
|
||||
$children[] = self::folder_tree_element($folders, $key, $js_folders);
|
||||
}
|
||||
|
||||
if (!empty($children)) {
|
||||
$content .= html::div('treetoggle ' . (!empty($data['collapsed']) ? 'collapsed' : 'expanded'), ' ')
|
||||
. html::tag('ul', ['style' => !empty($data['collapsed']) ? "display:none" : null],
|
||||
implode("\n", $children));
|
||||
}
|
||||
|
||||
return html::tag('li', $attribs, $content);
|
||||
}
|
||||
|
||||
public static function folder_filter($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
$namespace = $storage->get_namespace();
|
||||
|
||||
if (empty($namespace['personal']) && empty($namespace['shared']) && empty($namespace['other'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmfolderfilter';
|
||||
}
|
||||
|
||||
if (!self::get_bool_attr($attrib, 'noevent')) {
|
||||
$attrib['onchange'] = rcmail_output::JS_OBJECT_NAME . '.folder_filter(this.value)';
|
||||
}
|
||||
|
||||
$roots = [];
|
||||
$select = new html_select($attrib);
|
||||
$select->add($rcmail->gettext('all'), '---');
|
||||
|
||||
foreach (array_keys($namespace) as $type) {
|
||||
foreach ((array)$namespace[$type] as $ns) {
|
||||
$root = rtrim($ns[0], $ns[1]);
|
||||
$label = $rcmail->gettext('namespace.' . $type);
|
||||
|
||||
if (count($namespace[$type]) > 1) {
|
||||
$label .= ' (' . rcube_charset::convert($root, 'UTF7-IMAP', RCUBE_CHARSET) . ')';
|
||||
}
|
||||
|
||||
$select->add($label, $root);
|
||||
|
||||
if (strlen($root)) {
|
||||
$roots[] = $root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->add_gui_object('foldersfilter', $attrib['id']);
|
||||
$rcmail->output->set_env('ns_roots', $roots);
|
||||
|
||||
return $select->show();
|
||||
}
|
||||
|
||||
public static function folder_options($mailbox)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$options = $rcmail->get_storage()->folder_info($mailbox);
|
||||
$options['protected'] = !empty($options['is_root'])
|
||||
|| strtoupper($mailbox) === 'INBOX'
|
||||
|| (!empty($options['special']) && $rcmail->config->get('protect_default_folders'));
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates (or creates) folder row in the subscriptions table
|
||||
*
|
||||
* @param string $name Folder name
|
||||
* @param string $oldname Old folder name (for update)
|
||||
* @param bool $subscribe Checks subscription checkbox
|
||||
* @param string $class CSS class name for folder row
|
||||
*/
|
||||
public static function update_folder_row($name, $oldname = null, $subscribe = false, $class_name = null)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
$delimiter = $storage->get_hierarchy_delimiter();
|
||||
$options = self::folder_options($name);
|
||||
$name_utf8 = rcube_charset::convert($name, 'UTF7-IMAP');
|
||||
$foldersplit = explode($delimiter, $storage->mod_folder($name));
|
||||
$level = count($foldersplit) - 1;
|
||||
$class_name = trim($class_name . ' mailbox');
|
||||
|
||||
if (!empty($options['protected'])) {
|
||||
$display_name = self::localize_foldername($name);
|
||||
}
|
||||
else {
|
||||
$display_name = rcube_charset::convert($foldersplit[$level], 'UTF7-IMAP');
|
||||
}
|
||||
|
||||
$protected = !empty($options['protected']) || !empty($options['noselect']);
|
||||
|
||||
if ($oldname === null) {
|
||||
$rcmail->output->command('add_folder_row', $name, $name_utf8, $display_name,
|
||||
$protected, $subscribe, $class_name);
|
||||
}
|
||||
else {
|
||||
$rcmail->output->command('replace_folder_row', $oldname, $name, $name_utf8, $display_name,
|
||||
$protected, $class_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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: |
|
||||
| Manage identities of a user account |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_identities 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();
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext('identities'));
|
||||
$rcmail->output->include_script('list.js');
|
||||
$rcmail->output->set_env('identities_level', (int) $rcmail->config->get('identities_level', 0));
|
||||
$rcmail->output->add_label('deleteidentityconfirm');
|
||||
$rcmail->output->add_handlers([
|
||||
'identitieslist' => [$this, 'identities_list'],
|
||||
]);
|
||||
|
||||
$rcmail->output->send('identities');
|
||||
}
|
||||
|
||||
public static function identities_list($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// add id to message list table if not specified
|
||||
if (empty($attrib['id'])) {
|
||||
$attrib['id'] = 'rcmIdentitiesList';
|
||||
}
|
||||
|
||||
// get identities list and define 'mail' column
|
||||
$list = $rcmail->user->list_emails();
|
||||
foreach ($list as $idx => $row) {
|
||||
$list[$idx]['mail'] = trim($row['name'] . ' <' . rcube_utils::idn_to_utf8($row['email']) . '>');
|
||||
}
|
||||
|
||||
// get all identities from DB and define list of cols to be displayed
|
||||
$plugin = $rcmail->plugins->exec_hook('identities_list', [
|
||||
'list' => $list,
|
||||
'cols' => ['mail']
|
||||
]);
|
||||
|
||||
// @TODO: use <UL> instead of <TABLE> for identities list
|
||||
$out = self::table_output($attrib, $plugin['list'], $plugin['cols'], 'identity_id');
|
||||
|
||||
// set client env
|
||||
$rcmail->output->add_gui_object('identitieslist', $attrib['id']);
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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 identity create form |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_identity_create extends rcmail_action_settings_identity_edit
|
||||
{
|
||||
}
|
||||
@@ -0,0 +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_string('_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 = !empty($plugin['message']) ? $plugin['message'] : ($deleted < 0 ? 'nodeletelastidentity' : 'errorsaving');
|
||||
$rcmail->output->show_message($msg, 'error', null, false);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?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 ($rcmail->action == 'edit-identity'
|
||||
&& ($id = rcube_utils::get_input_string('_iid', rcube_utils::INPUT_GPC))
|
||||
) {
|
||||
self::$record = $rcmail->user->get_identity($id);
|
||||
|
||||
if (!is_array(self::$record)) {
|
||||
$rcmail->output->show_message('dberror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
|
||||
$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->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', 'rcmfd_signature');
|
||||
|
||||
// add some labels to client
|
||||
$rcmail->output->add_label('noemailwarning', 'converting', 'editorwarning');
|
||||
|
||||
$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 (!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';
|
||||
}
|
||||
}
|
||||
|
||||
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'] ?? 0),
|
||||
['name' => '_iid', 'value' => 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;
|
||||
|
||||
if (!empty($colprop['label'])) {
|
||||
$label = $colprop['label'];
|
||||
}
|
||||
else {
|
||||
$label = $rcmail->gettext(str_replace('-', '', $col));
|
||||
}
|
||||
|
||||
if (!empty($colprop['value'])) {
|
||||
$value = $colprop['value'];
|
||||
}
|
||||
else {
|
||||
$val = 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', !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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?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_settings_index
|
||||
{
|
||||
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_bool_cols = ['standard', 'html_signature'];
|
||||
$updated = 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_string($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_bool_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 = !empty($save_data['email']) ? [rcube_utils::idn_to_ascii($save_data['email'])] : [];
|
||||
foreach (['reply-to', 'bcc'] as $item) {
|
||||
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'], 'identity');
|
||||
|
||||
// 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_string('_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
|
||||
$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 (!empty($default_id)) {
|
||||
$rcmail->user->set_default($default_id);
|
||||
}
|
||||
|
||||
// go to next step
|
||||
$rcmail->overwrite_action('edit-identity');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
<?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_prefs_edit extends rcmail_action_settings_index
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
protected static $section;
|
||||
protected static $sections;
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
|
||||
self::$section = rcube_utils::get_input_string('_section', rcube_utils::INPUT_GPC);
|
||||
list(self::$sections,) = self::user_prefs(self::$section);
|
||||
|
||||
// register UI objects
|
||||
$rcmail->output->add_handlers([
|
||||
'userprefs' => [$this, 'user_prefs_form'],
|
||||
'sectionname' => [$this, 'prefs_section_name'],
|
||||
]);
|
||||
|
||||
$rcmail->output->send('settingsedit');
|
||||
}
|
||||
|
||||
public static function user_prefs_form($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// add some labels to client
|
||||
$rcmail->output->add_label('nopagesizewarning', 'nosupporterror');
|
||||
|
||||
unset($attrib['form']);
|
||||
|
||||
$hidden = ['name' => '_section', 'value' => self::$section];
|
||||
list($form_start, $form_end) = self::get_form_tags($attrib, 'save-prefs', null, $hidden);
|
||||
|
||||
$out = $form_start;
|
||||
|
||||
if (!empty(self::$sections[self::$section]['header'])) {
|
||||
$div_attr = ['id' => 'preferences-header', 'class' =>'boxcontent'];
|
||||
$out .= html::div($div_attr, self::$sections[self::$section]['header']);
|
||||
}
|
||||
|
||||
foreach (self::$sections[self::$section]['blocks'] as $class => $block) {
|
||||
if (!empty($block['options'])) {
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
foreach ($block['options'] as $option) {
|
||||
if (isset($option['title'])) {
|
||||
$table->add('title', $option['title']);
|
||||
$table->add(null, $option['content']);
|
||||
}
|
||||
else {
|
||||
$table->add(['colspan' => 2], $option['content']);
|
||||
}
|
||||
}
|
||||
|
||||
$out .= html::tag('fieldset', $class, html::tag('legend', null, $block['name']) . $table->show($attrib));
|
||||
}
|
||||
else if (!empty($block['content'])) {
|
||||
$out .= html::tag('fieldset', null, html::tag('legend', null, $block['name']) . $block['content']);
|
||||
}
|
||||
}
|
||||
|
||||
return $out . $form_end;
|
||||
}
|
||||
|
||||
public static function prefs_section_name()
|
||||
{
|
||||
return self::$sections[self::$section]['section'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
<?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 user preferences to DB and to the current session |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_prefs_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();
|
||||
|
||||
$CURR_SECTION = rcube_utils::get_input_string('_section', rcube_utils::INPUT_POST);
|
||||
$dont_override = (array) $rcmail->config->get('dont_override');
|
||||
$a_user_prefs = [];
|
||||
|
||||
// set options for specified section
|
||||
switch ($CURR_SECTION) {
|
||||
case 'general':
|
||||
$a_user_prefs = [
|
||||
'language' => self::prefs_input('language', '/^[a-zA-Z0-9_-]+$/'),
|
||||
'timezone' => self::prefs_input('timezone', '/^[a-zA-Z_\/-]+$/'),
|
||||
'date_format' => self::prefs_input('date_format', '/^[a-zA-Z_.\/ -]+$/'),
|
||||
'time_format' => self::prefs_input('time_format', '/^[a-zA-Z0-9: ]+$/'),
|
||||
'prettydate' => isset($_POST['_pretty_date']),
|
||||
'display_next' => isset($_POST['_display_next']),
|
||||
'refresh_interval' => self::prefs_input_int('refresh_interval') * 60,
|
||||
'standard_windows' => isset($_POST['_standard_windows']),
|
||||
'skin' => self::prefs_input('skin', '/^[a-zA-Z0-9_.-]+$/'),
|
||||
];
|
||||
|
||||
// compose derived date/time format strings
|
||||
if (
|
||||
(isset($_POST['_date_format']) || isset($_POST['_time_format']))
|
||||
&& !empty($a_user_prefs['date_format'])
|
||||
&& !empty($a_user_prefs['time_format'])
|
||||
) {
|
||||
$a_user_prefs['date_short'] = 'D ' . $a_user_prefs['time_format'];
|
||||
$a_user_prefs['date_long'] = $a_user_prefs['date_format'] . ' ' . $a_user_prefs['time_format'];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'mailbox':
|
||||
$a_user_prefs = [
|
||||
'layout' => self::prefs_input('layout', '/^[a-z]+$/'),
|
||||
'mail_read_time' => self::prefs_input_int('mail_read_time'),
|
||||
'autoexpand_threads' => self::prefs_input_int('autoexpand_threads'),
|
||||
'check_all_folders' => isset($_POST['_check_all_folders']),
|
||||
'mail_pagesize' => max(2, self::prefs_input_int('mail_pagesize')),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'mailview':
|
||||
$a_user_prefs = [
|
||||
'message_extwin' => self::prefs_input_int('message_extwin'),
|
||||
'message_show_email' => isset($_POST['_message_show_email']),
|
||||
'prefer_html' => isset($_POST['_prefer_html']),
|
||||
'inline_images' => isset($_POST['_inline_images']),
|
||||
'show_images' => self::prefs_input_int('show_images'),
|
||||
'mdn_requests' => self::prefs_input_int('mdn_requests'),
|
||||
'default_charset' => self::prefs_input('default_charset', '/^[a-zA-Z0-9-]+$/'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'compose':
|
||||
$a_user_prefs = [
|
||||
'compose_extwin' => self::prefs_input_int('compose_extwin'),
|
||||
'htmleditor' => self::prefs_input_int('htmleditor'),
|
||||
'draft_autosave' => self::prefs_input_int('draft_autosave'),
|
||||
'mime_param_folding' => self::prefs_input_int('mime_param_folding'),
|
||||
'force_7bit' => isset($_POST['_force_7bit']),
|
||||
'mdn_default' => isset($_POST['_mdn_default']),
|
||||
'dsn_default' => isset($_POST['_dsn_default']),
|
||||
'reply_same_folder' => isset($_POST['_reply_same_folder']),
|
||||
'spellcheck_before_send' => isset($_POST['_spellcheck_before_send']),
|
||||
'spellcheck_ignore_syms' => isset($_POST['_spellcheck_ignore_syms']),
|
||||
'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']),
|
||||
'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']),
|
||||
'show_sig' => self::prefs_input_int('show_sig'),
|
||||
'reply_mode' => self::prefs_input_int('reply_mode'),
|
||||
'sig_below' => isset($_POST['_sig_below']),
|
||||
'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
|
||||
'sig_separator' => isset($_POST['_sig_separator']),
|
||||
'default_font' => self::prefs_input('default_font', '/^[a-zA-Z ]+$/'),
|
||||
'default_font_size' => self::prefs_input('default_font_size', '/^[0-9]+pt$/'),
|
||||
'reply_all_mode' => self::prefs_input_int('reply_all_mode'),
|
||||
'forward_attachment' => !empty($_POST['_forward_attachment']),
|
||||
'compose_save_localstorage' => self::prefs_input_int('compose_save_localstorage'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'addressbook':
|
||||
$a_user_prefs = [
|
||||
'default_addressbook' => rcube_utils::get_input_string('_default_addressbook', rcube_utils::INPUT_POST, true),
|
||||
'collected_recipients' => rcube_utils::get_input_string('_collected_recipients', rcube_utils::INPUT_POST, true),
|
||||
'collected_senders' => rcube_utils::get_input_string('_collected_senders', rcube_utils::INPUT_POST, true),
|
||||
'autocomplete_single' => isset($_POST['_autocomplete_single']),
|
||||
'addressbook_sort_col' => self::prefs_input('addressbook_sort_col', '/^[a-z_]+$/'),
|
||||
'addressbook_name_listing' => self::prefs_input_int('addressbook_name_listing'),
|
||||
'addressbook_pagesize' => max(2, self::prefs_input_int('addressbook_pagesize')),
|
||||
'contact_form_mode' => self::prefs_input('contact_form_mode', '/^(private|business)$/'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'server':
|
||||
$a_user_prefs = [
|
||||
'read_when_deleted' => isset($_POST['_read_when_deleted']),
|
||||
'skip_deleted' => isset($_POST['_skip_deleted']),
|
||||
'flag_for_deletion' => isset($_POST['_flag_for_deletion']),
|
||||
'delete_junk' => isset($_POST['_delete_junk']),
|
||||
'logout_purge' => self::prefs_input('logout_purge', '/^(all|never|30|60|90)$/'),
|
||||
'logout_expunge' => isset($_POST['_logout_expunge']),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'folders':
|
||||
$a_user_prefs = [
|
||||
'show_real_foldernames' => isset($_POST['_show_real_foldernames']),
|
||||
// stop using SPECIAL-USE (#4782)
|
||||
'lock_special_folders' => !in_array('lock_special_folders', $dont_override),
|
||||
];
|
||||
|
||||
foreach (rcube_storage::$folder_types as $type) {
|
||||
$a_user_prefs[$type . '_mbox'] = rcube_utils::get_input_string('_' . $type . '_mbox', rcube_utils::INPUT_POST, true);
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case 'encryption':
|
||||
$a_user_prefs = [
|
||||
'mailvelope_main_keyring' => isset($_POST['_mailvelope_main_keyring']),
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$plugin = rcmail::get_instance()->plugins->exec_hook('preferences_save',
|
||||
['prefs' => $a_user_prefs, 'section' => $CURR_SECTION]);
|
||||
|
||||
$a_user_prefs = $plugin['prefs'];
|
||||
|
||||
// don't override these parameters
|
||||
foreach ($dont_override as $p) {
|
||||
$a_user_prefs[$p] = $rcmail->config->get($p);
|
||||
}
|
||||
|
||||
// verify some options
|
||||
switch ($CURR_SECTION) {
|
||||
case 'general':
|
||||
// switch UI language
|
||||
if (isset($_POST['_language']) && $a_user_prefs['language'] != $_SESSION['language']) {
|
||||
$rcmail->load_language($a_user_prefs['language']);
|
||||
$rcmail->output->command('reload', 500);
|
||||
}
|
||||
|
||||
// switch skin (if valid, otherwise unset the pref and fall back to default)
|
||||
if (!empty($a_user_prefs['skin'])) {
|
||||
if (!$rcmail->output->check_skin($a_user_prefs['skin'])) {
|
||||
unset($a_user_prefs['skin']);
|
||||
}
|
||||
else if ($rcmail->config->get('skin') != $a_user_prefs['skin']) {
|
||||
$rcmail->output->command('reload', 500);
|
||||
}
|
||||
}
|
||||
|
||||
$a_user_prefs['timezone'] = (string) $a_user_prefs['timezone'];
|
||||
|
||||
$min_refresh_interval = (int) $rcmail->config->get('min_refresh_interval');
|
||||
if (!empty($a_user_prefs['refresh_interval']) && $min_refresh_interval) {
|
||||
if ($a_user_prefs['refresh_interval'] < $min_refresh_interval) {
|
||||
$a_user_prefs['refresh_interval'] = $min_refresh_interval;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'mailbox':
|
||||
// force min size
|
||||
if ($a_user_prefs['mail_pagesize'] < 1) {
|
||||
$a_user_prefs['mail_pagesize'] = 10;
|
||||
}
|
||||
|
||||
$max_pagesize = (int) $rcmail->config->get('max_pagesize');
|
||||
if ($max_pagesize && ($a_user_prefs['mail_pagesize'] > $max_pagesize)) {
|
||||
$a_user_prefs['mail_pagesize'] = $max_pagesize;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'addressbook':
|
||||
// force min size
|
||||
if ($a_user_prefs['addressbook_pagesize'] < 1) {
|
||||
$a_user_prefs['addressbook_pagesize'] = 10;
|
||||
}
|
||||
|
||||
$max_pagesize = (int) $rcmail->config->get('max_pagesize');
|
||||
if ($max_pagesize && ($a_user_prefs['addressbook_pagesize'] > $max_pagesize)) {
|
||||
$a_user_prefs['addressbook_pagesize'] = $max_pagesize;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'folders':
|
||||
$storage = $rcmail->get_storage();
|
||||
$specials = [];
|
||||
|
||||
foreach (rcube_storage::$folder_types as $type) {
|
||||
$specials[$type] = $a_user_prefs[$type . '_mbox'];
|
||||
}
|
||||
|
||||
$storage->set_special_folders($specials);
|
||||
|
||||
break;
|
||||
|
||||
case 'server':
|
||||
if (isset($a_user_prefs['logout_purge']) && !is_numeric($a_user_prefs['logout_purge'])) {
|
||||
$a_user_prefs['logout_purge'] = $a_user_prefs['logout_purge'] !== 'never';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Save preferences
|
||||
if (empty($plugin['abort'])) {
|
||||
$saved = $rcmail->user->save_prefs($a_user_prefs);
|
||||
}
|
||||
else {
|
||||
$saved = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$rcmail->output->show_message('successfullysaved', 'confirmation');
|
||||
}
|
||||
else {
|
||||
$rcmail->output->show_message(!empty($plugin['message']) ? $plugin['message'] : 'errorsaving', 'error');
|
||||
}
|
||||
|
||||
// display the form again
|
||||
$rcmail->overwrite_action('edit-prefs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get option value from POST and validate with a regex
|
||||
*/
|
||||
public static function prefs_input($name, $regex)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$value = rcube_utils::get_input_value('_' . $name, rcube_utils::INPUT_POST);
|
||||
|
||||
if (!is_string($value)) {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
if ($value !== null && strlen($value) && !preg_match($regex, $value)) {
|
||||
$value = $rcmail->config->get($name);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get integer option value from POST
|
||||
*/
|
||||
public static function prefs_input_int($name)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$value = rcube_utils::get_input_value('_' . $name, rcube_utils::INPUT_POST);
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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 a canned response creation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_response_create extends rcmail_action_settings_response_edit
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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 canned response deletion |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_response_delete extends rcmail_action
|
||||
{
|
||||
static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
if ($id = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GP)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('response_delete', ['id' => $id]);
|
||||
|
||||
$deleted = !$plugin['abort'] ? $rcmail->user->delete_response($id) : $plugin['result'];
|
||||
|
||||
if (!empty($deleted)) {
|
||||
$rcmail->output->command('display_message', $rcmail->gettext('deletedsuccessfully'), 'confirmation');
|
||||
$rcmail->output->command('remove_response', $id);
|
||||
}
|
||||
else {
|
||||
$msg = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($msg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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 a canned response record |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_response_edit extends rcmail_action_settings_responses
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
protected static $response;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
$title = $rcmail->gettext($rcmail->action == 'add-response' ? 'addresponse' : 'editresponse');
|
||||
|
||||
if (!empty($args['post'])) {
|
||||
self::$response = $args['post'];
|
||||
}
|
||||
else if ($id = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GP)) {
|
||||
self::$response = $rcmail->get_compose_response($id);
|
||||
|
||||
if (!is_array(self::$response)) {
|
||||
$rcmail->output->show_message('dberror', 'error');
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->set_pagetitle($title);
|
||||
$rcmail->output->set_env('readonly', !empty(self::$response['static']));
|
||||
$rcmail->output->add_handler('responseform', [$this, 'response_form']);
|
||||
$rcmail->output->send('responseedit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content of a response editing/adding form
|
||||
*
|
||||
* @param array $attrib Template object attributes
|
||||
*
|
||||
* @return string HTML content
|
||||
*/
|
||||
public static function response_form($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// add some labels to client
|
||||
$rcmail->output->add_label('converting', 'editorwarning');
|
||||
|
||||
// Set form tags and hidden fields
|
||||
$readonly = !empty(self::$response['static']);
|
||||
$is_html = self::$response['is_html'] ?? false;
|
||||
$id = self::$response['id'] ?? '';
|
||||
$hidden = ['name' => '_id', 'value' => $id];
|
||||
|
||||
list($form_start, $form_end) = self::get_form_tags($attrib, 'save-response', $id, $hidden);
|
||||
unset($attrib['form'], $attrib['id']);
|
||||
|
||||
$name_attr = [
|
||||
'id' => 'ffname',
|
||||
'size' => $attrib['size'] ?? null,
|
||||
'readonly' => $readonly,
|
||||
'required' => true,
|
||||
];
|
||||
|
||||
$text_attr = [
|
||||
'id' => 'fftext',
|
||||
'size' => $attrib['textareacols'] ?? null,
|
||||
'rows' => $attrib['textarearows'] ?? null,
|
||||
'readonly' => $readonly,
|
||||
'spellcheck' => true,
|
||||
'data-html-editor' => true
|
||||
];
|
||||
|
||||
$chk_attr = [
|
||||
'id' => 'ffis_html',
|
||||
'disabled' => $readonly,
|
||||
'onclick' => "return rcmail.command('toggle-editor', {id: 'fftext', html: this.checked}, '', event)"
|
||||
];
|
||||
|
||||
// Add HTML editor script(s)
|
||||
self::html_editor('response', 'fftext');
|
||||
|
||||
// Enable TinyMCE editor
|
||||
if ($is_html) {
|
||||
$text_attr['class'] = 'mce_editor';
|
||||
$text_attr['is_escaped'] = true;
|
||||
|
||||
// Correctly handle HTML entities in HTML editor (#1488483)
|
||||
self::$response['data'] = htmlspecialchars(self::$response['data'], ENT_NOQUOTES, RCUBE_CHARSET);
|
||||
}
|
||||
|
||||
$table = new html_table(['cols' => 2]);
|
||||
|
||||
$table->add('title', html::label('ffname', rcube::Q($rcmail->gettext('responsename'))));
|
||||
$table->add(null, rcube_output::get_edit_field('name', self::$response['name'] ?? '', $name_attr, 'text'));
|
||||
|
||||
$table->add('title', html::label('fftext', rcube::Q($rcmail->gettext('responsetext'))));
|
||||
$table->add(null, rcube_output::get_edit_field('text', self::$response['data'] ?? '', $text_attr, 'textarea'));
|
||||
|
||||
$table->add('title', html::label('ffis_html', rcube::Q($rcmail->gettext('htmltoggle'))));
|
||||
$table->add(null, rcube_output::get_edit_field('is_html', $is_html, $chk_attr, 'checkbox'));
|
||||
|
||||
// return the complete edit form as table
|
||||
return "$form_start\n" . $table->show($attrib) . $form_end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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 fetching a canned response content |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_response_get extends rcmail_action
|
||||
{
|
||||
static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$id = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GET);
|
||||
|
||||
if ($id && ($response = $rcmail->get_compose_response($id))) {
|
||||
$is_html = (bool) rcube_utils::get_input_string('_is_html', rcube_utils::INPUT_GET);
|
||||
|
||||
if ($is_html && empty($response['is_html'])) {
|
||||
$converter = new rcube_text2html($response['data'], false, ['wrap' => true]);
|
||||
|
||||
$response['data'] = $converter->get_html();
|
||||
$response['is_html'] = true;
|
||||
}
|
||||
else if (!$is_html && !empty($response['is_html'])) {
|
||||
$params = [
|
||||
'width' => $rcmail->config->get('line_length', 72),
|
||||
'links' => false,
|
||||
];
|
||||
|
||||
$response['data'] = $rcmail->html2text($response['data'], $params);
|
||||
$response['is_html'] = false;
|
||||
}
|
||||
|
||||
$rcmail->output->command('insert_response', $response);
|
||||
}
|
||||
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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 saving a canned response record |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_response_save extends rcmail_action_settings_index
|
||||
{
|
||||
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();
|
||||
|
||||
$id = trim(rcube_utils::get_input_string('_id', rcube_utils::INPUT_POST));
|
||||
$name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST));
|
||||
$text = trim(rcube_utils::get_input_string('_text', rcube_utils::INPUT_POST, true));
|
||||
$is_html = (bool) rcube_utils::get_input_string('_is_html', rcube_utils::INPUT_POST);
|
||||
|
||||
$response = [
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'data' => $text,
|
||||
'is_html' => $is_html,
|
||||
];
|
||||
|
||||
if (!empty($text) && $is_html) {
|
||||
// replace uploaded images with data URIs
|
||||
$text = self::attach_images($text, 'response');
|
||||
// XSS protection in HTML signature (#1489251)
|
||||
$text = self::wash_html($text);
|
||||
|
||||
$response['data'] = $text;
|
||||
}
|
||||
|
||||
if (empty($name) || empty($text)) {
|
||||
// TODO: error
|
||||
$rcmail->output->show_message('formincomplete', 'error');
|
||||
$rcmail->overwrite_action('edit-response', ['post' => $response]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($id) && is_numeric($id)) {
|
||||
$plugin = $rcmail->plugins->exec_hook('response_update', ['id' => $id, 'record' => $response]);
|
||||
$response = $plugin['record'];
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
$updated = $rcmail->user->update_response($id, $response);
|
||||
}
|
||||
else {
|
||||
$updated = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$rcmail->output->show_message('successfullysaved', 'confirmation');
|
||||
$rcmail->output->command('parent.update_response_row', $id, rcube::Q($response['name']));
|
||||
}
|
||||
else {
|
||||
// show error message
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error', null, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$plugin = $rcmail->plugins->exec_hook('response_create', ['record' => $response]);
|
||||
$response = $plugin['record'];
|
||||
|
||||
if (!$plugin['abort']) {
|
||||
$insert_id = $rcmail->user->insert_response($response);
|
||||
}
|
||||
else {
|
||||
$insert_id = $plugin['result'];
|
||||
}
|
||||
|
||||
if ($insert_id) {
|
||||
$rcmail->output->show_message('successfullysaved', 'confirmation');
|
||||
|
||||
$response['id'] = $_GET['_id'] = $insert_id;
|
||||
|
||||
// add a new row to the list
|
||||
$rcmail->output->command('parent.update_response_row', $insert_id, rcube::Q($response['name']), true);
|
||||
}
|
||||
else {
|
||||
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
|
||||
$rcmail->output->show_message($error, 'error', null, false);
|
||||
$rcmail->overwrite_action('add-response');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// display the form again
|
||||
$rcmail->overwrite_action('edit-response', ['post' => $response]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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: |
|
||||
| Listing of canned responses, and quick insert action handler |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_responses extends rcmail_action_settings_index
|
||||
{
|
||||
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();
|
||||
|
||||
$rcmail->output->set_pagetitle($rcmail->gettext('responses'));
|
||||
$rcmail->output->include_script('list.js');
|
||||
$rcmail->output->add_label('deleteresponseconfirm');
|
||||
$rcmail->output->add_handlers(['responseslist' => [$this, 'responses_list']]);
|
||||
$rcmail->output->send('responses');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create template object 'responseslist'
|
||||
*
|
||||
* @param array $attrib Object attributes
|
||||
*
|
||||
* @return string HTML table output
|
||||
*/
|
||||
public static function responses_list($attrib)
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$attrib += ['id' => 'rcmresponseslist', 'tagname' => 'table'];
|
||||
|
||||
$plugin = $rcmail->plugins->exec_hook('responses_list', [
|
||||
'list' => $rcmail->get_compose_responses(),
|
||||
'cols' => ['name']
|
||||
]);
|
||||
|
||||
$out = self::table_output($attrib, $plugin['list'], $plugin['cols'], 'id');
|
||||
|
||||
$readonly_responses = [];
|
||||
foreach ($plugin['list'] as $item) {
|
||||
if (!empty($item['static'])) {
|
||||
$readonly_responses[] = $item['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// set client env
|
||||
$rcmail->output->add_gui_object('responseslist', $attrib['id']);
|
||||
$rcmail->output->set_env('readonly_responses', $readonly_responses);
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?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: |
|
||||
| Handles image uploads |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_upload 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();
|
||||
$from = rcube_utils::get_input_string('_from', rcube_utils::INPUT_GET);
|
||||
$type = preg_replace('/(add|edit)-/', '', $from);
|
||||
|
||||
// Plugins in Settings may use this file for some uploads (#5694)
|
||||
// Make sure it does not contain a dot, which is a special character
|
||||
// when using rcube_session::append() below
|
||||
$type = str_replace('.', '-', $type);
|
||||
|
||||
// Supported image format types
|
||||
$IMAGE_TYPES = explode(',', 'jpeg,jpg,jp2,tiff,tif,bmp,eps,gif,png,png8,png24,png32,svg,ico');
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$rcmail->output->reset();
|
||||
|
||||
$max_size = $rcmail->config->get($type . '_image_size', 64) * 1024;
|
||||
$uploadid = rcube_utils::get_input_string('_uploadid', rcube_utils::INPUT_GET);
|
||||
|
||||
if (!empty($_FILES['_file']['tmp_name']) && is_array($_FILES['_file']['tmp_name'])) {
|
||||
$multiple = count($_FILES['_file']['tmp_name']) > 1;
|
||||
|
||||
foreach ($_FILES['_file']['tmp_name'] as $i => $filepath) {
|
||||
$err = $_FILES['_file']['error'][$i];
|
||||
$imageprop = null;
|
||||
$attachment = null;
|
||||
|
||||
// Process uploaded attachment if there is no error
|
||||
if (!$err) {
|
||||
if ($max_size < $_FILES['_file']['size'][$i]) {
|
||||
$err = 'size_error';
|
||||
}
|
||||
// check image file type
|
||||
else {
|
||||
$image = new rcube_image($filepath);
|
||||
$imageprop = $image->props();
|
||||
|
||||
if (!in_array(strtolower($imageprop['type']), $IMAGE_TYPES)) {
|
||||
$err = 'type_error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save uploaded image in storage backend
|
||||
if (!empty($imageprop)) {
|
||||
$attachment = $rcmail->plugins->exec_hook('attachment_upload', [
|
||||
'path' => $filepath,
|
||||
'size' => $_FILES['_file']['size'][$i],
|
||||
'name' => $_FILES['_file']['name'][$i],
|
||||
'mimetype' => 'image/' . $imageprop['type'],
|
||||
'group' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$err && !empty($attachment['status']) && empty($attachment['abort'])) {
|
||||
$id = $attachment['id'];
|
||||
|
||||
// store new file in session
|
||||
unset($attachment['status'], $attachment['abort']);
|
||||
$rcmail->session->append($type . '.files', $id, $attachment);
|
||||
|
||||
$content = rcube::Q($attachment['name']);
|
||||
|
||||
$rcmail->output->command('add2attachment_list', "rcmfile$id", [
|
||||
'html' => $content,
|
||||
'name' => $attachment['name'],
|
||||
'mimetype' => $attachment['mimetype'],
|
||||
'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']),
|
||||
'complete' => true
|
||||
],
|
||||
$uploadid
|
||||
);
|
||||
}
|
||||
else {
|
||||
$error_label = null;
|
||||
if ($err == 'type_error') {
|
||||
$error_label = 'invalidimageformat';
|
||||
}
|
||||
else if ($err == 'size_error') {
|
||||
$error_label = ['name' => 'filesizeerror', 'vars' => ['size' => self::show_bytes($max_size)]];
|
||||
}
|
||||
|
||||
self::upload_error($err, $attachment, $error_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (self::upload_failure()) {
|
||||
$rcmail->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
$rcmail->output->send('iframe');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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: |
|
||||
| Displaying uploaded images |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_settings_upload_display extends rcmail_action
|
||||
{
|
||||
protected static $mode = self::MODE_HTTP;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$from = rcube_utils::get_input_string('_from', rcube_utils::INPUT_GET);
|
||||
$type = preg_replace('/(add|edit)-/', '', $from);
|
||||
|
||||
// Plugins in Settings may use this file for some uploads (#5694)
|
||||
// Make sure it does not contain a dot, which is a special character
|
||||
// when using rcube_session::append() below
|
||||
$type = str_replace('.', '-', $type);
|
||||
|
||||
$id = 'undefined';
|
||||
|
||||
if (preg_match('/^rcmfile(\w+)$/', $_GET['_file'], $regs)) {
|
||||
$id = $regs[1];
|
||||
}
|
||||
|
||||
self::display_uploaded_file($_SESSION[$type]['files'][$id]);
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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: |
|
||||
| Display error message page |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_error extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$ERROR_CODE = !empty($args['code']) ? $args['code'] : 500;
|
||||
$ERROR_MESSAGE = !empty($args['message']) ? $args['message'] : null;
|
||||
|
||||
// authorization error
|
||||
if ($ERROR_CODE == 401) {
|
||||
$error_title = $rcmail->gettext('errauthorizationfailed');
|
||||
$error_text = nl2br($rcmail->gettext('errunauthorizedexplain')
|
||||
. "\n" . $rcmail->gettext('errcontactserveradmin'));
|
||||
}
|
||||
// forbidden due to request check
|
||||
else if ($ERROR_CODE == 403) {
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->request_status == rcube::REQUEST_ERROR_URL) {
|
||||
$url = $rcmail->url($_GET, true, false, true);
|
||||
$add = html::a($url, $rcmail->gettext('clicktoresumesession'));
|
||||
}
|
||||
else {
|
||||
$add = $rcmail->gettext('errcontactserveradmin');
|
||||
}
|
||||
|
||||
$error_title = $rcmail->gettext('errrequestcheckfailed');
|
||||
$error_text = nl2br($rcmail->gettext('errcsrfprotectionexplain')) . '<p>' . $add . '</p>';
|
||||
}
|
||||
// failed request (wrong step in URL)
|
||||
else if ($ERROR_CODE == 404) {
|
||||
$request_url = htmlentities($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
$error_title = $rcmail->gettext('errnotfound');
|
||||
$error_text = nl2br($rcmail->gettext('errnotfoundexplain')
|
||||
. "\n" . $rcmail->gettext('errcontactserveradmin'));
|
||||
|
||||
$error_text .= '<p><i>' . $rcmail->gettext('errfailedrequest') . ": $request_url</i></p>";
|
||||
}
|
||||
// Gone, e.g. message cached but not in the storage
|
||||
else if ($ERROR_CODE == 410) {
|
||||
$error_title = $rcmail->gettext('servererror');
|
||||
$error_text = $rcmail->gettext('messageopenerror');
|
||||
}
|
||||
// invalid compose ID
|
||||
else if ($ERROR_CODE == 450 && $_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->action == 'compose') {
|
||||
$url = $rcmail->url('compose');
|
||||
|
||||
$error_title = $rcmail->gettext('errcomposesession');
|
||||
$error_text = nl2br($rcmail->gettext('errcomposesessionexplain'))
|
||||
. '<p>' . html::a($url, $rcmail->gettext('clicktocompose')) . '</p>';
|
||||
}
|
||||
// database connection error
|
||||
else if ($ERROR_CODE == 601) {
|
||||
$error_title = "Configuration error";
|
||||
$error_text = nl2br($ERROR_MESSAGE) . "<br />Please read the INSTALL instructions!";
|
||||
}
|
||||
// database connection error
|
||||
else if ($ERROR_CODE == 603) {
|
||||
$error_title = $rcmail->gettext('dberror');
|
||||
$error_text = nl2br($rcmail->gettext('dbconnerror') . "\n" . $rcmail->gettext('errcontactserveradmin'));
|
||||
}
|
||||
// system error
|
||||
else {
|
||||
$error_title = $rcmail->gettext('servererror');
|
||||
$error_text = sprintf('Error No. [%s]', $ERROR_CODE);
|
||||
}
|
||||
|
||||
// inform plugins
|
||||
if ($rcmail->plugins) {
|
||||
$plugin = $rcmail->plugins->exec_hook('error_page', [
|
||||
'code' => $ERROR_CODE,
|
||||
'title' => $error_title,
|
||||
'text' => $error_text,
|
||||
]);
|
||||
|
||||
if (!empty($plugin['title'])) {
|
||||
$error_title = $plugin['title'];
|
||||
}
|
||||
if (!empty($plugin['text'])) {
|
||||
$error_text = $plugin['text'];
|
||||
}
|
||||
}
|
||||
|
||||
$HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500;
|
||||
|
||||
// Ajax request
|
||||
if ($rcmail->output && $rcmail->output->type == 'js') {
|
||||
$rcmail->output->sendExit('', ["HTTP/1.0 $HTTP_ERR_CODE $error_title"]);
|
||||
}
|
||||
|
||||
// compose page content
|
||||
$page_content = '<div class="boxerror">'
|
||||
.'<h3 class="error-title">' . mb_strtoupper($error_title) . '</h3>'
|
||||
.'<div class="error-text">' . $error_text . '</div>'
|
||||
.'</div>';
|
||||
|
||||
if ($rcmail->output && $rcmail->output->template_exists('error')) {
|
||||
$GLOBALS['__page_content'] = $page_content;
|
||||
|
||||
$task = empty($rcmail->user) || empty($rcmail->user->ID) ? '-login' : '';
|
||||
|
||||
$rcmail->output->reset();
|
||||
$rcmail->output->set_env('error_task', 'error' . $task);
|
||||
$rcmail->output->set_env('server_error', $ERROR_CODE);
|
||||
$rcmail->output->set_env('comm_path', $rcmail->comm_path);
|
||||
$rcmail->output->send('error');
|
||||
}
|
||||
|
||||
$skin = $rcmail->config->get('skin', 'default');
|
||||
$product = $rcmail->config->get('product_name', 'Roundcube Webmail');
|
||||
|
||||
$output = '<!doctype html><html><head>'
|
||||
. '<title>' . $product . ':: ERROR</title>'
|
||||
. '<link rel="stylesheet" type="text/css" href="skins/$skin/common.css" />'
|
||||
. '</head><body>'
|
||||
. '<table border="0" cellspacing="0" cellpadding="0" width="100%" height="80%">'
|
||||
. '<tr><td align="center">' . $page_content . '</td></tr>'
|
||||
. '</table>'
|
||||
. '</body></html>';
|
||||
|
||||
$rcmail->output->sendExit($output);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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: |
|
||||
| Convert HTML message to plain text |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_html2text extends rcmail_action
|
||||
{
|
||||
public static $source = 'php://input';
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$html = file_get_contents(self::$source);
|
||||
|
||||
$params['links'] = (bool) rcube_utils::get_input_value('_do_links', rcube_utils::INPUT_GET);
|
||||
$params['width'] = (int) rcube_utils::get_input_value('_width', rcube_utils::INPUT_GET);
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$text = $rcmail->html2text($html, $params);
|
||||
|
||||
$rcmail->output->sendExit($text, ['Content-Type: text/plain; charset=' . RCUBE_CHARSET]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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: |
|
||||
| Delete rows from cache tables |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Dennis P. Nikolaenko <dennis@nikolaenko.ru> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_killcache extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
// don't allow public access if not in devel_mode
|
||||
if (!$rcmail->config->get('devel_mode')) {
|
||||
header("HTTP/1.0 401 Access denied");
|
||||
die("Access denied!");
|
||||
}
|
||||
|
||||
// @TODO: transaction here (if supported by DB) would be a good thing
|
||||
$res = $rcmail->db->query("DELETE FROM " . $rcmail->db->table_name('cache', true));
|
||||
if ($err = $rcmail->db->is_error($res)) {
|
||||
exit($err);
|
||||
}
|
||||
|
||||
$res = $rcmail->db->query("DELETE FROM " . $rcmail->db->table_name('cache_shared', true));
|
||||
if ($err = $rcmail->db->is_error($res)) {
|
||||
exit($err);
|
||||
}
|
||||
|
||||
$res = $rcmail->db->query("DELETE FROM " . $rcmail->db->table_name('cache_messages', true));
|
||||
if ($err = $rcmail->db->is_error($res)) {
|
||||
exit($err);
|
||||
}
|
||||
|
||||
$res = $rcmail->db->query("DELETE FROM " . $rcmail->db->table_name('cache_index', true));
|
||||
if ($err = $rcmail->db->is_error($res)) {
|
||||
exit($err);
|
||||
}
|
||||
|
||||
$res = $rcmail->db->query("DELETE FROM " . $rcmail->db->table_name('cache_thread', true));
|
||||
if ($err = $rcmail->db->is_error($res)) {
|
||||
exit($err);
|
||||
}
|
||||
|
||||
echo "Cache cleared\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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: |
|
||||
| Modify CSS source from a URL |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_modcss extends rcmail_action
|
||||
{
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$url = preg_replace('![^a-z0-9.-]!i', '', $_GET['_u']);
|
||||
|
||||
if ($url === null || !($realurl = $_SESSION['modcssurls'][$url])) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit("Unauthorized request");
|
||||
}
|
||||
|
||||
// don't allow any other connections than http(s)
|
||||
if (!preg_match('~^https?://~i', $realurl, $matches)) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit("Invalid URL");
|
||||
}
|
||||
|
||||
$source = false;
|
||||
$ctype = null;
|
||||
|
||||
try {
|
||||
$client = rcube::get_instance()->get_http_client();
|
||||
$response = $client->get($realurl);
|
||||
|
||||
if (!empty($response)) {
|
||||
$ctype = $response->getHeader('Content-Type');
|
||||
$ctype = !empty($ctype) ? $ctype[0] : '';
|
||||
$source = $response->getBody();
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
rcube::raise_error($e, true, false);
|
||||
}
|
||||
|
||||
$ctype_regexp = '~^text/(css|plain)~i';
|
||||
$container_id = isset($_GET['_c']) ? preg_replace('/[^a-z0-9]/i', '', $_GET['_c']) : '';
|
||||
$css_prefix = isset($_GET['_p']) ? preg_replace('/[^a-z0-9]/i', '', $_GET['_p']) : '';
|
||||
|
||||
if ($source !== false && $ctype && preg_match($ctype_regexp, $ctype)) {
|
||||
header('Content-Type: text/css');
|
||||
echo rcube_utils::mod_css_styles($source, $container_id, false, $css_prefix);
|
||||
exit;
|
||||
}
|
||||
|
||||
header('HTTP/1.0 404 Not Found');
|
||||
exit("Invalid response returned by server");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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 preferences setting in database |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_save_pref extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$name = rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST);
|
||||
$value = rcube_utils::get_input_value('_value', rcube_utils::INPUT_POST);
|
||||
$sessname = rcube_utils::get_input_string('_session', rcube_utils::INPUT_POST);
|
||||
|
||||
// Whitelisted preferences and session variables, others
|
||||
// can be added by plugins
|
||||
$whitelist = [
|
||||
'list_cols',
|
||||
'collapsed_folders',
|
||||
'collapsed_abooks',
|
||||
];
|
||||
|
||||
$whitelist_sess = [
|
||||
'list_attrib/columns',
|
||||
];
|
||||
|
||||
$whitelist = array_merge($whitelist, $rcmail->plugins->allowed_prefs);
|
||||
$whitelist_sess = array_merge($whitelist_sess, $rcmail->plugins->allowed_session_prefs);
|
||||
|
||||
if (!in_array($name, $whitelist) || ($sessname && !in_array($sessname, $whitelist_sess))) {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => sprintf("Hack attempt detected (user: %s)", $rcmail->get_user_name())
|
||||
],
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
$rcmail->output->reset();
|
||||
$rcmail->output->send();
|
||||
}
|
||||
|
||||
// save preference value
|
||||
$rcmail->user->save_prefs([$name => $value]);
|
||||
|
||||
// update also session if requested
|
||||
if ($sessname) {
|
||||
// Support multidimensional arrays...
|
||||
$vars = explode('/', $sessname);
|
||||
|
||||
// ... up to 3 levels
|
||||
if (count($vars) == 1) {
|
||||
$_SESSION[$vars[0]] = $value;
|
||||
}
|
||||
else if (count($vars) == 2) {
|
||||
$_SESSION[$vars[0]][$vars[1]] = $value;
|
||||
}
|
||||
else if (count($vars) == 3) {
|
||||
$_SESSION[$vars[0]][$vars[1]][$vars[2]] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$rcmail->output->reset();
|
||||
$rcmail->output->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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: |
|
||||
| Invoke the configured or default spell checking engine. |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Kris Steinhoff <steinhof@umich.edu> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_spell extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
protected static $mode = self::MODE_AJAX;
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
// read input
|
||||
$lang = rcube_utils::get_input_string('lang', rcube_utils::INPUT_GET);
|
||||
$data = file_get_contents('php://input');
|
||||
|
||||
$learn_word = strpos($data, '<learnword>');
|
||||
|
||||
// Get data string
|
||||
$left = strpos($data, '<text>');
|
||||
$right = strrpos($data, '</text>');
|
||||
$data = substr($data, $left+6, $right-($left+6));
|
||||
$data = html_entity_decode($data, ENT_QUOTES, RCUBE_CHARSET);
|
||||
|
||||
$spellchecker = new rcube_spellchecker($lang);
|
||||
|
||||
if ($learn_word) {
|
||||
$spellchecker->add_word($data);
|
||||
$result = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><learnwordresult></learnwordresult>';
|
||||
}
|
||||
else if (empty($data)) {
|
||||
$result = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="0"></spellresult>';
|
||||
}
|
||||
else {
|
||||
$spellchecker->check($data);
|
||||
$result = $spellchecker->get_xml();
|
||||
}
|
||||
|
||||
if ($error = $spellchecker->error()) {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Spellcheck error: " . $error
|
||||
],
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
header("HTTP/1.0 500 Internal Server Error");
|
||||
exit;
|
||||
}
|
||||
|
||||
// set response length
|
||||
header("Content-Length: " . strlen($result));
|
||||
|
||||
// Don't use server's default Content-Type charset (#1486406)
|
||||
header("Content-Type: text/xml; charset=" . RCUBE_CHARSET);
|
||||
print $result;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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: |
|
||||
| Spellchecker for TinyMCE |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_spell_html extends rcmail_action
|
||||
{
|
||||
// only process ajax requests
|
||||
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();
|
||||
$method = rcube_utils::get_input_string('method', rcube_utils::INPUT_POST);
|
||||
$lang = rcube_utils::get_input_string('lang', rcube_utils::INPUT_POST);
|
||||
$result = [];
|
||||
|
||||
$spellchecker = new rcube_spellchecker($lang);
|
||||
|
||||
if ($method == 'addToDictionary') {
|
||||
$data = rcube_utils::get_input_string('word', rcube_utils::INPUT_POST);
|
||||
|
||||
$spellchecker->add_word($data);
|
||||
$result['result'] = true;
|
||||
}
|
||||
else {
|
||||
$data = rcube_utils::get_input_string('text', rcube_utils::INPUT_POST, true);
|
||||
$data = html_entity_decode($data, ENT_QUOTES, RCUBE_CHARSET);
|
||||
|
||||
if ($data && !$spellchecker->check($data)) {
|
||||
$result['words'] = $spellchecker->get();
|
||||
$result['dictionary'] = (bool) $rcmail->config->get('spellcheck_dictionary');
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Type: application/json; charset=" . RCUBE_CHARSET);
|
||||
|
||||
if ($error = $spellchecker->error()) {
|
||||
rcube::raise_error([
|
||||
'code' => 500,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Spellcheck error: " . $error
|
||||
],
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
echo json_encode(['error' => $rcmail->gettext('internalerror')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// send output
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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: |
|
||||
| Convert plain text to HTML |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
class rcmail_action_utils_text2html extends rcmail_action
|
||||
{
|
||||
public static $source = 'php://input';
|
||||
|
||||
/**
|
||||
* Request handler.
|
||||
*
|
||||
* @param array $args Arguments from the previous step(s)
|
||||
*/
|
||||
public function run($args = [])
|
||||
{
|
||||
$text = file_get_contents(self::$source);
|
||||
|
||||
$converter = new rcube_text2html($text, false, ['wrap' => true]);
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
$html = $converter->get_html();
|
||||
|
||||
$rcmail->output->sendExit($html, ['Content-Type: text/html; charset=' . RCUBE_CHARSET]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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: |
|
||||
| Setup the command line environment and provide some utility |
|
||||
| functions. |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
if (php_sapi_name() != 'cli') {
|
||||
die('Not on the "shell" (php-cli).');
|
||||
}
|
||||
|
||||
require_once INSTALL_PATH . 'program/include/iniset.php';
|
||||
|
||||
// Unset max. execution time limit, set to 120 seconds in iniset.php
|
||||
@set_time_limit(0);
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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: |
|
||||
| Setup the application environment required to process |
|
||||
| any request. |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Till Klampaeckel <till@php.net> |
|
||||
| Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
if (PHP_VERSION_ID < 70300) {
|
||||
die("Unsupported PHP version. Required PHP >= 7.3.");
|
||||
}
|
||||
|
||||
// application constants
|
||||
define('RCMAIL_VERSION', '1.6.3');
|
||||
define('RCMAIL_START', microtime(true));
|
||||
|
||||
if (!defined('INSTALL_PATH')) {
|
||||
define('INSTALL_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
|
||||
}
|
||||
|
||||
if (!defined('RCMAIL_CONFIG_DIR')) {
|
||||
define('RCMAIL_CONFIG_DIR', getenv('ROUNDCUBE_CONFIG_DIR') ?: (INSTALL_PATH . 'config'));
|
||||
}
|
||||
|
||||
if (!defined('RCUBE_LOCALIZATION_DIR')) {
|
||||
define('RCUBE_LOCALIZATION_DIR', INSTALL_PATH . 'program/localization/');
|
||||
}
|
||||
|
||||
define('RCUBE_INSTALL_PATH', INSTALL_PATH);
|
||||
define('RCUBE_CONFIG_DIR', RCMAIL_CONFIG_DIR.'/');
|
||||
|
||||
// Show basic error message on fatal PHP error
|
||||
register_shutdown_function('rcmail_error_handler');
|
||||
|
||||
// RC include folders MUST be included FIRST to avoid other
|
||||
// possible not compatible libraries (i.e PEAR) to be included
|
||||
// instead the ones provided by RC
|
||||
$include_path = INSTALL_PATH . 'program/lib' . PATH_SEPARATOR;
|
||||
$include_path.= ini_get('include_path');
|
||||
|
||||
if (set_include_path($include_path) === false) {
|
||||
die("Fatal error: ini_set/set_include_path does not work.");
|
||||
}
|
||||
|
||||
// increase maximum execution time for php scripts
|
||||
// (does not work in safe mode)
|
||||
@set_time_limit(120);
|
||||
|
||||
// include composer autoloader (if available)
|
||||
if (@file_exists(INSTALL_PATH . 'vendor/autoload.php')) {
|
||||
require INSTALL_PATH . 'vendor/autoload.php';
|
||||
}
|
||||
|
||||
// translate PATH_INFO to _task and _action GET parameters
|
||||
if (!empty($_SERVER['PATH_INFO']) && preg_match('!^/([a-z]+)/([a-z]+)$!', $_SERVER['PATH_INFO'], $m)) {
|
||||
if (!isset($_GET['_task'])) {
|
||||
$_GET['_task'] = $m[1];
|
||||
}
|
||||
if (!isset($_GET['_action'])) {
|
||||
$_GET['_action'] = $m[2];
|
||||
}
|
||||
}
|
||||
|
||||
// include Roundcube Framework
|
||||
require_once 'Roundcube/bootstrap.php';
|
||||
|
||||
// register autoloader for rcmail app classes
|
||||
spl_autoload_register('rcmail_autoload');
|
||||
|
||||
/**
|
||||
* PHP5 autoloader routine for dynamic class loading
|
||||
*/
|
||||
function rcmail_autoload($classname)
|
||||
{
|
||||
if (strpos($classname, 'rcmail') === 0) {
|
||||
if (preg_match('/^rcmail_action_([^_]+)_(.*)$/', $classname, $matches)) {
|
||||
$filepath = INSTALL_PATH . "program/actions/{$matches[1]}/{$matches[2]}.php";
|
||||
}
|
||||
else {
|
||||
$filepath = INSTALL_PATH . "program/include/$classname.php";
|
||||
}
|
||||
|
||||
if (is_readable($filepath)) {
|
||||
include_once $filepath;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a generic error message on fatal PHP error
|
||||
*/
|
||||
function rcmail_error_handler()
|
||||
{
|
||||
$error = error_get_last();
|
||||
|
||||
if ($error && ($error['type'] === E_ERROR || $error['type'] === E_PARSE)) {
|
||||
rcmail_fatal_error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise a generic error message on error
|
||||
*/
|
||||
function rcmail_fatal_error()
|
||||
{
|
||||
if (php_sapi_name() === 'cli') {
|
||||
echo "Fatal error: Please check the Roundcube error log and/or server error logs for more information.\n";
|
||||
}
|
||||
elseif (!empty($_REQUEST['_remote'])) {
|
||||
// Ajax request from UI
|
||||
header('Content-Type: application/json; charset=UTF-8');
|
||||
echo json_encode(['code' => 500, 'message' => "Internal Server Error"]);
|
||||
}
|
||||
else {
|
||||
if (!defined('RCUBE_FATAL_ERROR_MSG')) {
|
||||
define('RCUBE_FATAL_ERROR_MSG', INSTALL_PATH . 'program/resources/error.html');
|
||||
}
|
||||
|
||||
echo file_get_contents(RCUBE_FATAL_ERROR_MSG);
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,393 @@
|
||||
<?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: |
|
||||
| Unified access to attachment properties and body |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Unified access to attachment properties and body
|
||||
* Unified for message parts as well as uploaded attachments
|
||||
*
|
||||
* @package Webmail
|
||||
*/
|
||||
class rcmail_attachment_handler
|
||||
{
|
||||
public $filename;
|
||||
public $size;
|
||||
public $mimetype;
|
||||
public $ident;
|
||||
public $charset = RCUBE_CHARSET;
|
||||
|
||||
private $message;
|
||||
private $part;
|
||||
private $upload;
|
||||
private $body;
|
||||
private $body_file;
|
||||
private $download = false;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
* Reads request parameters and initializes attachment/part props.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
ob_end_clean();
|
||||
|
||||
$part_id = rcube_utils::get_input_string('_part', rcube_utils::INPUT_GET);
|
||||
$file_id = rcube_utils::get_input_string('_file', rcube_utils::INPUT_GET);
|
||||
$compose_id = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GET);
|
||||
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
|
||||
$rcube = rcube::get_instance();
|
||||
|
||||
$this->download = !empty($_GET['_download']);
|
||||
|
||||
// similar code as in program/steps/mail/show.inc
|
||||
if (!empty($uid)) {
|
||||
$rcube->config->set('prefer_html', true);
|
||||
$this->message = new rcube_message($uid, null, !empty($_GET['_safe']));
|
||||
|
||||
if ($this->part = $this->message->mime_parts[$part_id]) {
|
||||
$this->filename = rcmail_action_mail_index::attachment_name($this->part);
|
||||
$this->mimetype = $this->part->mimetype;
|
||||
$this->size = $this->part->size;
|
||||
$this->ident = $this->message->headers->messageID . ':' . $this->part->mime_id . ':' . $this->size . ':' . $this->mimetype;
|
||||
$this->charset = $this->part->charset ?: RCUBE_CHARSET;
|
||||
|
||||
if (empty($_GET['_frame'])) {
|
||||
// allow post-processing of the attachment body
|
||||
$plugin = $rcube->plugins->exec_hook('message_part_get', [
|
||||
'uid' => $uid,
|
||||
'id' => $this->part->mime_id,
|
||||
'mimetype' => $this->mimetype,
|
||||
'part' => $this->part,
|
||||
'download' => $this->download,
|
||||
]);
|
||||
|
||||
if ($plugin['abort']) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// overwrite modified vars from plugin
|
||||
$this->mimetype = $plugin['mimetype'];
|
||||
|
||||
if (!empty($plugin['body'])) {
|
||||
$this->body = $plugin['body'];
|
||||
$this->size = strlen($this->body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($file_id && $compose_id) {
|
||||
$file_id = preg_replace('/^rcmfile/', '', $file_id);
|
||||
|
||||
if (($compose = $_SESSION['compose_data_' . $compose_id])
|
||||
&& ($this->upload = $compose['attachments'][$file_id])
|
||||
) {
|
||||
$this->filename = $this->upload['name'];
|
||||
$this->mimetype = $this->upload['mimetype'];
|
||||
$this->size = $this->upload['size'];
|
||||
$this->ident = sprintf('%s:%s%s', $compose_id, $file_id, $this->size);
|
||||
$this->charset = !empty($this->upload['charset']) ? $this->upload['charset'] : RCUBE_CHARSET;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->part) && empty($this->upload)) {
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
|
||||
// check connection status
|
||||
self::check_storage_status();
|
||||
|
||||
$this->mimetype = rcube_mime::fix_mimetype($this->mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove temp files, etc.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->body_file) {
|
||||
@unlink($this->body_file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the object is a message part not uploaded file
|
||||
*
|
||||
* @return bool True if the object is a message part
|
||||
*/
|
||||
public function is_message_part()
|
||||
{
|
||||
return !empty($this->message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object/request status
|
||||
*
|
||||
* @return bool Status
|
||||
*/
|
||||
public function is_valid()
|
||||
{
|
||||
return !empty($this->part) || !empty($this->upload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return attachment/part mimetype if this is an image
|
||||
* of supported type.
|
||||
*
|
||||
* @return string Image mimetype
|
||||
*/
|
||||
public function image_type()
|
||||
{
|
||||
$part = (object) [
|
||||
'filename' => $this->filename,
|
||||
'mimetype' => $this->mimetype,
|
||||
];
|
||||
|
||||
return rcmail_action_mail_index::part_image_type($part);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted attachment/part size (with units)
|
||||
*
|
||||
* @return string Attachment/part size (with units)
|
||||
*/
|
||||
public function size()
|
||||
{
|
||||
$part = $this->part ?: ((object) ['size' => $this->size, 'exact_size' => true]);
|
||||
return rcmail_action::message_part_size($part);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, prints or saves the attachment/part body
|
||||
*/
|
||||
public function body($size = null, $fp = null)
|
||||
{
|
||||
// we may have the body in memory or file already
|
||||
if ($this->body !== null) {
|
||||
if ($fp == -1) {
|
||||
echo $size ? substr($this->body, 0, $size) : $this->body;
|
||||
}
|
||||
else if ($fp) {
|
||||
$result = fwrite($fp, $size ? substr($this->body, $size) : $this->body) !== false;
|
||||
}
|
||||
else {
|
||||
$result = $size ? substr($this->body, 0, $size) : $this->body;
|
||||
}
|
||||
}
|
||||
else if ($this->body_file) {
|
||||
if ($size) {
|
||||
$result = file_get_contents($this->body_file, false, null, 0, $size);
|
||||
}
|
||||
else {
|
||||
$result = file_get_contents($this->body_file);
|
||||
}
|
||||
|
||||
if ($fp == -1) {
|
||||
echo $result;
|
||||
}
|
||||
else if ($fp) {
|
||||
$result = fwrite($fp, $result) !== false;
|
||||
}
|
||||
}
|
||||
else if ($this->message) {
|
||||
$result = $this->message->get_part_body($this->part->mime_id, false, 0, $fp);
|
||||
|
||||
// check connection status
|
||||
if (!$fp && $this->size && empty($result)) {
|
||||
self::check_storage_status();
|
||||
}
|
||||
}
|
||||
else if ($this->upload) {
|
||||
// This hook retrieves the attachment contents from the file storage backend
|
||||
$attachment = rcube::get_instance()->plugins->exec_hook('attachment_get', $this->upload);
|
||||
|
||||
if ($fp && $fp != -1) {
|
||||
if ($attachment['data']) {
|
||||
$result = fwrite($fp, $size ? substr($attachment['data'], 0, $size) : $attachment['data']) !== false;
|
||||
}
|
||||
else if ($attachment['path']) {
|
||||
if ($fh = fopen($attachment['path'], 'rb')) {
|
||||
$result = stream_copy_to_stream($fh, $fp, $size ? $size : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data = $attachment['data'] ?? '';
|
||||
if (!$data && $attachment['path']) {
|
||||
$data = file_get_contents($attachment['path']);
|
||||
}
|
||||
|
||||
if ($fp == -1) {
|
||||
echo $size ? substr($data, 0, $size) : $data;
|
||||
}
|
||||
else {
|
||||
$result = $size ? substr($data, 0, $size) : $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the body to a file
|
||||
*
|
||||
* @param string $filename File name with path
|
||||
*
|
||||
* @return bool True on success, False on failure
|
||||
*/
|
||||
public function body_to_file($filename)
|
||||
{
|
||||
if ($filename && $this->size && ($fp = fopen($filename, 'w'))) {
|
||||
$this->body(0, $fp);
|
||||
$this->body_file = $filename;
|
||||
fclose($fp);
|
||||
@chmod($filename, 0600);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output attachment body with content filtering
|
||||
*/
|
||||
public function output($mimetype)
|
||||
{
|
||||
if (!$this->size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$secure = stripos($mimetype, 'image/') === false || $this->download;
|
||||
|
||||
// Remove <script> in SVG images
|
||||
if (!$secure && stripos($mimetype, 'image/svg') === 0) {
|
||||
if (!$this->body) {
|
||||
$this->body = $this->body();
|
||||
if (empty($this->body)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
echo self::svg_filter($this->body);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->body !== null && !$this->download) {
|
||||
header("Content-Length: " . strlen($this->body));
|
||||
echo $this->body;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't be tempted to set Content-Length to $part->d_parameters['size'] (#1490482)
|
||||
// RFC2183 says "The size parameter indicates an approximate size"
|
||||
|
||||
return $this->body(0, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted HTML if the attachment is HTML
|
||||
*/
|
||||
public function html()
|
||||
{
|
||||
list($type, $subtype) = explode('/', $this->mimetype);
|
||||
$part = (object) [
|
||||
'charset' => $this->charset,
|
||||
'ctype_secondary' => $subtype,
|
||||
];
|
||||
|
||||
// get part body if not available
|
||||
// fix formatting and charset
|
||||
$body = rcube_message::format_part_body($this->body(), $part);
|
||||
|
||||
// show images?
|
||||
$is_safe = $this->is_safe();
|
||||
|
||||
return rcmail_action_mail_index::wash_html($body, ['safe' => $is_safe, 'inline_html' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove <script> in SVG images
|
||||
*/
|
||||
public static function svg_filter($body)
|
||||
{
|
||||
// clean SVG with washtml
|
||||
$wash_opts = [
|
||||
'show_washed' => false,
|
||||
'allow_remote' => false,
|
||||
'charset' => RCUBE_CHARSET,
|
||||
'html_elements' => ['title'],
|
||||
];
|
||||
|
||||
// initialize HTML washer
|
||||
$washer = new rcube_washtml($wash_opts);
|
||||
|
||||
// allow CSS styles, will be sanitized by rcmail_washtml_callback()
|
||||
$washer->add_callback('style', 'rcmail_action_mail_index::washtml_callback');
|
||||
|
||||
return $washer->wash($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles nicely storage connection errors
|
||||
*/
|
||||
public static function check_storage_status()
|
||||
{
|
||||
$error = rcmail::get_instance()->storage->get_error_code();
|
||||
|
||||
// Check if we have a connection error
|
||||
if ($error == rcube_imap_generic::ERROR_BAD) {
|
||||
ob_end_clean();
|
||||
|
||||
// Get action is often executed simultaneously.
|
||||
// Some servers have MAXPERIP or other limits.
|
||||
// To workaround this we'll wait for some time
|
||||
// and try again (once).
|
||||
// Note: Random sleep interval is used to minimize concurrency
|
||||
// in getting message parts
|
||||
|
||||
if (!isset($_GET['_redirected'])) {
|
||||
usleep(rand(10, 30) * 100000); // 1-3 sec.
|
||||
header('Location: ' . $_SERVER['REQUEST_URI'] . '&_redirected=1');
|
||||
}
|
||||
else {
|
||||
rcube::raise_error([
|
||||
'code' => 500, 'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => 'Unable to get/display message part. IMAP connection error'
|
||||
],
|
||||
true, true
|
||||
);
|
||||
}
|
||||
|
||||
// Don't kill session, just quit (#1486995)
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public function is_safe()
|
||||
{
|
||||
if ($this->message) {
|
||||
return rcmail_action_mail_index::check_safe($this->message);
|
||||
}
|
||||
|
||||
return !empty($_GET['_safe']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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: |
|
||||
| Render a simple HTML page with the given contents |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to create an empty HTML page with some default styles
|
||||
*
|
||||
* @package Webmail
|
||||
* @subpackage View
|
||||
*/
|
||||
class rcmail_html_page extends rcmail_output_html
|
||||
{
|
||||
protected $inline_warning;
|
||||
|
||||
/**
|
||||
* Process the page content and write to stdOut
|
||||
*
|
||||
* @param string $contents HTML page content
|
||||
*/
|
||||
public function write($contents = '')
|
||||
{
|
||||
self::reset(true);
|
||||
|
||||
// load embed.css from skin folder (if exists)
|
||||
$embed_css = $this->config->get('embed_css_location', '/embed.css');
|
||||
if ($embed_css = $this->get_skin_file($embed_css, $path, null, true)) {
|
||||
$this->include_css($embed_css);
|
||||
}
|
||||
else { // set default styles for warning blocks inside the attachment part frame
|
||||
$this->add_header(html::tag('style', ['type' => 'text/css'],
|
||||
".rcmail-inline-message { font-family: sans-serif; border:2px solid #ffdf0e;"
|
||||
. "background:#fef893; padding:0.6em 1em; margin-bottom:0.6em }\n" .
|
||||
".rcmail-inline-buttons { margin-bottom:0 }"
|
||||
));
|
||||
}
|
||||
|
||||
if (empty($contents)) {
|
||||
$contents = '<html><body></body></html>';
|
||||
}
|
||||
|
||||
if ($this->inline_warning) {
|
||||
$body_start = 0;
|
||||
if ($body_pos = strpos($contents, '<body')) {
|
||||
$body_start = strpos($contents, '>', $body_pos) + 1;
|
||||
}
|
||||
|
||||
$contents = substr_replace($contents, $this->inline_warning, $body_start, 0);
|
||||
}
|
||||
|
||||
parent::write($contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add inline warning with optional button
|
||||
*
|
||||
* @param string $text Warning content
|
||||
* @param string $button_label Button label
|
||||
* @param string $button_url Button URL
|
||||
*/
|
||||
public function register_inline_warning($text, $button_label = null, $button_url = null)
|
||||
{
|
||||
$text = html::span(null, $text);
|
||||
|
||||
if ($button_label) {
|
||||
$onclick = "location.href = '$button_url'";
|
||||
$button = html::tag('button', ['onclick' => $onclick], rcube::Q($button_label));
|
||||
$text .= html::p(['class' => 'rcmail-inline-buttons'], $button);
|
||||
}
|
||||
|
||||
$this->inline_warning = html::div(['class' => 'rcmail-inline-message rcmail-inline-warning'], $text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,932 @@
|
||||
<?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: |
|
||||
| Roundcube Installer |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to control the installation process of the Roundcube Webmail package
|
||||
*
|
||||
* @category Install
|
||||
* @package Webmail
|
||||
*/
|
||||
class rcmail_install
|
||||
{
|
||||
public $step;
|
||||
public $last_error;
|
||||
public $is_post = false;
|
||||
public $failures = 0;
|
||||
public $config = [];
|
||||
public $defaults = [];
|
||||
public $comments = [];
|
||||
public $configured = false;
|
||||
public $legacy_config = false;
|
||||
public $email_pattern = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9])';
|
||||
|
||||
public $bool_config_props = ['ip_check', 'enable_spellcheck', 'auto_create_user', 'smtp_log', 'prefer_html'];
|
||||
public $local_config = ['db_dsnw', 'imap_host', 'support_url', 'des_key', 'plugins'];
|
||||
public $obsolete_config = ['db_backend', 'db_max_length', 'double_auth', 'preview_pane', 'debug_level', 'referer_check'];
|
||||
public $replaced_config = [
|
||||
'skin_path' => 'skin',
|
||||
'locale_string' => 'language',
|
||||
'multiple_identities' => 'identities_level',
|
||||
'addrbook_show_images' => 'show_images',
|
||||
'imap_root' => 'imap_ns_personal',
|
||||
'pagesize' => 'mail_pagesize',
|
||||
'top_posting' => 'reply_mode',
|
||||
'keep_alive' => 'refresh_interval',
|
||||
'min_keep_alive' => 'min_refresh_interval',
|
||||
'default_host' => 'imap_host',
|
||||
'smtp_server' => 'smtp_host',
|
||||
];
|
||||
|
||||
// List of configuration options supported by the Installer
|
||||
public $supported_config = [
|
||||
'product_name', 'support_url', 'temp_dir', 'des_key', 'ip_check', 'enable_spellcheck',
|
||||
'spellcheck_engine', 'identities_level', 'log_driver', 'log_dir', 'syslog_id',
|
||||
'syslog_facility', 'db_dsnw', 'db_prefix', 'imap_host', 'username_domain',
|
||||
'auto_create_user', 'sent_mbox', 'trash_mbox', 'drafts_mbox', 'junk_mbox',
|
||||
'smtp_host', 'smtp_user', 'smtp_pass', 'smtp_log', 'language', 'skin', 'mail_pagesize',
|
||||
'addressbook_pagesize', 'prefer_html', 'htmleditor', 'draft_autosave', 'mdn_requests',
|
||||
'mime_param_folding', 'plugins',
|
||||
];
|
||||
|
||||
// list of supported database drivers
|
||||
public $supported_dbs = [
|
||||
'MySQL' => 'pdo_mysql',
|
||||
'PostgreSQL' => 'pdo_pgsql',
|
||||
'SQLite' => 'pdo_sqlite',
|
||||
'SQLite (v2)' => 'pdo_sqlite2',
|
||||
'SQL Server (SQLSRV)' => 'pdo_sqlsrv',
|
||||
'SQL Server (DBLIB)' => 'pdo_dblib',
|
||||
'Oracle' => 'oci8',
|
||||
];
|
||||
|
||||
/** @var array List of config options with default value change per-release */
|
||||
public $defaults_changes = [
|
||||
'1.4.0' => ['skin', 'smtp_port', 'smtp_user', 'smtp_pass'],
|
||||
'1.4.1' => ['jquery_ui_skin_map'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->step = isset($_REQUEST['_step']) ? intval($_REQUEST['_step']) : 0;
|
||||
$this->is_post = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST';
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton getter
|
||||
*/
|
||||
public static function get_instance()
|
||||
{
|
||||
static $inst;
|
||||
|
||||
if (!$inst) {
|
||||
$inst = new rcmail_install();
|
||||
}
|
||||
|
||||
return $inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the local config files and store properties
|
||||
*/
|
||||
public function load_config()
|
||||
{
|
||||
if ($this->configured) {
|
||||
return;
|
||||
}
|
||||
|
||||
// defaults
|
||||
if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'defaults.inc.php')) {
|
||||
$this->config = (array) $config;
|
||||
$this->defaults = $this->config;
|
||||
}
|
||||
|
||||
$config = null;
|
||||
|
||||
// config
|
||||
if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'config.inc.php')) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
else {
|
||||
if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'main.inc.php')) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
$this->legacy_config = true;
|
||||
}
|
||||
|
||||
if ($config = $this->load_config_file(RCUBE_CONFIG_DIR . 'db.inc.php')) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
$this->legacy_config = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->configured = !empty($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the default config file and store properties
|
||||
*
|
||||
* @param string $file File name with path
|
||||
*/
|
||||
public function load_config_file($file)
|
||||
{
|
||||
if (!is_readable($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = [];
|
||||
$rcmail_config = []; // deprecated var name
|
||||
|
||||
include $file;
|
||||
|
||||
// read comments from config file
|
||||
if (function_exists('token_get_all')) {
|
||||
$tokens = token_get_all(file_get_contents($file));
|
||||
$in_config = false;
|
||||
$buffer = '';
|
||||
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
$token = $tokens[$i];
|
||||
if ($token[0] == T_VARIABLE && ($token[1] == '$config' || $token[1] == '$rcmail_config')) {
|
||||
$in_config = true;
|
||||
if ($buffer && $tokens[$i+1] == '[' && $tokens[$i+2][0] == T_CONSTANT_ENCAPSED_STRING) {
|
||||
$propname = trim($tokens[$i+2][1], "'\"");
|
||||
$this->comments[$propname] = preg_replace('/\n\n/', "\n", $buffer);
|
||||
$buffer = '';
|
||||
$i += 3;
|
||||
}
|
||||
}
|
||||
else if ($in_config && $token[0] == T_COMMENT) {
|
||||
$buffer .= strtr($token[1], ['\n' => "\n"]) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge((array) $rcmail_config, (array) $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for a certain config property
|
||||
*
|
||||
* @param string $name Property name
|
||||
* @param string $default Default value
|
||||
*
|
||||
* @return mixed The property value
|
||||
*/
|
||||
public function getprop($name, $default = '')
|
||||
{
|
||||
$value = $this->config[$name] ?? null;
|
||||
|
||||
if ($name == 'des_key' && !$this->configured && !isset($_REQUEST["_$name"])) {
|
||||
$value = rcube_utils::random_bytes(24);
|
||||
}
|
||||
|
||||
return $value !== null && $value !== '' ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configuration file that contains parameters
|
||||
* that differ from default values.
|
||||
*
|
||||
* @param bool $use_post Use POSTed configuration values (of supported options)
|
||||
*
|
||||
* @return string The complete config file content
|
||||
*/
|
||||
public function create_config($use_post = true)
|
||||
{
|
||||
$config = [];
|
||||
|
||||
foreach ($this->config as $prop => $default) {
|
||||
$post_value = $_POST["_$prop"] ?? null;
|
||||
$value = $default;
|
||||
|
||||
if ($use_post && in_array($prop, $this->supported_config)
|
||||
&& ($post_value !== null || in_array($prop, $this->bool_config_props))
|
||||
) {
|
||||
$value = $post_value;
|
||||
}
|
||||
|
||||
// always disable installer
|
||||
if ($prop == 'enable_installer') {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
// generate new encryption key, never use the default value
|
||||
if ($prop == 'des_key' && $value == $this->defaults[$prop]) {
|
||||
$value = rcube_utils::random_bytes(24);
|
||||
}
|
||||
|
||||
// convert some form data
|
||||
if ($prop == 'db_dsnw' && !empty($_POST['_dbtype'])) {
|
||||
if ($_POST['_dbtype'] == 'sqlite') {
|
||||
$value = sprintf('%s://%s?mode=0646', $_POST['_dbtype'],
|
||||
$_POST['_dbname'][0] == '/' ? '/' . $_POST['_dbname'] : $_POST['_dbname']);
|
||||
}
|
||||
else if ($_POST['_dbtype']) {
|
||||
$value = sprintf('%s://%s:%s@%s/%s', $_POST['_dbtype'],
|
||||
rawurlencode($_POST['_dbuser']), rawurlencode($_POST['_dbpass']), $_POST['_dbhost'], $_POST['_dbname']);
|
||||
}
|
||||
}
|
||||
else if ($prop == 'imap_host' && is_array($value)) {
|
||||
$value = self::_clean_array($value);
|
||||
if (count($value) <= 1) {
|
||||
$value = $value[0];
|
||||
}
|
||||
}
|
||||
else if ($prop == 'mail_pagesize' || $prop == 'addressbook_pagesize') {
|
||||
$value = max(2, intval($value));
|
||||
}
|
||||
else if ($prop == 'smtp_user' && !empty($_POST['_smtp_user_u'])) {
|
||||
$value = '%u';
|
||||
}
|
||||
else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) {
|
||||
$value = '%p';
|
||||
}
|
||||
else if (is_bool($default)) {
|
||||
$value = (bool) $value;
|
||||
}
|
||||
else if (is_numeric($value)) {
|
||||
$value = intval($value);
|
||||
}
|
||||
else if ($prop == 'plugins' && !empty($_POST['submit'])) {
|
||||
$value = [];
|
||||
foreach (array_keys($_POST) as $key) {
|
||||
if (preg_match('/^_plugins_*/', $key)) {
|
||||
array_push($value, $_POST[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip this property
|
||||
if ($value == ($this->defaults[$prop] ?? null)
|
||||
&& (!in_array($prop, $this->local_config)
|
||||
|| in_array($prop, array_merge($this->obsolete_config, array_keys($this->replaced_config)))
|
||||
|| preg_match('/^db_(table|sequence)_/', $prop)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// save change
|
||||
$this->config[$prop] = $value;
|
||||
$config[$prop] = $value;
|
||||
}
|
||||
|
||||
$out = "<?php\n\n";
|
||||
$out .= "/* Local configuration for Roundcube Webmail */\n\n";
|
||||
|
||||
foreach ($config as $prop => $value) {
|
||||
// copy option descriptions from existing config or defaults.inc.php
|
||||
$out .= $this->comments[$prop] ?? '';
|
||||
$out .= "\$config['$prop'] = " . self::_dump_var($value, $prop) . ";\n\n";
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* save generated config file in RCUBE_CONFIG_DIR
|
||||
*
|
||||
* @return boolean True if the file was saved successfully, false if not
|
||||
*/
|
||||
public function save_configfile($config)
|
||||
{
|
||||
if (is_writable(RCUBE_CONFIG_DIR)) {
|
||||
return file_put_contents(RCUBE_CONFIG_DIR . 'config.inc.php', $config);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current configuration for missing properties
|
||||
* and deprecated or obsolete settings
|
||||
*
|
||||
* @param string $version Previous version on upgrade
|
||||
*
|
||||
* @return array List with problems detected
|
||||
*/
|
||||
public function check_config($version = null)
|
||||
{
|
||||
$this->load_config();
|
||||
|
||||
if (!$this->configured) {
|
||||
return;
|
||||
}
|
||||
|
||||
$out = $seen = [];
|
||||
|
||||
// iterate over the current configuration
|
||||
foreach (array_keys($this->config) as $prop) {
|
||||
if (!empty($this->replaced_config[$prop])) {
|
||||
$replacement = $this->replaced_config[$prop];
|
||||
$out['replaced'][] = ['prop' => $prop, 'replacement' => $replacement];
|
||||
$seen[$replacement] = true;
|
||||
}
|
||||
else if (empty($seen[$prop]) && in_array($prop, $this->obsolete_config)) {
|
||||
$out['obsolete'][] = ['prop' => $prop];
|
||||
$seen[$prop] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// the old default mime_magic reference is obsolete
|
||||
if ($this->config['mime_magic'] == '/usr/share/misc/magic') {
|
||||
$out['obsolete'][] = [
|
||||
'prop' => 'mime_magic',
|
||||
'explain' => "Set value to null in order to use system default"
|
||||
];
|
||||
}
|
||||
|
||||
// check config dependencies and contradictions
|
||||
if (!empty($this->config['enable_spellcheck']) && $this->config['spellcheck_engine'] == 'pspell') {
|
||||
if (!extension_loaded('pspell')) {
|
||||
$out['dependencies'][] = [
|
||||
'prop' => 'spellcheck_engine',
|
||||
'explain' => "This requires the <tt>pspell</tt> extension which could not be loaded."
|
||||
];
|
||||
}
|
||||
else if (!empty($this->config['spellcheck_languages'])) {
|
||||
foreach ($this->config['spellcheck_languages'] as $lang => $descr) {
|
||||
if (!@pspell_new($lang)) {
|
||||
$out['dependencies'][] = [
|
||||
'prop' => 'spellcheck_languages',
|
||||
'explain' => "You are missing pspell support for language $lang ($descr)"
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->config['log_driver'] == 'syslog') {
|
||||
if (!function_exists('openlog')) {
|
||||
$out['dependencies'][] = [
|
||||
'prop' => 'log_driver',
|
||||
'explain' => "This requires the <tt>syslog</tt> extension which could not be loaded."
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($this->config['syslog_id'])) {
|
||||
$out['dependencies'][] = [
|
||||
'prop' => 'syslog_id',
|
||||
'explain' => "Using <tt>syslog</tt> for logging requires a syslog ID to be configured"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// check ldap_public sources having global_search enabled
|
||||
if (is_array($this->config['ldap_public']) && !is_array($this->config['autocomplete_addressbooks'])) {
|
||||
foreach ($this->config['ldap_public'] as $ldap_public) {
|
||||
if ($ldap_public['global_search']) {
|
||||
$out['replaced'][] = [
|
||||
'prop' => 'ldap_public::global_search',
|
||||
'replacement' => 'autocomplete_addressbooks'
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($version) {
|
||||
$out['defaults'] = [];
|
||||
|
||||
foreach ($this->defaults_changes as $v => $opts) {
|
||||
if (version_compare($v, $version, '>')) {
|
||||
$out['defaults'] = array_merge($out['defaults'], $opts);
|
||||
}
|
||||
}
|
||||
|
||||
$out['defaults'] = array_unique($out['defaults']);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the current configuration with the defaults
|
||||
* and copy replaced values to the new options.
|
||||
*/
|
||||
public function merge_config()
|
||||
{
|
||||
$current = $this->config;
|
||||
$this->config = [];
|
||||
|
||||
foreach ($this->replaced_config as $prop => $replacement) {
|
||||
if (isset($current[$prop])) {
|
||||
if ($prop == 'skin_path') {
|
||||
$this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]);
|
||||
}
|
||||
else if ($prop == 'multiple_identities') {
|
||||
$this->config[$replacement] = $current[$prop] ? 2 : 0;
|
||||
}
|
||||
else {
|
||||
$this->config[$replacement] = $current[$prop];
|
||||
}
|
||||
|
||||
unset($current[$prop]);
|
||||
unset($current[$replacement]);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge old *_port options into the new *_host options, where possible
|
||||
foreach (['default' => 'imap', 'smtp' => 'smtp'] as $prop => $type) {
|
||||
$old_prop = "{$prop}_port";
|
||||
$new_prop = "{$type}_host";
|
||||
if (!empty($current[$old_prop]) && !empty($this->config[$new_prop])
|
||||
&& is_string($this->config[$new_prop])
|
||||
&& !preg_match('/:[0-9]+$/', $this->config[$new_prop])
|
||||
) {
|
||||
$this->config[$new_prop] .= ':' . $current[$old_prop];
|
||||
}
|
||||
|
||||
unset($current[$old_prop]);
|
||||
}
|
||||
|
||||
foreach ($this->obsolete_config as $prop) {
|
||||
unset($current[$prop]);
|
||||
}
|
||||
|
||||
// add all ldap_public sources having global_search enabled to autocomplete_addressbooks
|
||||
if (!empty($current['ldap_public']) && is_array($current['ldap_public'])) {
|
||||
foreach ($current['ldap_public'] as $key => $ldap_public) {
|
||||
if (!empty($ldap_public['global_search'])) {
|
||||
$this->config['autocomplete_addressbooks'][] = $key;
|
||||
unset($current['ldap_public'][$key]['global_search']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->config = array_merge($this->config, $current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the local database schema with the reference schema
|
||||
* required for this version of Roundcube
|
||||
*
|
||||
* @param rcube_db $db Database object
|
||||
*
|
||||
* @return bool True if the schema is up-to-date, false if not or an error occurred
|
||||
*/
|
||||
public function db_schema_check($db)
|
||||
{
|
||||
if (!$this->configured) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// read reference schema from mysql.initial.sql
|
||||
$engine = $db->db_provider;
|
||||
$db_schema = $this->db_read_schema(INSTALL_PATH . "SQL/$engine.initial.sql", $schema_version);
|
||||
$errors = [];
|
||||
|
||||
// Just check the version
|
||||
if ($schema_version) {
|
||||
$version = rcmail_utils::db_version();
|
||||
|
||||
if (empty($version)) {
|
||||
$errors[] = "Schema version not found";
|
||||
}
|
||||
else if ($schema_version != $version) {
|
||||
$errors[] = "Schema version: {$version} (required: {$schema_version})";
|
||||
}
|
||||
|
||||
return !empty($errors) ? $errors : false;
|
||||
}
|
||||
|
||||
// check list of tables
|
||||
$existing_tables = $db->list_tables();
|
||||
|
||||
foreach ($db_schema as $table => $cols) {
|
||||
$table = $this->config['db_prefix'] . $table;
|
||||
|
||||
if (!in_array($table, $existing_tables)) {
|
||||
$errors[] = "Missing table '".$table."'";
|
||||
}
|
||||
else { // compare cols
|
||||
$db_cols = $db->list_cols($table);
|
||||
$diff = array_diff(array_keys($cols), $db_cols);
|
||||
|
||||
if (!empty($diff)) {
|
||||
$errors[] = "Missing columns in table '$table': " . implode(',', $diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($errors) ? $errors : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to read database schema from an .sql file
|
||||
*/
|
||||
private function db_read_schema($schemafile, &$version = null)
|
||||
{
|
||||
$lines = file($schemafile);
|
||||
$schema = [];
|
||||
$keywords = ['PRIMARY','KEY','INDEX','UNIQUE','CONSTRAINT','REFERENCES','FOREIGN'];
|
||||
$table_name = null;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^\s*create table ([\S]+)/i', $line, $m)) {
|
||||
$table_name = explode('.', $m[1]);
|
||||
$table_name = end($table_name);
|
||||
$table_name = preg_replace('/[`"\[\]]/', '', $table_name);
|
||||
}
|
||||
else if (preg_match('/insert into/i', $line) && preg_match('/\'roundcube-version\',\s*\'([0-9]+)\'/', $line, $m)) {
|
||||
$version = $m[1];
|
||||
}
|
||||
else if ($table_name && ($line = trim($line))) {
|
||||
if ($line == 'GO' || $line[0] == ')' || $line[strlen($line)-1] == ';') {
|
||||
$table_name = null;
|
||||
}
|
||||
else {
|
||||
$items = explode(' ', $line);
|
||||
$col = $items[0];
|
||||
$col = preg_replace('/[`"\[\]]/', '', $col);
|
||||
|
||||
if (!in_array(strtoupper($col), $keywords)) {
|
||||
$type = strtolower($items[1]);
|
||||
$type = preg_replace('/[^a-zA-Z0-9()]/', '', $type);
|
||||
|
||||
$schema[$table_name][$col] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect some file's mimetypes to test the correct behavior of fileinfo
|
||||
*/
|
||||
public function check_mime_detection()
|
||||
{
|
||||
$errors = [];
|
||||
$files = [
|
||||
'program/resources/tinymce/video.png' => 'image/png',
|
||||
'program/resources/blank.tiff' => 'image/tiff',
|
||||
'program/resources/blocked.gif' => 'image/gif',
|
||||
];
|
||||
|
||||
foreach ($files as $path => $expected) {
|
||||
$mimetype = rcube_mime::file_content_type(INSTALL_PATH . $path, basename($path));
|
||||
if ($mimetype != $expected) {
|
||||
$errors[] = [$path, $mimetype, $expected];
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the correct configuration of the 'mime_types' mapping option
|
||||
*/
|
||||
public function check_mime_extensions()
|
||||
{
|
||||
$errors = [];
|
||||
$types = [
|
||||
'application/zip' => 'zip',
|
||||
'text/css' => 'css',
|
||||
'application/pdf' => 'pdf',
|
||||
'image/gif' => 'gif',
|
||||
'image/svg+xml' => 'svg',
|
||||
];
|
||||
|
||||
foreach ($types as $mimetype => $expected) {
|
||||
$ext = rcube_mime::get_mime_extensions($mimetype);
|
||||
if (!in_array($expected, (array) $ext)) {
|
||||
$errors[] = [$mimetype, $ext, $expected];
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the last error message
|
||||
*
|
||||
* @return string Error message or null if none exists
|
||||
*/
|
||||
public function get_error()
|
||||
{
|
||||
return $this->last_error['message'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list with all imap/smtp hosts configured
|
||||
*
|
||||
* @return array Clean list with imap/smtp hosts
|
||||
*/
|
||||
public function get_hostlist($prop = 'imap_host')
|
||||
{
|
||||
$hosts = (array) $this->getprop($prop);
|
||||
$out = [];
|
||||
$imap_host = '';
|
||||
|
||||
if ($prop == 'smtp_host') {
|
||||
// Set the imap host name for the %h macro
|
||||
$default_hosts = $this->get_hostlist();
|
||||
$imap_host = !empty($default_hosts) ? $default_hosts[0] : '';
|
||||
}
|
||||
|
||||
foreach ($hosts as $key => $name) {
|
||||
if (!empty($name)) {
|
||||
if ($prop == 'smtp_host') {
|
||||
// SMTP host array uses `IMAP host => SMTP host` format
|
||||
$host = $name;
|
||||
}
|
||||
else {
|
||||
$host = is_numeric($key) ? $name : $key;
|
||||
}
|
||||
|
||||
$out[] = rcube_utils::parse_host($host, $imap_host);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HTML dropdown to select a previous version of Roundcube
|
||||
*/
|
||||
public function versions_select($attrib = [])
|
||||
{
|
||||
$select = new html_select($attrib);
|
||||
$select->add([
|
||||
'0.1-stable', '0.1.1',
|
||||
'0.2-alpha', '0.2-beta', '0.2-stable',
|
||||
'0.3-stable', '0.3.1',
|
||||
'0.4-beta', '0.4.2',
|
||||
'0.5-beta', '0.5', '0.5.1', '0.5.2', '0.5.3', '0.5.4',
|
||||
'0.6-beta', '0.6',
|
||||
'0.7-beta', '0.7', '0.7.1', '0.7.2', '0.7.3', '0.7.4',
|
||||
'0.8-beta', '0.8-rc', '0.8.0', '0.8.1', '0.8.2', '0.8.3', '0.8.4', '0.8.5', '0.8.6',
|
||||
'0.9-beta', '0.9-rc', '0.9-rc2',
|
||||
// Note: Do not add newer versions here
|
||||
]);
|
||||
|
||||
return $select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list with available subfolders of the skin directory
|
||||
*
|
||||
* @return array List of available skins
|
||||
*/
|
||||
public function list_skins()
|
||||
{
|
||||
$skins = [];
|
||||
$skindir = INSTALL_PATH . 'skins/';
|
||||
|
||||
foreach (glob($skindir . '*') as $path) {
|
||||
if (is_dir($path) && is_readable($path)) {
|
||||
$skins[] = substr($path, strlen($skindir));
|
||||
}
|
||||
}
|
||||
|
||||
return $skins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list with available subfolders of the plugins directory
|
||||
* (with their associated description in composer.json)
|
||||
*
|
||||
* @return array List of available plugins
|
||||
*/
|
||||
public function list_plugins()
|
||||
{
|
||||
$plugins = [];
|
||||
$plugin_dir = INSTALL_PATH . 'plugins/';
|
||||
$enabled = isset($this->config['plugins']) ? (array) $this->config['plugins'] : [];
|
||||
|
||||
foreach (glob($plugin_dir . '*') as $path) {
|
||||
if (!is_dir($path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_readable($path . '/composer.json')) {
|
||||
$file_json = json_decode(file_get_contents($path . '/composer.json'));
|
||||
$plugin_desc = $file_json->description ?: 'N/A';
|
||||
}
|
||||
else {
|
||||
$plugin_desc = 'N/A';
|
||||
}
|
||||
|
||||
$name = substr($path, strlen($plugin_dir));
|
||||
$plugins[] = [
|
||||
'name' => $name,
|
||||
'desc' => $plugin_desc,
|
||||
'enabled' => in_array($name, $enabled)
|
||||
];
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display OK status
|
||||
*
|
||||
* @param string $name Test name
|
||||
* @param string $message Confirm message
|
||||
*/
|
||||
public function pass($name, $message = '')
|
||||
{
|
||||
echo rcube::Q($name) . ': <span class="success">OK</span>';
|
||||
$this->_showhint($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error status and increase failure count
|
||||
*
|
||||
* @param string $name Test name
|
||||
* @param string $message Error message
|
||||
* @param string $url URL for details
|
||||
* @param bool $optional Do not count this failure
|
||||
*/
|
||||
public function fail($name, $message = '', $url = '', $optional = false)
|
||||
{
|
||||
if (!$optional) {
|
||||
$this->failures++;
|
||||
}
|
||||
|
||||
echo rcube::Q($name) . ': <span class="fail">NOT OK</span>';
|
||||
$this->_showhint($message, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error status for optional settings/features
|
||||
*
|
||||
* @param string $name Test name
|
||||
* @param string $message Error message
|
||||
* @param string $url URL for details
|
||||
*/
|
||||
public function optfail($name, $message = '', $url = '')
|
||||
{
|
||||
echo rcube::Q($name) . ': <span class="na">NOT OK</span>';
|
||||
$this->_showhint($message, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display warning status
|
||||
*
|
||||
* @param string $name Test name
|
||||
* @param string $message Warning message
|
||||
* @param string $url URL for details
|
||||
*/
|
||||
public function na($name, $message = '', $url = '')
|
||||
{
|
||||
echo rcube::Q($name) . ': <span class="na">NOT AVAILABLE</span>';
|
||||
$this->_showhint($message, $url);
|
||||
}
|
||||
|
||||
private function _showhint($message, $url = '')
|
||||
{
|
||||
$hint = rcube::Q($message);
|
||||
|
||||
if ($url) {
|
||||
$hint .= ($hint ? '; ' : '') . 'See <a href="' . rcube::Q($url) . '" target="_blank">' . rcube::Q($url) . '</a>';
|
||||
}
|
||||
|
||||
if ($hint) {
|
||||
echo '<span class="indent">(' . $hint . ')</span>';
|
||||
}
|
||||
}
|
||||
|
||||
private static function _clean_array($arr)
|
||||
{
|
||||
$out = [];
|
||||
|
||||
foreach (array_unique($arr) as $k => $val) {
|
||||
if (!empty($val)) {
|
||||
if (is_numeric($k)) {
|
||||
$out[] = $val;
|
||||
}
|
||||
else {
|
||||
$out[$k] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function _dump_var($var, $name = null)
|
||||
{
|
||||
// special values
|
||||
switch ($name) {
|
||||
case 'syslog_facility':
|
||||
$list = [
|
||||
32 => 'LOG_AUTH', 80 => 'LOG_AUTHPRIV', 72 => ' LOG_CRON',
|
||||
24 => 'LOG_DAEMON', 0 => 'LOG_KERN', 128 => 'LOG_LOCAL0',
|
||||
136 => 'LOG_LOCAL1', 144 => 'LOG_LOCAL2', 152 => 'LOG_LOCAL3',
|
||||
160 => 'LOG_LOCAL4', 168 => 'LOG_LOCAL5', 176 => 'LOG_LOCAL6',
|
||||
184 => 'LOG_LOCAL7', 48 => 'LOG_LPR', 16 => 'LOG_MAIL',
|
||||
56 => 'LOG_NEWS', 40 => 'LOG_SYSLOG', 8 => 'LOG_USER', 64 => 'LOG_UUCP'
|
||||
];
|
||||
|
||||
if (!empty($list[$var])) {
|
||||
return $list[$var];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_array($var)) {
|
||||
if (empty($var)) {
|
||||
return '[]';
|
||||
}
|
||||
// check if all keys are numeric
|
||||
$isnum = true;
|
||||
foreach (array_keys($var) as $key) {
|
||||
if (!is_numeric($key)) {
|
||||
$isnum = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isnum) {
|
||||
return '[' . implode(', ', array_map(['rcmail_install', '_dump_var'], $var)) . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return var_export($var, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database with the according schema
|
||||
*
|
||||
* @param rcube_db $db Database connection
|
||||
*
|
||||
* @return bool True on success, False on error
|
||||
*/
|
||||
public function init_db($db)
|
||||
{
|
||||
$engine = $db->db_provider;
|
||||
|
||||
// read schema file from /SQL/*
|
||||
$fname = INSTALL_PATH . "SQL/$engine.initial.sql";
|
||||
if ($sql = @file_get_contents($fname)) {
|
||||
$db->set_option('table_prefix', $this->config['db_prefix']);
|
||||
$db->exec_script($sql);
|
||||
}
|
||||
else {
|
||||
$this->fail('DB Schema', "Cannot read the schema file: $fname");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($err = $this->get_error()) {
|
||||
$this->fail('DB Schema', "Error creating database schema: $err");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update database schema
|
||||
*
|
||||
* @param string $version Version to update from
|
||||
*
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
public function update_db($version)
|
||||
{
|
||||
return rcmail_utils::db_update(INSTALL_PATH . 'SQL', 'roundcube', $version, ['quiet' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for Roundcube errors
|
||||
*/
|
||||
public function raise_error($p)
|
||||
{
|
||||
$this->last_error = $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if vendor/autoload.php was created by Roundcube and left untouched
|
||||
*
|
||||
* @param string $target_dir The target installation dir
|
||||
* @return string
|
||||
*/
|
||||
public static function vendor_dir_untouched($target_dir)
|
||||
{
|
||||
system('grep -q "generated by Roundcube" ' . escapeshellarg($target_dir . '/vendor/autoload.php') . ' 2>/dev/null', $exit_code);
|
||||
return $exit_code === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
<?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. |
|
||||
| |
|
||||
| CONTENTS: |
|
||||
| Roundcube OAuth2 utilities |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\MessageFormatter;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
/**
|
||||
* Roundcube OAuth2 utilities
|
||||
*
|
||||
* @package Webmail
|
||||
* @subpackage Utils
|
||||
*/
|
||||
class rcmail_oauth
|
||||
{
|
||||
/** @var rcmail */
|
||||
protected $rcmail;
|
||||
|
||||
/** @var array */
|
||||
protected $options = [];
|
||||
|
||||
/** @var string */
|
||||
protected $last_error = null;
|
||||
|
||||
/** @var boolean */
|
||||
protected $no_redirect = false;
|
||||
|
||||
/** @var rcmail_oauth */
|
||||
static protected $instance;
|
||||
|
||||
/**
|
||||
* Singleton factory
|
||||
*
|
||||
* @return rcmail_oauth The one and only instance
|
||||
*/
|
||||
static function get_instance($options = [])
|
||||
{
|
||||
if (!self::$instance) {
|
||||
self::$instance = new rcmail_oauth($options);
|
||||
self::$instance->init();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*
|
||||
* @param array $options Config options:
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
$this->rcmail = rcmail::get_instance();
|
||||
$this->options = (array) $options + [
|
||||
'provider' => $this->rcmail->config->get('oauth_provider'),
|
||||
'auth_uri' => $this->rcmail->config->get('oauth_auth_uri'),
|
||||
'token_uri' => $this->rcmail->config->get('oauth_token_uri'),
|
||||
'client_id' => $this->rcmail->config->get('oauth_client_id'),
|
||||
'client_secret' => $this->rcmail->config->get('oauth_client_secret'),
|
||||
'identity_uri' => $this->rcmail->config->get('oauth_identity_uri'),
|
||||
'identity_fields' => $this->rcmail->config->get('oauth_identity_fields', ['email']),
|
||||
'scope' => $this->rcmail->config->get('oauth_scope'),
|
||||
'verify_peer' => $this->rcmail->config->get('oauth_verify_peer', true),
|
||||
'auth_parameters' => $this->rcmail->config->get('oauth_auth_parameters', []),
|
||||
'login_redirect' => $this->rcmail->config->get('oauth_login_redirect', false),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
// subscribe to storage and smtp init events
|
||||
if ($this->is_enabled()) {
|
||||
$this->rcmail->plugins->register_hook('storage_init', [$this, 'storage_init']);
|
||||
$this->rcmail->plugins->register_hook('smtp_connect', [$this, 'smtp_connect']);
|
||||
$this->rcmail->plugins->register_hook('managesieve_connect', [$this, 'managesieve_connect']);
|
||||
$this->rcmail->plugins->register_hook('logout_after', [$this, 'logout_after']);
|
||||
$this->rcmail->plugins->register_hook('login_failed', [$this, 'login_failed']);
|
||||
$this->rcmail->plugins->register_hook('unauthenticated', [$this, 'unauthenticated']);
|
||||
$this->rcmail->plugins->register_hook('refresh', [$this, 'refresh']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OAuth is generally enabled in config
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_enabled()
|
||||
{
|
||||
return !empty($this->options['provider']) &&
|
||||
!empty($this->options['token_uri']) &&
|
||||
!empty($this->options['client_id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a fully qualified redirect URI for auth requests
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_redirect_uri()
|
||||
{
|
||||
$url = $this->rcmail->url([], true, true);
|
||||
|
||||
// rewrite redirect URL to not contain query parameters because some providers do not support this
|
||||
$url = preg_replace('/\?.*/', '', $url);
|
||||
|
||||
return slashify($url) . 'index.php/login/oauth';
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the last error occurred
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_last_error()
|
||||
{
|
||||
return $this->last_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode a JWT
|
||||
*
|
||||
* @param string $jwt
|
||||
* @return array Hash array with decoded body
|
||||
*/
|
||||
public function jwt_decode($jwt)
|
||||
{
|
||||
list($headb64, $bodyb64, $cryptob64) = explode('.', strtr($jwt, '-_', '+/'));
|
||||
|
||||
$header = json_decode(base64_decode($headb64), true);
|
||||
$body = json_decode(base64_decode($bodyb64), true);
|
||||
|
||||
if (isset($body['azp']) && $body['azp'] !== $this->options['client_id']) {
|
||||
throw new RuntimeException('Failed to validate JWT: invalid azp value');
|
||||
}
|
||||
else if (isset($body['aud']) && !in_array($this->options['client_id'], (array) $body['aud'])) {
|
||||
throw new RuntimeException('Failed to validate JWT: invalid aud value');
|
||||
}
|
||||
else if (!isset($body['azp']) && !isset($body['aud'])) {
|
||||
throw new RuntimeException('Failed to validate JWT: missing aud/azp value');
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login action: redirect to `oauth_auth_uri`
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function login_redirect()
|
||||
{
|
||||
if (!empty($this->options['auth_uri']) && !empty($this->options['client_id'])) {
|
||||
// create a secret string
|
||||
$_SESSION['oauth_state'] = rcube_utils::random_bytes(12);
|
||||
|
||||
// compose full oauth login uri
|
||||
$delimiter = strpos($this->options['auth_uri'], '?') > 0 ? '&' : '?';
|
||||
$query = http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->options['client_id'],
|
||||
'scope' => $this->options['scope'],
|
||||
'redirect_uri' => $this->get_redirect_uri(),
|
||||
'state' => $_SESSION['oauth_state'],
|
||||
] + (array) $this->options['auth_parameters']);
|
||||
$this->rcmail->output->redirect($this->options['auth_uri'] . $delimiter . $query); // exit
|
||||
}
|
||||
else {
|
||||
// log error about missing config options
|
||||
rcube::raise_error([
|
||||
'message' => "Missing required OAuth config options 'oauth_auth_uri', 'oauth_client_id'",
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request access token with auth code returned from oauth login
|
||||
*
|
||||
* @param string $auth_code
|
||||
* @param string $state
|
||||
*
|
||||
* @return array Authorization data as hash array with entries
|
||||
* `username` as the authentication user name
|
||||
* `authorization` as the oauth authorization string "<type> <access-token>"
|
||||
* `token` as the complete oauth response to be stored in session
|
||||
*/
|
||||
public function request_access_token($auth_code, $state = null)
|
||||
{
|
||||
$oauth_token_uri = $this->options['token_uri'];
|
||||
$oauth_client_id = $this->options['client_id'];
|
||||
$oauth_client_secret = $this->options['client_secret'];
|
||||
$oauth_identity_uri = $this->options['identity_uri'];
|
||||
|
||||
if (!empty($oauth_token_uri) && !empty($oauth_client_secret)) {
|
||||
try {
|
||||
// validate state parameter against $_SESSION['oauth_state']
|
||||
if (!empty($_SESSION['oauth_state']) && $_SESSION['oauth_state'] !== $state) {
|
||||
throw new RuntimeException('Invalid state parameter');
|
||||
}
|
||||
|
||||
// send token request to get a real access token for the given auth code
|
||||
$client = new Client([
|
||||
'timeout' => 10.0,
|
||||
'verify' => $this->options['verify_peer'],
|
||||
]);
|
||||
|
||||
$response = $client->post($oauth_token_uri, [
|
||||
'form_params' => [
|
||||
'code' => $auth_code,
|
||||
'client_id' => $oauth_client_id,
|
||||
'client_secret' => $oauth_client_secret,
|
||||
'redirect_uri' => $this->get_redirect_uri(),
|
||||
'grant_type' => 'authorization_code',
|
||||
],
|
||||
]);
|
||||
|
||||
$data = \GuzzleHttp\json_decode($response->getBody(), true);
|
||||
|
||||
// auth success
|
||||
if (!empty($data['access_token'])) {
|
||||
$username = null;
|
||||
$identity = null;
|
||||
$authorization = sprintf('%s %s', $data['token_type'], $data['access_token']);
|
||||
|
||||
// decode JWT id_token if provided
|
||||
if (!empty($data['id_token'])) {
|
||||
try {
|
||||
$identity = $this->jwt_decode($data['id_token']);
|
||||
foreach ($this->options['identity_fields'] as $field) {
|
||||
if (isset($identity[$field])) {
|
||||
$username = $identity[$field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// log error
|
||||
rcube::raise_error([
|
||||
'message' => $e->getMessage(),
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// request user identity (email)
|
||||
if (empty($username) && !empty($oauth_identity_uri)) {
|
||||
$identity_response = $client->get($oauth_identity_uri, [
|
||||
'headers' => [
|
||||
'Authorization' => $authorization,
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
$identity = \GuzzleHttp\json_decode($identity_response->getBody(), true);
|
||||
|
||||
foreach ($this->options['identity_fields'] as $field) {
|
||||
if (isset($identity[$field])) {
|
||||
$username = $identity[$field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['identity'] = $username;
|
||||
$this->mask_auth_data($data);
|
||||
|
||||
$this->rcmail->session->remove('oauth_state');
|
||||
|
||||
$this->rcmail->plugins->exec_hook('oauth_login', array_merge($data, [
|
||||
'username' => $username,
|
||||
'identity' => $identity,
|
||||
]));
|
||||
|
||||
// remove some data we don't want to store in session
|
||||
unset($data['id_token']);
|
||||
|
||||
// return auth data
|
||||
return [
|
||||
'username' => $username,
|
||||
'authorization' => $authorization,
|
||||
'token' => $data,
|
||||
];
|
||||
}
|
||||
else {
|
||||
throw new Exception('Unexpected response from OAuth service');
|
||||
}
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->last_error = "OAuth token request failed: " . $e->getMessage();
|
||||
$this->no_redirect = true;
|
||||
$formatter = new MessageFormatter();
|
||||
|
||||
rcube::raise_error([
|
||||
'message' => $this->last_error . '; ' . $formatter->format($e->getRequest(), $e->getResponse()),
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->last_error = "OAuth token request failed: " . $e->getMessage();
|
||||
$this->no_redirect = true;
|
||||
|
||||
rcube::raise_error([
|
||||
'message' => $this->last_error,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->last_error = "Missing required OAuth config options 'oauth_token_uri', 'oauth_client_id', 'oauth_client_secret'";
|
||||
|
||||
rcube::raise_error([
|
||||
'message' => $this->last_error,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new access token using the refresh_token grant type
|
||||
*
|
||||
* If successful, this will update the `oauth_token` entry in
|
||||
* session data.
|
||||
*
|
||||
* @param array $token
|
||||
*
|
||||
* @return array Updated authorization data
|
||||
*/
|
||||
public function refresh_access_token(array $token)
|
||||
{
|
||||
$oauth_token_uri = $this->options['token_uri'];
|
||||
$oauth_client_id = $this->options['client_id'];
|
||||
$oauth_client_secret = $this->options['client_secret'];
|
||||
|
||||
// send token request to get a real access token for the given auth code
|
||||
try {
|
||||
$client = new Client([
|
||||
'timeout' => 10.0,
|
||||
'verify' => $this->options['verify_peer'],
|
||||
]);
|
||||
$response = $client->post($oauth_token_uri, [
|
||||
'form_params' => [
|
||||
'client_id' => $oauth_client_id,
|
||||
'client_secret' => $oauth_client_secret,
|
||||
'refresh_token' => $this->rcmail->decrypt($token['refresh_token']),
|
||||
'grant_type' => 'refresh_token',
|
||||
],
|
||||
]);
|
||||
$data = \GuzzleHttp\json_decode($response->getBody(), true);
|
||||
|
||||
// auth success
|
||||
if (!empty($data['access_token'])) {
|
||||
// update access token stored as password
|
||||
$authorization = sprintf('%s %s', $data['token_type'], $data['access_token']);
|
||||
$_SESSION['password'] = $this->rcmail->encrypt($authorization);
|
||||
|
||||
$this->mask_auth_data($data);
|
||||
|
||||
// update session data
|
||||
$_SESSION['oauth_token'] = array_merge($token, $data);
|
||||
|
||||
$this->rcmail->plugins->exec_hook('oauth_refresh_token', $data);
|
||||
|
||||
return [
|
||||
'token' => $data,
|
||||
'authorization' => $authorization,
|
||||
];
|
||||
}
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->last_error = "OAuth refresh token request failed: " . $e->getMessage();
|
||||
$formatter = new MessageFormatter();
|
||||
rcube::raise_error([
|
||||
'message' => $this->last_error . '; ' . $formatter->format($e->getRequest(), $e->getResponse()),
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
|
||||
// refrehsing token failed, mark session as expired
|
||||
if ($e->getCode() >= 400 && $e->getCode() < 500) {
|
||||
$this->rcmail->kill_session();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->last_error = "OAuth refresh token request failed: " . $e->getMessage();
|
||||
rcube::raise_error([
|
||||
'message' => $this->last_error,
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
], true, false
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify some properties of the received auth response
|
||||
*
|
||||
* @param array $token
|
||||
* @return void
|
||||
*/
|
||||
protected function mask_auth_data(&$data)
|
||||
{
|
||||
// compute absolute token expiration date
|
||||
$data['expires'] = time() + $data['expires_in'] - 10;
|
||||
|
||||
// encrypt refresh token if provided
|
||||
if (isset($data['refresh_token'])) {
|
||||
$data['refresh_token'] = $this->rcmail->encrypt($data['refresh_token']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given access token data if still valid
|
||||
*
|
||||
* ... and attempt to refresh if possible.
|
||||
*
|
||||
* @param array $token
|
||||
* @return boolean
|
||||
*/
|
||||
protected function check_token_validity($token)
|
||||
{
|
||||
if ($token['expires'] < time() && isset($token['refresh_token']) && empty($this->last_error)) {
|
||||
return $this->refresh_access_token($token) !== false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'storage_init' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function storage_init($options)
|
||||
{
|
||||
if (isset($_SESSION['oauth_token']) && $options['driver'] === 'imap') {
|
||||
// check token validity
|
||||
if ($this->check_token_validity($_SESSION['oauth_token'])) {
|
||||
$options['password'] = $this->rcmail->decrypt($_SESSION['password']);
|
||||
}
|
||||
|
||||
// enforce XOAUTH2 authorization type
|
||||
$options['auth_type'] = 'XOAUTH2';
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'smtp_connect' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function smtp_connect($options)
|
||||
{
|
||||
if (isset($_SESSION['oauth_token'])) {
|
||||
// check token validity
|
||||
$this->check_token_validity($_SESSION['oauth_token']);
|
||||
|
||||
// enforce XOAUTH2 authorization type
|
||||
$options['smtp_user'] = '%u';
|
||||
$options['smtp_pass'] = '%p';
|
||||
$options['smtp_auth_type'] = 'XOAUTH2';
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'managesieve_connect' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function managesieve_connect($options)
|
||||
{
|
||||
if (isset($_SESSION['oauth_token'])) {
|
||||
// check token validity
|
||||
$this->check_token_validity($_SESSION['oauth_token']);
|
||||
|
||||
// enforce XOAUTH2 authorization type
|
||||
$options['auth_type'] = 'XOAUTH2';
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'logout_after' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function logout_after($options)
|
||||
{
|
||||
$this->no_redirect = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'login_failed' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function login_failed($options)
|
||||
{
|
||||
// no redirect on imap login failures
|
||||
$this->no_redirect = true;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for 'unauthenticated' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function unauthenticated($options)
|
||||
{
|
||||
if (
|
||||
$this->options['login_redirect']
|
||||
&& !$this->rcmail->output->ajax_call
|
||||
&& !$this->no_redirect
|
||||
&& empty($options['error'])
|
||||
&& $options['http_code'] === 200
|
||||
) {
|
||||
$this->login_redirect();
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for 'refresh' hook
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function refresh($options)
|
||||
{
|
||||
if (isset($_SESSION['oauth_token'])) {
|
||||
$this->check_token_validity($_SESSION['oauth_token']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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. |
|
||||
| |
|
||||
| CONTENTS: |
|
||||
| Abstract class for output generation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for output generation
|
||||
*
|
||||
* @package Webmail
|
||||
* @subpackage View
|
||||
*/
|
||||
abstract class rcmail_output extends rcube_output
|
||||
{
|
||||
const JS_OBJECT_NAME = 'rcmail';
|
||||
const BLANK_GIF = 'R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7';
|
||||
|
||||
public $type = 'html';
|
||||
public $ajax_call = false;
|
||||
public $framed = false;
|
||||
|
||||
protected $pagetitle = '';
|
||||
protected $object_handlers = [];
|
||||
protected $devel_mode = false;
|
||||
|
||||
|
||||
/**
|
||||
* Object constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->devel_mode = (bool) $this->config->get('devel_mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for page title
|
||||
*
|
||||
* @param string $title Page title
|
||||
*/
|
||||
public function set_pagetitle($title)
|
||||
{
|
||||
$this->pagetitle = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the current skin path property
|
||||
*/
|
||||
public function get_skin_path()
|
||||
{
|
||||
return $this->config->get('skin_path');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored env variables and commands
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
$this->object_handlers = [];
|
||||
$this->pagetitle = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a client method
|
||||
*
|
||||
* @param string $cmd Method to call
|
||||
* @param mixed ...$args Method arguments
|
||||
*/
|
||||
abstract function command($cmd, ...$args);
|
||||
|
||||
/**
|
||||
* Add a localized label(s) to the client environment
|
||||
*
|
||||
* @param mixed ...$args Labels (an array of strings, or many string arguments)
|
||||
*/
|
||||
abstract function add_label(...$args);
|
||||
|
||||
/**
|
||||
* Register a template object handler
|
||||
*
|
||||
* @param string $name Object name
|
||||
* @param callable $func Function name to call
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_handler($name, $func)
|
||||
{
|
||||
$this->object_handlers[$name] = $func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a list of template object handlers
|
||||
*
|
||||
* @param array $handlers Hash array with object=>handler pairs
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_handlers($handlers)
|
||||
{
|
||||
$this->object_handlers = array_merge($this->object_handlers, $handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for header() function, so it can be replaced for automated tests
|
||||
*
|
||||
* @param string $header The header string
|
||||
* @param bool $replace Replace previously set header?
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function header($header, $replace = true)
|
||||
{
|
||||
header($header, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to send output to the browser and exit
|
||||
*
|
||||
* @param string $body The output body
|
||||
* @param array $headers Headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendExit($body = '', $headers = [])
|
||||
{
|
||||
foreach ($headers as $header) {
|
||||
header($header);
|
||||
}
|
||||
|
||||
print $body;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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. |
|
||||
| |
|
||||
| CONTENTS: |
|
||||
| Abstract class for output generation |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for output generation
|
||||
*
|
||||
* @package Webmail
|
||||
* @subpackage View
|
||||
*/
|
||||
class rcmail_output_cli extends rcmail_output
|
||||
{
|
||||
public $type = 'cli';
|
||||
|
||||
|
||||
/**
|
||||
* Call a client method
|
||||
*
|
||||
* @see rcube_output::command()
|
||||
*/
|
||||
function command($cmd, ...$args)
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a localized label to the client environment
|
||||
*
|
||||
* @see rcube_output::add_label()
|
||||
*/
|
||||
function add_label(...$args)
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke display_message command
|
||||
*
|
||||
* @see rcube_output::show_message()
|
||||
*/
|
||||
function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0)
|
||||
{
|
||||
if ($this->app->text_exists($message)) {
|
||||
$message = $this->app->gettext(['name' => $message, 'vars' => $vars]);
|
||||
}
|
||||
|
||||
printf("[%s] %s\n", strtoupper($type), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to a certain url.
|
||||
*
|
||||
* @see rcube_output::redirect()
|
||||
*/
|
||||
function redirect($p = [], $delay = 1)
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Send output to the client.
|
||||
*/
|
||||
function send()
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user