Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2518367
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
233 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php
index 451edad4c..d1ec4f6c5 100644
--- a/plugins/acl/acl.php
+++ b/plugins/acl/acl.php
@@ -1,865 +1,865 @@
<?php
/**
* Folders Access Control Lists Management (RFC4314, RFC2086)
*
* @author Aleksander Machniak <alec@alec.pl>
*
* Copyright (C) Kolab Systems AG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
class acl extends rcube_plugin
{
public $task = 'settings';
private $rc;
private $supported = null;
private $mbox;
private $ldap;
private $specials = ['anyone', 'anonymous'];
/**
* Plugin initialization
*/
function init()
{
$this->rc = rcmail::get_instance();
// Register hooks
$this->add_hook('folder_form', [$this, 'folder_form']);
// Plugin actions
$this->register_action('plugin.acl', [$this, 'acl_actions']);
$this->register_action('plugin.acl-autocomplete', [$this, 'acl_autocomplete']);
}
/**
* Handler for plugin actions (AJAX)
*/
function acl_actions()
{
$action = trim(rcube_utils::get_input_value('_act', rcube_utils::INPUT_GPC));
// Connect to IMAP
$this->rc->storage_init();
// Load localization and configuration
$this->add_texts('localization/');
$this->load_config();
if ($action == 'save') {
$this->action_save();
}
else if ($action == 'delete') {
$this->action_delete();
}
else if ($action == 'list') {
$this->action_list();
}
// Only AJAX actions
$this->rc->output->send();
}
/**
* Handler for user login autocomplete request
*/
function acl_autocomplete()
{
$this->load_config();
$search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true);
$reqid = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC);
$users = [];
$keys = [];
if ($this->init_ldap()) {
$max = (int) $this->rc->config->get('autocomplete_max', 15);
$mode = (int) $this->rc->config->get('addressbook_search_mode');
$this->ldap->set_pagesize($max);
$result = $this->ldap->search('*', $search, $mode);
foreach ($result->records as $record) {
$user = $record['uid'];
if (is_array($user) && !empty($user)) {
$user = array_filter($user);
$user = $user[0];
}
if ($user) {
$display = rcube_addressbook::compose_search_name($record);
$user = ['name' => $user, 'display' => $display];
$users[] = $user;
$keys[] = $display ?: $user['name'];
}
}
if ($this->rc->config->get('acl_groups')) {
$prefix = $this->rc->config->get('acl_group_prefix');
$group_field = $this->rc->config->get('acl_group_field', 'name');
$result = $this->ldap->list_groups($search, $mode);
foreach ($result as $record) {
$group = $record['name'];
$group_id = is_array($record[$group_field]) ? $record[$group_field][0] : $record[$group_field];
if ($group) {
$users[] = ['name' => ($prefix ?: '') . $group_id, 'display' => $group, 'type' => 'group'];
$keys[] = $group;
}
}
}
}
if (count($users)) {
// sort users index
asort($keys, SORT_LOCALE_STRING);
// re-sort users according to index
foreach ($keys as $idx => $val) {
$keys[$idx] = $users[$idx];
}
$users = array_values($keys);
}
$this->rc->output->command('ksearch_query_results', $users, $search, $reqid);
$this->rc->output->send();
}
/**
* Handler for 'folder_form' hook
*
* @param array $args Hook arguments array (form data)
*
* @return array Hook arguments array
*/
function folder_form($args)
{
$mbox_imap = $args['options']['name'];
$myrights = $args['options']['rights'];
// Edited folder name (empty in create-folder mode)
if (!strlen($mbox_imap)) {
return $args;
}
/*
// Do nothing on protected folders (?)
if (!empty($args['options']['protected'])) {
return $args;
}
*/
// Get MYRIGHTS
if (empty($myrights)) {
return $args;
}
// Load localization and include scripts
$this->load_config();
$this->specials = $this->rc->config->get('acl_specials', $this->specials);
$this->add_texts('localization/', ['deleteconfirm', 'norights',
'nouser', 'deleting', 'saving', 'newuser', 'editperms']);
$this->rc->output->add_label('save', 'cancel');
$this->include_script('acl.js');
$this->rc->output->include_script('list.js');
$this->include_stylesheet($this->local_skin_path() . '/acl.css');
// add Info fieldset if it doesn't exist
if (!isset($args['form']['props']['fieldsets']['info']))
$args['form']['props']['fieldsets']['info'] = [
'name' => $this->rc->gettext('info'),
'content' => []
];
// Display folder rights to 'Info' fieldset
$args['form']['props']['fieldsets']['info']['content']['myrights'] = [
'label' => rcube::Q($this->gettext('myrights')),
'value' => $this->acl2text($myrights)
];
// Return if not folder admin
if (!in_array('a', $myrights)) {
return $args;
}
// The 'Sharing' tab
$this->mbox = $mbox_imap;
$this->rc->output->set_env('acl_users_source', (bool) $this->rc->config->get('acl_users_source'));
$this->rc->output->set_env('mailbox', $mbox_imap);
$this->rc->output->add_handlers([
'acltable' => [$this, 'templ_table'],
'acluser' => [$this, 'templ_user'],
'aclrights' => [$this, 'templ_rights'],
]);
$this->rc->output->set_env('autocomplete_max', (int) $this->rc->config->get('autocomplete_max', 15));
$this->rc->output->set_env('autocomplete_min_length', $this->rc->config->get('autocomplete_min_length'));
$this->rc->output->add_label('autocompletechars', 'autocompletemore');
$args['form']['sharing'] = [
'name' => rcube::Q($this->gettext('sharing')),
'content' => $this->rc->output->parse('acl.table', false, false),
];
return $args;
}
/**
* Creates ACL rights table
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_table($attrib)
{
if (empty($attrib['id'])) {
$attrib['id'] = 'acl-table';
}
$out = $this->list_rights($attrib);
$this->rc->output->add_gui_object('acltable', $attrib['id']);
return $out;
}
/**
* Creates ACL rights form (rights list part)
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_rights($attrib)
{
// Get supported rights
$supported = $this->rights_supported();
// give plugins the opportunity to adjust this list
$data = $this->rc->plugins->exec_hook('acl_rights_supported',
['rights' => $supported, 'folder' => $this->mbox, 'labels' => []]
);
$supported = $data['rights'];
// depending on server capability either use 'te' or 'd' for deleting msgs
$deleteright = implode(array_intersect(str_split('ted'), $supported));
$out = '';
$ul = '';
$input = new html_checkbox();
// Advanced rights
$attrib['id'] = 'advancedrights';
foreach ($supported as $key => $val) {
$id = "acl$val";
$ul .= html::tag('li', null,
$input->show('', ['name' => "acl[$val]", 'value' => $val, 'id' => $id])
. html::label(['for' => $id, 'title' => $this->gettext('longacl'.$val)], $this->gettext('acl'.$val))
);
}
$out = html::tag('ul', $attrib, $ul, html::$common_attrib);
// Simple rights
$ul = '';
$attrib['id'] = 'simplerights';
$items = [
'read' => 'lrs',
'write' => 'wi',
'delete' => $deleteright,
'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
];
// give plugins the opportunity to adjust this list
$data = $this->rc->plugins->exec_hook('acl_rights_simple',
['rights' => $items, 'folder' => $this->mbox, 'labels' => [], 'titles' => []]
);
foreach ($data['rights'] as $key => $val) {
$id = "acl$key";
$title = !empty($data['titles'][$key]) ? $data['titles'][$key] : $this->gettext('longacl'.$key);
$label = !empty($data['labels'][$key]) ? $data['labels'][$key] : $this->gettext('acl'.$key);
$ul .= html::tag('li', null,
$input->show('', ['name' => "acl[$val]", 'value' => $val, 'id' => $id])
. html::label(['for' => $id, 'title' => $title], $label)
);
}
$out .= "\n" . html::tag('ul', $attrib, $ul, html::$common_attrib);
$this->rc->output->set_env('acl_items', $data['rights']);
return $out;
}
/**
* Creates ACL rights form (user part)
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
function templ_user($attrib)
{
// Create username input
$class = !empty($attrib['class']) ? $attrib['class'] : '';
$attrib['name'] = 'acluser';
$attrib['class'] = 'form-control';
$textfield = new html_inputfield($attrib);
$label = html::label(['for' => $attrib['id'], 'class' => 'input-group-text'], $this->gettext('username'));
$fields['user'] = html::div('input-group',
html::span('input-group-prepend', $label) . ' ' . $textfield->show()
);
// Add special entries
if (!empty($this->specials)) {
foreach ($this->specials as $key) {
$fields[$key] = html::label(['for' => 'id' . $key], $this->gettext($key));
}
}
$this->rc->output->set_env('acl_specials', $this->specials);
// Create list with radio buttons
if (count($fields) > 1) {
$ul = '';
$radio = new html_radiobutton(['name' => 'usertype']);
foreach ($fields as $key => $val) {
$radio = $radio->show($key == 'user' ? 'user' : '', ['value' => $key, 'id' => 'id' . $key]);
$ul .= html::tag('li', null, $radio . $val);
}
$out = html::tag('ul', ['id' => 'usertype', 'class' => $class], $ul, html::$common_attrib);
}
// Display text input alone
else {
$out = html::div($class, $fields['user']);
}
return $out;
}
/**
* Creates ACL rights table
*
* @param array $attrib Template object attributes
*
* @return string HTML Content
*/
private function list_rights($attrib = [])
{
// Get ACL for the folder
$acl = $this->rc->storage->get_acl($this->mbox);
if (!is_array($acl)) {
$acl = [];
}
// Keep special entries (anyone/anonymous) on top of the list
if (!empty($this->specials) && !empty($acl)) {
foreach ($this->specials as $key) {
if (isset($acl[$key])) {
$acl_special[$key] = $acl[$key];
unset($acl[$key]);
}
}
}
// Sort the list by username
uksort($acl, 'strnatcasecmp');
if (!empty($acl_special)) {
$acl = array_merge($acl_special, $acl);
}
// Get supported rights and build column names
$supported = $this->rights_supported();
// give plugins the opportunity to adjust this list
$data = $this->rc->plugins->exec_hook('acl_rights_supported',
['rights' => $supported, 'folder' => $this->mbox, 'labels' => []]
);
$supported = $data['rights'];
// depending on server capability either use 'te' or 'd' for deleting msgs
$deleteright = implode(array_intersect(str_split('ted'), $supported));
// Use advanced or simple (grouped) rights
$advanced = $this->rc->config->get('acl_advanced_mode');
if ($advanced) {
$items = [];
foreach ($supported as $sup) {
$items[$sup] = $sup;
}
}
else {
$items = [
'read' => 'lrs',
'write' => 'wi',
'delete' => $deleteright,
'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
];
// give plugins the opportunity to adjust this list
$data = $this->rc->plugins->exec_hook('acl_rights_simple',
['rights' => $items, 'folder' => $this->mbox, 'labels' => []]
);
$items = $data['rights'];
}
// Create the table
$attrib['noheader'] = true;
$table = new html_table($attrib);
$self = $this->rc->get_user_name();
$js_table = [];
// Create table header
$table->add_header('user', $this->gettext('identifier'));
foreach (array_keys($items) as $key) {
$label = !empty($data['labels'][$key]) ? $data['labels'][$key] : $this->gettext('shortacl' . $key);
$table->add_header(['class' => 'acl' . $key, 'title' => $label], $label);
}
foreach ($acl as $user => $rights) {
if ($user === $self) {
continue;
}
// filter out virtual rights (c or d) the server may return
$userrights = array_intersect($rights, $supported);
$userid = rcube_utils::html_identifier($user);
$title = null;
if (!empty($this->specials) && in_array($user, $this->specials)) {
$username = $this->gettext($user);
}
else {
$username = $this->resolve_acl_identifier($user, $title);
}
$table->add_row(['id' => 'rcmrow' . $userid, 'data-userid' => $user]);
$table->add(['class' => 'user text-nowrap', 'title' => $title],
html::a(['id' => 'rcmlinkrow' . $userid], rcube::Q($username))
);
foreach ($items as $key => $right) {
$in = $this->acl_compare($userrights, $right);
switch ($in) {
case 2: $class = 'enabled'; break;
case 1: $class = 'partial'; break;
default: $class = 'disabled'; break;
}
$table->add('acl' . $key . ' ' . $class, '<span></span>');
}
$js_table[$userid] = implode($userrights);
}
$this->rc->output->set_env('acl', $js_table);
$this->rc->output->set_env('acl_advanced', $advanced);
$out = $table->show();
return $out;
}
/**
* Handler for ACL update/create action
*/
private function action_save()
{
$mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); // UTF7-IMAP
$user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST));
$acl = trim(rcube_utils::get_input_value('_acl', rcube_utils::INPUT_POST));
$oldid = trim(rcube_utils::get_input_value('_old', rcube_utils::INPUT_POST));
$acl = array_intersect(str_split($acl), $this->rights_supported());
$users = $oldid ? [$user] : explode(',', $user);
$result = 0;
$self = $this->rc->get_user_name();
foreach ($users as $user) {
$user = trim($user);
$username = '';
$prefix = $this->rc->config->get('acl_groups') ? $this->rc->config->get('acl_group_prefix') : '';
if ($prefix && strpos($user, $prefix) === 0) {
$username = $user;
}
else if (!empty($this->specials) && in_array($user, $this->specials)) {
$username = $this->gettext($user);
}
else if (!empty($user)) {
if (!strpos($user, '@') && ($realm = $this->get_realm())) {
$user .= '@' . rcube_utils::idn_to_ascii(preg_replace('/^@/', '', $realm));
}
// Make sure it's valid email address to prevent from "disappearing folder"
// issue in Cyrus IMAP e.g. when the acl user identifier contains spaces inside.
if (strpos($user, '@') && !rcube_utils::check_email($user, false)) {
$user = null;
}
$username = $user;
}
if (!$acl || !$user || !strlen($mbox)) {
continue;
}
$user = $this->mod_login($user);
$username = $this->mod_login($username);
if ($user != $self && $username != $self) {
if ($this->rc->storage->set_acl($mbox, $user, $acl)) {
$display = $this->resolve_acl_identifier($username, $title);
$this->rc->output->command('acl_update', [
'id' => rcube_utils::html_identifier($user),
'username' => $username,
'title' => $title,
'display' => $display,
'acl' => implode($acl),
'old' => $oldid
]);
$result++;
}
}
}
if ($result) {
$this->rc->output->show_message($oldid ? 'acl.updatesuccess' : 'acl.createsuccess', 'confirmation');
}
else {
$this->rc->output->show_message($oldid ? 'acl.updateerror' : 'acl.createerror', 'error');
}
}
/**
* Handler for ACL delete action
*/
private function action_delete()
{
$mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST, true)); //UTF7-IMAP
$user = trim(rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST));
$user = explode(',', $user);
foreach ($user as $u) {
$u = trim($u);
if ($this->rc->storage->delete_acl($mbox, $u)) {
$this->rc->output->command('acl_remove_row', rcube_utils::html_identifier($u));
}
else {
$error = true;
}
}
if (empty($error)) {
$this->rc->output->show_message('acl.deletesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('acl.deleteerror', 'error');
}
}
/**
* Handler for ACL list update action (with display mode change)
*/
private function action_list()
{
if (in_array('acl_advanced_mode', (array)$this->rc->config->get('dont_override'))) {
return;
}
$this->mbox = trim(rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP
$advanced = trim(rcube_utils::get_input_value('_mode', rcube_utils::INPUT_GPC));
$advanced = $advanced == 'advanced';
// Save state in user preferences
$this->rc->user->save_prefs(['acl_advanced_mode' => $advanced]);
$out = $this->list_rights();
- $out = preg_replace(array('/^<table[^>]+>/', '/<\/table>$/'), '', $out);
+ $out = preg_replace(['/^<table[^>]+>/', '/<\/table>$/'], '', $out);
$this->rc->output->command('acl_list_update', $out);
}
/**
* Creates <UL> list with descriptive access rights
*
* @param array $rights MYRIGHTS result
*
* @return string HTML content
*/
function acl2text($rights)
{
if (empty($rights)) {
return '';
}
$supported = $this->rights_supported();
$list = [];
$attrib = [
'name' => 'rcmyrights',
'style' => 'margin:0; padding:0 15px;',
];
foreach ($supported as $right) {
if (in_array($right, $rights)) {
$list[] = html::tag('li', null, rcube::Q($this->gettext('acl' . $right)));
}
}
if (count($list) == count($supported)) {
return rcube::Q($this->gettext('aclfull'));
}
return html::tag('ul', $attrib, implode("\n", $list));
}
/**
* Compares two ACLs (according to supported rights)
*
* @param array $acl1 ACL rights array (or string)
* @param array $acl2 ACL rights array (or string)
*
* @param int Comparison result, 2 - full match, 1 - partial match, 0 - no match
*/
function acl_compare($acl1, $acl2)
{
if (!is_array($acl1)) $acl1 = str_split($acl1);
if (!is_array($acl2)) $acl2 = str_split($acl2);
$rights = $this->rights_supported();
$acl1 = array_intersect($acl1, $rights);
$acl2 = array_intersect($acl2, $rights);
$res = array_intersect($acl1, $acl2);
$cnt1 = count($res);
$cnt2 = count($acl2);
if ($cnt1 == $cnt2) {
return 2;
}
if ($cnt1) {
return 1;
}
return 0;
}
/**
* Get list of supported access rights (according to RIGHTS capability)
*
* @return array List of supported access rights abbreviations
*/
function rights_supported()
{
if ($this->supported !== null) {
return $this->supported;
}
$capa = $this->rc->storage->get_capability('RIGHTS');
if (is_array($capa) && !empty($capa)) {
$rights = strtolower($capa[0]);
}
else {
$rights = 'cd';
}
return $this->supported = str_split('lrswi' . $rights . 'pa');
}
/**
* Username realm detection.
*
* @return string Username realm (domain)
*/
private function get_realm()
{
// When user enters a username without domain part, realm
// allows to add it to the username (and display correct username in the table)
if (isset($_SESSION['acl_username_realm'])) {
return $_SESSION['acl_username_realm'];
}
$self = $this->rc->get_user_name();
// find realm in username of logged user (?)
list($name, $domain) = rcube_utils::explode('@', $self);
// Use (always existent) ACL entry on the INBOX for the user to determine
// whether or not the user ID in ACL entries need to be qualified and how
// they would need to be qualified.
if (empty($domain)) {
$acl = $this->rc->storage->get_acl('INBOX');
if (is_array($acl)) {
$regexp = '/^' . preg_quote($self, '/') . '@(.*)$/';
foreach (array_keys($acl) as $name) {
if (preg_match($regexp, $name, $matches)) {
$domain = $matches[1];
break;
}
}
}
}
return $_SESSION['acl_username_realm'] = $domain;
}
/**
* Initializes autocomplete LDAP backend
*/
protected function init_ldap()
{
if ($this->ldap) {
return $this->ldap->ready;
}
// get LDAP config
$config = $this->rc->config->get('acl_users_source');
if (empty($config)) {
return false;
}
// not an array, use configured ldap_public source
if (!is_array($config)) {
$ldap_config = (array) $this->rc->config->get('ldap_public');
$config = $ldap_config[$config];
}
$uid_field = $this->rc->config->get('acl_users_field', 'mail');
$filter = $this->rc->config->get('acl_users_filter');
if (empty($uid_field) || empty($config)) {
return false;
}
// get name attribute
if (!empty($config['fieldmap'])) {
$name_field = $config['fieldmap']['name'];
}
// ... no fieldmap, use the old method
if (empty($name_field)) {
$name_field = $config['name_field'];
}
// add UID field to fieldmap, so it will be returned in a record with name
$config['fieldmap']['name'] = $name_field;
$config['fieldmap']['uid'] = $uid_field;
// search in UID and name fields
// $name_field can be in a form of <field>:<modifier> (#1490591)
$name_field = preg_replace('/:.*$/', '', $name_field);
$search = array_unique([$name_field, $uid_field]);
$config['search_fields'] = $search;
$config['required_fields'] = [$uid_field];
// set search filter
if ($filter) {
$config['filter'] = $filter;
}
// disable vlv
$config['vlv'] = false;
// Initialize LDAP connection
$this->ldap = new rcube_ldap(
$config,
$this->rc->config->get('ldap_debug'),
$this->rc->config->mail_domain($_SESSION['imap_host'])
);
return $this->ldap->ready;
}
/**
* Modify user login according to 'login_lc' setting
*/
protected function mod_login($user)
{
$login_lc = $this->rc->config->get('login_lc');
if ($login_lc === true || $login_lc == 2) {
$user = mb_strtolower($user);
}
// lowercase domain name
else if ($login_lc && strpos($user, '@')) {
list($local, $domain) = explode('@', $user);
$user = $local . '@' . mb_strtolower($domain);
}
return $user;
}
/**
* Resolve acl identifier to user/group name
*/
protected function resolve_acl_identifier($id, &$title = null)
{
if ($this->init_ldap()) {
$groups = $this->rc->config->get('acl_groups');
$prefix = $this->rc->config->get('acl_group_prefix');
$group_field = $this->rc->config->get('acl_group_field', 'name');
// Unfortunately this works only if group_field=name,
// list_groups() allows searching by group name only
if ($groups && $prefix && $group_field === 'name' && strpos($id, $prefix) === 0) {
$gid = substr($id, strlen($prefix));
$result = $this->ldap->list_groups($gid, rcube_addressbook::SEARCH_STRICT);
if (count($result) === 1 && ($record = $result[0])) {
if (isset($record[$group_field]) && $record[$group_field] === $gid) {
$display = $record['name'];
if ($display != $gid) {
$title = sprintf('%s (%s)', $display, $gid);
}
return $display;
}
}
return $id;
}
$this->ldap->set_pagesize('2');
// Note: 'uid' works here because we overwrite fieldmap in init_ldap() above
$result = $this->ldap->search('uid', $id, rcube_addressbook::SEARCH_STRICT);
if ($result->count === 1 && ($record = $result->first())) {
if ($record['uid'] === $id) {
$title = rcube_addressbook::compose_search_name($record);
$display = rcube_addressbook::compose_list_name($record);
return $display;
}
}
}
return $id;
}
}
diff --git a/plugins/additional_message_headers/additional_message_headers.php b/plugins/additional_message_headers/additional_message_headers.php
index 1861aa319..b20f9bc0d 100644
--- a/plugins/additional_message_headers/additional_message_headers.php
+++ b/plugins/additional_message_headers/additional_message_headers.php
@@ -1,47 +1,47 @@
<?php
/**
* Additional Message Headers
*
* Very simple plugin which will add additional headers
* to or remove them from outgoing messages.
*
* Enable the plugin in config.inc.php and add your desired headers:
- * $config['additional_message_headers'] = array('User-Agent' => 'My-Very-Own-Webmail');
+ * $config['additional_message_headers'] = ['User-Agent' => 'My-Very-Own-Webmail'];
*
* @author Ziba Scott
* @website http://roundcube.net
*/
class additional_message_headers extends rcube_plugin
{
/**
* Plugin initialization
*/
function init()
{
$this->add_hook('message_before_send', [$this, 'message_headers']);
}
/**
* 'message_before_send' hook handler
*
* @param array $args Hook arguments
*
* @return array Modified hook arguments
*/
function message_headers($args)
{
$this->load_config();
$rcube = rcube::get_instance();
// additional email headers
$additional_headers = $rcube->config->get('additional_message_headers', []);
if (!empty($additional_headers)) {
$args['message']->headers($additional_headers, true);
}
return $args;
}
}
diff --git a/plugins/attachment_reminder/localization/en_US.inc b/plugins/attachment_reminder/localization/en_US.inc
index b1fdc9e37..17e775968 100644
--- a/plugins/attachment_reminder/localization/en_US.inc
+++ b/plugins/attachment_reminder/localization/en_US.inc
@@ -1,20 +1,19 @@
<?php
/*
+-----------------------------------------------------------------------+
| Localization file of the Roundcube Webmail Archive plugin |
| 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. |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-attachment_reminder/
*/
-$messages = array();
$messages['missingattachment'] = "Missing attachment?";
$messages['forgotattachment'] = "Did you forget to attach a file?";
$messages['reminderoption'] = "Remind about forgotten attachments";
$messages['keywords'] = "attachment,file,attach,attached,attaching,enclosed,CV,cover letter";
diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php
index 504432eef..19f60bedd 100644
--- a/plugins/enigma/enigma.php
+++ b/plugins/enigma/enigma.php
@@ -1,583 +1,583 @@
<?php
/**
+-------------------------------------------------------------------------+
| Enigma Plugin for Roundcube |
| |
| 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. |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/**
* This class contains only hooks and action handlers.
* Most plugin logic is placed in enigma_engine and enigma_ui classes.
*/
class enigma extends rcube_plugin
{
public $task = 'mail|settings|cli';
public $rc;
public $engine;
public $ui;
private $env_loaded = false;
/**
* Plugin initialization.
*/
function init()
{
$this->rc = rcube::get_instance();
if ($this->rc->task == 'mail') {
// message parse/display hooks
$this->add_hook('message_part_structure', [$this, 'part_structure']);
$this->add_hook('message_part_body', [$this, 'part_body']);
$this->add_hook('message_body_prefix', [$this, 'status_message']);
$this->register_action('plugin.enigmaimport', [$this, 'import_file']);
$this->register_action('plugin.enigmakeys', [$this, 'preferences_ui']);
// load the Enigma plugin configuration
$this->load_config();
$enabled = $this->rc->config->get('enigma_encryption', true);
// message displaying
if ($this->rc->action == 'show' || $this->rc->action == 'preview' || $this->rc->action == 'print') {
$this->add_hook('message_load', [$this, 'message_load']);
$this->add_hook('template_object_messagebody', [$this, 'message_output']);
}
// message composing
else if ($enabled && $this->rc->action == 'compose') {
$this->add_hook('message_compose_body', [$this, 'message_compose']);
$this->load_ui();
$this->ui->init();
}
// message sending (and draft storing)
else if ($enabled && $this->rc->action == 'send') {
$this->add_hook('message_ready', [$this, 'message_ready']);
}
$this->password_handler();
}
else if ($this->rc->task == 'settings') {
// add hooks for Enigma settings
$this->add_hook('settings_actions', [$this, 'settings_actions']);
$this->add_hook('preferences_sections_list', [$this, 'preferences_sections_list']);
$this->add_hook('preferences_list', [$this, 'preferences_list']);
$this->add_hook('preferences_save', [$this, 'preferences_save']);
$this->add_hook('identity_form', [$this, 'identity_form']);
// register handler for keys/certs management
$this->register_action('plugin.enigmakeys', [$this, 'preferences_ui']);
-// $this->register_action('plugin.enigmacerts', array($this, 'preferences_ui'));
+// $this->register_action('plugin.enigmacerts', [$this, 'preferences_ui']);
$this->load_ui();
if (empty($_REQUEST['_framed']) || strpos($this->rc->action, 'plugin.enigma') === 0) {
$this->ui->add_css();
}
$this->password_handler();
}
else if ($this->rc->task == 'cli') {
$this->add_hook('user_delete_commit', [$this, 'user_delete']);
}
$this->add_hook('refresh', [$this, 'refresh']);
}
/**
* Plugin environment initialization.
*/
function load_env()
{
if ($this->env_loaded) {
return;
}
$this->env_loaded = true;
// Add include path for Enigma classes and drivers
$include_path = $this->home . '/lib' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// load the Enigma plugin configuration
$this->load_config();
// include localization (if wasn't included before)
$this->add_texts('localization/');
}
/**
* Plugin UI initialization.
*/
function load_ui($all = false)
{
if (!$this->ui) {
// load config/localization
$this->load_env();
// Load UI
$this->ui = new enigma_ui($this, $this->home);
}
if ($all) {
$this->ui->add_css();
$this->ui->add_js();
}
}
/**
* Plugin engine initialization.
*/
function load_engine()
{
if ($this->engine) {
return $this->engine;
}
// load config/localization
$this->load_env();
return $this->engine = new enigma_engine($this);
}
/**
* Handler for message_part_structure hook.
* Called for every part of the message.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function part_structure($p)
{
$this->load_engine();
return $this->engine->part_structure($p);
}
/**
* Handler for message_part_body hook.
* Called to get body of a message part.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function part_body($p)
{
$this->load_engine();
return $this->engine->part_body($p);
}
/**
* Handler for settings_actions hook.
* Adds Enigma settings section into preferences.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function settings_actions($args)
{
// add labels
$this->add_texts('localization/');
// register as settings action
$args['actions'][] = [
'action' => 'plugin.enigmakeys',
'class' => 'enigma keys',
'label' => 'enigmakeys',
'title' => 'enigmakeys',
'domain' => 'enigma',
];
/*
$args['actions'][] = [
'action' => 'plugin.enigmacerts',
'class' => 'enigma certs',
'label' => 'enigmacerts',
'title' => 'enigmacerts',
'domain' => 'enigma',
];
*/
return $args;
}
/**
* Handler for preferences_sections_list hook.
* Adds Encryption settings section into preferences sections list.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_sections_list($p)
{
$p['list']['enigma'] = [
'id' => 'enigma', 'section' => $this->gettext('encryption'),
];
return $p;
}
/**
* Handler for preferences_list hook.
* Adds options blocks into Enigma settings sections in Preferences.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_list($p)
{
if ($p['section'] != 'encryption') {
return $p;
}
$no_override = array_flip((array)$this->rc->config->get('dont_override'));
if (!isset($no_override['enigma_encryption'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_encryption';
$input = new html_checkbox([
'name' => '_enigma_encryption',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_encryption'] = [
'title' => html::label($field_id, $this->gettext('supportencryption')),
'content' => $input->show(intval($this->rc->config->get('enigma_encryption'))),
];
}
if (!isset($no_override['enigma_signatures'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_signatures';
$input = new html_checkbox([
'name' => '_enigma_signatures',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_signatures'] = [
'title' => html::label($field_id, $this->gettext('supportsignatures')),
'content' => $input->show(intval($this->rc->config->get('enigma_signatures'))),
];
}
if (!isset($no_override['enigma_decryption'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_decryption';
$input = new html_checkbox([
'name' => '_enigma_decryption',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_decryption'] = [
'title' => html::label($field_id, $this->gettext('supportdecryption')),
'content' => $input->show(intval($this->rc->config->get('enigma_decryption'))),
];
}
if (!isset($no_override['enigma_sign_all'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_sign_all';
$input = new html_checkbox([
'name' => '_enigma_sign_all',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_sign_all'] = [
'title' => html::label($field_id, $this->gettext('signdefault')),
'content' => $input->show($this->rc->config->get('enigma_sign_all') ? 1 : 0),
];
}
if (!isset($no_override['enigma_encrypt_all'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_encrypt_all';
$input = new html_checkbox([
'name' => '_enigma_encrypt_all',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_encrypt_all'] = [
'title' => html::label($field_id, $this->gettext('encryptdefault')),
'content' => $input->show($this->rc->config->get('enigma_encrypt_all') ? 1 : 0),
];
}
if (!isset($no_override['enigma_attach_pubkey'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_attach_pubkey';
$input = new html_checkbox([
'name' => '_enigma_attach_pubkey',
'id' => $field_id,
'value' => 1,
]);
$p['blocks']['main']['options']['enigma_attach_pubkey'] = [
'title' => html::label($field_id, $this->gettext('attachpubkeydefault')),
'content' => $input->show($this->rc->config->get('enigma_attach_pubkey') ? 1 : 0),
];
}
if (!isset($no_override['enigma_password_time'])) {
if (!$p['current']) {
$p['blocks']['main']['content'] = true;
return $p;
}
$field_id = 'rcmfd_enigma_password_time';
$select = new html_select(['name' => '_enigma_password_time', 'id' => $field_id, 'class' => 'custom-select']);
- foreach (array(1, 5, 10, 15, 30) as $m) {
+ foreach ([1, 5, 10, 15, 30] as $m) {
$label = $this->gettext(['name' => 'nminutes', 'vars' => ['m' => $m]]);
$select->add($label, $m);
}
$select->add($this->gettext('wholesession'), 0);
$p['blocks']['main']['options']['enigma_password_time'] = [
'title' => html::label($field_id, $this->gettext('passwordtime')),
'content' => $select->show(intval($this->rc->config->get('enigma_password_time'))),
];
}
return $p;
}
/**
* Handler for preferences_save hook.
* Executed on Enigma settings form submit.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_save($p)
{
if ($p['section'] == 'encryption') {
$p['prefs'] = [
'enigma_signatures' => (bool) rcube_utils::get_input_value('_enigma_signatures', rcube_utils::INPUT_POST),
'enigma_decryption' => (bool) rcube_utils::get_input_value('_enigma_decryption', rcube_utils::INPUT_POST),
'enigma_encryption' => (bool) rcube_utils::get_input_value('_enigma_encryption', rcube_utils::INPUT_POST),
'enigma_sign_all' => (bool) rcube_utils::get_input_value('_enigma_sign_all', rcube_utils::INPUT_POST),
'enigma_encrypt_all' => (bool) rcube_utils::get_input_value('_enigma_encrypt_all', rcube_utils::INPUT_POST),
'enigma_attach_pubkey' => (bool) rcube_utils::get_input_value('_enigma_attach_pubkey', rcube_utils::INPUT_POST),
'enigma_password_time' => intval(rcube_utils::get_input_value('_enigma_password_time', rcube_utils::INPUT_POST)),
];
}
return $p;
}
/**
* Handler for keys/certs management UI template.
*/
function preferences_ui()
{
$this->load_ui();
$this->ui->init();
}
/**
* Handler for 'identity_form' plugin hook.
*
* This will list private keys matching this identity
* and add a link to enigma key management action.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function identity_form($p)
{
if (isset($p['form']['encryption']) && !empty($p['record']['identity_id'])) {
$content = '';
// find private keys for this identity
if (!empty($p['record']['email'])) {
$listing = [];
$engine = $this->load_engine();
$keys = (array) $engine->list_keys($p['record']['email']);
foreach ($keys as $key) {
if ($key->get_type() === enigma_key::TYPE_KEYPAIR) {
$listing[] = html::tag('li', null,
html::tag('strong', 'uid', html::quote($key->id))
. ' ' . html::tag('span', 'identity', html::quote($key->name))
);
}
}
if (count($listing)) {
$content .= html::p(null, $this->gettext(['name' => 'identitymatchingprivkeys', 'vars' => ['nr' => count($listing)]]));
$content .= html::tag('ul', 'keylist', implode("\n", $listing));
}
else {
$content .= html::p(null, $this->gettext('identitynoprivkeys'));
}
}
// add button linking to enigma key management
$button_attr = [
'class' => 'button',
'href' => $this->rc->url(['action' => 'plugin.enigmakeys']),
'target' => '_parent',
];
$content .= html::p(null, html::a($button_attr, $this->gettext('managekeys')));
// rename class to avoid Mailvelope key management to kick in
$p['form']['encryption']['attrs'] = ['class' => 'enigma-identity-encryption'];
// fill fieldset content with our stuff
$p['form']['encryption']['content'] = html::div('identity-encryption-block', $content);
}
return $p;
}
/**
* Handler for message_body_prefix hook.
* Called for every displayed (content) part of the message.
* Adds infobox about signature verification and/or decryption
* status above the body.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function status_message($p)
{
$this->load_ui();
return $this->ui->status_message($p);
}
/**
* Handler for message_load hook.
* Check message bodies and attachments for keys/certs.
*/
function message_load($p)
{
$this->load_ui();
return $this->ui->message_load($p);
}
/**
* Handler for template_object_messagebody hook.
* This callback function adds a box below the message content
* if there is a key/cert attachment available
*/
function message_output($p)
{
$this->load_ui();
return $this->ui->message_output($p);
}
/**
* Handler for attached keys/certs import
*/
function import_file()
{
$this->load_ui();
$this->ui->import_file();
}
/**
* Handle password submissions
*/
function password_handler()
{
$this->load_engine();
$this->engine->password_handler();
}
/**
* Handle message_ready hook (encryption/signing)
*/
function message_ready($p)
{
$this->load_ui();
return $this->ui->message_ready($p);
}
/**
* Handle message_compose_body hook
*/
function message_compose($p)
{
$this->load_ui();
return $this->ui->message_compose($p);
}
/**
* Handler for refresh hook.
*/
function refresh($p)
{
// calling enigma_engine constructor to remove passwords
// stored in session after expiration time
$this->load_engine();
return $p;
}
/**
* Handle delete_user_commit hook
*/
function user_delete($p)
{
$this->load_engine();
$p['abort'] = $p['abort'] || !$this->engine->delete_user_data($p['username']);
return $p;
}
}
diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php
index def197639..1c14946e5 100644
--- a/plugins/enigma/lib/enigma_driver_gnupg.php
+++ b/plugins/enigma/lib/enigma_driver_gnupg.php
@@ -1,769 +1,769 @@
<?php
/**
+-------------------------------------------------------------------------+
| GnuPG (PGP) driver for the Enigma Plugin |
| |
| 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. |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
require_once 'Crypt/GPG.php';
class enigma_driver_gnupg extends enigma_driver
{
protected $rc;
protected $gpg;
protected $homedir;
protected $user;
protected $last_sig_algorithm;
protected $debug = false;
protected $db_files = ['pubring.gpg', 'secring.gpg', 'pubring.kbx'];
/**
* Class constructor
*
* @param rcube_user $user User object
*/
function __construct($user)
{
$this->rc = rcmail::get_instance();
$this->user = $user;
}
/**
* Driver initialization and environment checking.
* Should only return critical errors.
*
* @return enigma_error|null NULL on success, enigma_error on failure
*/
function init()
{
$homedir = $this->rc->config->get('enigma_pgp_homedir');
$debug = $this->rc->config->get('enigma_debug');
$binary = $this->rc->config->get('enigma_pgp_binary');
$agent = $this->rc->config->get('enigma_pgp_agent');
$gpgconf = $this->rc->config->get('enigma_pgp_gpgconf');
if (!$homedir) {
return new enigma_error(enigma_error::INTERNAL,
"Option 'enigma_pgp_homedir' not specified");
}
// check if homedir exists (create it if not) and is readable
if (!file_exists($homedir)) {
return new enigma_error(enigma_error::INTERNAL,
"Keys directory doesn't exists: $homedir");
}
if (!is_writable($homedir)) {
return new enigma_error(enigma_error::INTERNAL,
"Keys directory isn't writeable: $homedir");
}
$homedir = $homedir . '/' . $this->user;
// check if user's homedir exists (create it if not) and is readable
if (!file_exists($homedir)) {
mkdir($homedir, 0700);
}
if (!file_exists($homedir)) {
return new enigma_error(enigma_error::INTERNAL,
"Unable to create keys directory: $homedir");
}
if (!is_writable($homedir)) {
return new enigma_error(enigma_error::INTERNAL,
"Unable to write to keys directory: $homedir");
}
$this->debug = $debug;
$this->homedir = $homedir;
$options = ['homedir' => $this->homedir];
if ($debug) {
$options['debug'] = [$this, 'debug'];
}
if ($binary) {
$options['binary'] = $binary;
}
if ($agent) {
$options['agent'] = $agent;
}
if ($gpgconf) {
$options['gpgconf'] = $gpgconf;
}
$options['cipher-algo'] = $this->rc->config->get('enigma_pgp_cipher_algo');
$options['digest-algo'] = $this->rc->config->get('enigma_pgp_digest_algo');
// Create Crypt_GPG object
try {
$this->gpg = new Crypt_GPG($options);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
$this->db_sync();
}
/**
* Encryption (and optional signing).
*
* @param string $text Message body
* @param array $keys List of keys (enigma_key objects)
* @param enigma_key $sign_key Optional signing Key ID
*
* @return string|enigma_error Encrypted message or enigma_error on failure
*/
function encrypt($text, $keys, $sign_key = null)
{
try {
foreach ($keys as $key) {
$this->gpg->addEncryptKey($key->reference);
}
if ($sign_key) {
$this->gpg->addSignKey($sign_key->reference, $sign_key->password);
$res = $this->gpg->encryptAndSign($text, true);
$sigInfo = $this->gpg->getLastSignatureInfo();
$this->last_sig_algorithm = $sigInfo->getHashAlgorithmName();
return $res;
}
return $this->gpg->encrypt($text, true);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Decrypt a message (and verify if signature found)
*
* @param string $text Encrypted message
* @param array $keys List of key-password mapping
* @param enigma_signature &$signature Signature information (if available)
*
* @return mixed Decrypted message or enigma_error on failure
*/
function decrypt($text, $keys = [], &$signature = null)
{
try {
foreach ($keys as $key => $password) {
$this->gpg->addDecryptKey($key, $password);
}
$result = $this->gpg->decryptAndVerify($text, true);
if (!empty($result['signatures'])) {
$signature = $this->parse_signature($result['signatures'][0]);
}
// EFAIL vulnerability mitigation (#6289)
// Handle MDC warning as an exception, this is the default for gpg 2.3.
if (method_exists($this->gpg, 'getWarnings')) {
foreach ($this->gpg->getWarnings() as $warning_msg) {
if (strpos($warning_msg, 'not integrity protected') !== false) {
return new enigma_error(enigma_error::NOMDC, ucfirst($warning_msg));
}
}
}
return $result['data'];
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Signing.
*
* @param string $text Message body
* @param enigma_key $key The key
* @param int $mode Signing mode (enigma_engine::SIGN_*)
*
* @return mixed True on success or enigma_error on failure
*/
function sign($text, $key, $mode = null)
{
try {
$this->gpg->addSignKey($key->reference, $key->password);
$res = $this->gpg->sign($text, $mode, Crypt_GPG::ARMOR_ASCII, true);
$sigInfo = $this->gpg->getLastSignatureInfo();
$this->last_sig_algorithm = $sigInfo->getHashAlgorithmName();
return $res;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Signature verification.
*
* @param string $text Message body
* @param string $signature Signature, if message is of type PGP/MIME and body doesn't contain it
*
* @return enigma_signature|enigma_error Signature information or enigma_error
*/
function verify($text, $signature)
{
try {
$verified = $this->gpg->verify($text, $signature);
return $this->parse_signature($verified[0]);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key file import.
*
* @param string $content File name or file content
* @param bool $isfile True if first argument is a filename
* @param array $password Optional key => password map
*
* @return mixed Import status array or enigma_error
*/
public function import($content, $isfile = false, $passwords = [])
{
try {
// GnuPG 2.1 requires secret key passphrases on import
foreach ($passwords as $keyid => $pass) {
$this->gpg->addPassphrase($keyid, $pass);
}
if ($isfile) {
$result = $this->gpg->importKeyFile($content);
}
else {
$result = $this->gpg->importKey($content);
}
$this->db_save();
return $result;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key export.
*
* @param string $keyid Key ID
* @param bool $with_private Include private key
* @param array $passwords Optional key => password map
*
* @return string|enigma_error Key content or enigma_error
*/
public function export($keyid, $with_private = false, $passwords = [])
{
try {
$key = $this->gpg->exportPublicKey($keyid, true);
if ($with_private) {
// GnuPG 2.1 requires secret key passphrases on export
foreach ($passwords as $_keyid => $pass) {
$this->gpg->addPassphrase($_keyid, $pass);
}
$priv = $this->gpg->exportPrivateKey($keyid, true);
$key .= $priv;
}
return $key;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Keys listing.
*
* @param string $patter Optional pattern for key ID, user ID or fingerprint
*
* @return enigma_key[]|enigma_error Array of keys or enigma_error
*/
public function list_keys($pattern = '')
{
try {
$keys = $this->gpg->getKeys($pattern);
$result = [];
foreach ($keys as $idx => $key) {
$result[] = $this->parse_key($key);
}
return $result;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Single key information.
*
* @param string $keyid Key ID, user ID or fingerprint
*
* @return enigma_key|enigma_error Key object or enigma_error
*/
public function get_key($keyid)
{
$list = $this->list_keys($keyid);
if (is_array($list)) {
return $list[key($list)];
}
// error
return $list;
}
/**
* Key pair generation.
*
* @param array $data Key/User data (user, email, password, size)
*
* @return mixed Key (enigma_key) object or enigma_error
*/
public function gen_key($data)
{
try {
$debug = $this->rc->config->get('enigma_debug');
$keygen = new Crypt_GPG_KeyGenerator([
'homedir' => $this->homedir,
// 'binary' => '/usr/bin/gpg2',
'debug' => $debug ? [$this, 'debug'] : false,
]);
$key = $keygen
->setExpirationDate(0)
->setPassphrase($data['password'])
->generateKey($data['user'], $data['email']);
return $this->parse_key($key);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key deletion.
*
* @param string $keyid Key ID
*
* @return mixed True on success or enigma_error
*/
public function delete_key($keyid)
{
// delete public key
$result = $this->delete_pubkey($keyid);
// error handling
if ($result !== true) {
$code = $result->getCode();
// if not found, delete private key
if ($code == enigma_error::KEYNOTFOUND) {
$result = $this->delete_privkey($keyid);
}
// need to delete private key first
else if ($code == enigma_error::DELKEY) {
$result = $this->delete_privkey($keyid);
if ($result === true) {
$result = $this->delete_pubkey($keyid);
}
}
}
$this->db_save();
return $result;
}
/**
* Returns a name of the hash algorithm used for the last
* signing operation.
*
* @return string Hash algorithm name e.g. sha1
*/
public function signature_algorithm()
{
return $this->last_sig_algorithm;
}
/**
* Returns a list of supported features.
*
* @return array Capabilities list
*/
public function capabilities()
{
$caps = [enigma_driver::SUPPORT_RSA];
$version = $this->gpg->getVersion();
if (version_compare($version, '2.1.7', 'ge')) {
$caps[] = enigma_driver::SUPPORT_ECC;
}
return $caps;
}
/**
* Private key deletion.
*/
protected function delete_privkey($keyid)
{
try {
$this->gpg->deletePrivateKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Public key deletion.
*/
protected function delete_pubkey($keyid)
{
try {
$this->gpg->deletePublicKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Converts Crypt_GPG exception into Enigma's error object
*
* @param mixed $e Exception object
*
* @return enigma_error Error object
*/
protected function get_error_from_exception($e)
{
$data = [];
if ($e instanceof Crypt_GPG_KeyNotFoundException) {
$error = enigma_error::KEYNOTFOUND;
$data['id'] = $e->getKeyId();
}
else if ($e instanceof Crypt_GPG_BadPassphraseException) {
$error = enigma_error::BADPASS;
$data['bad'] = $e->getBadPassphrases();
$data['missing'] = $e->getMissingPassphrases();
}
else if ($e instanceof Crypt_GPG_NoDataException) {
$error = enigma_error::NODATA;
}
else if ($e instanceof Crypt_GPG_DeletePrivateKeyException) {
$error = enigma_error::DELKEY;
}
else {
$error = enigma_error::INTERNAL;
}
$msg = $e->getMessage();
return new enigma_error($error, $msg, $data);
}
/**
* Converts Crypt_GPG_Signature object into Enigma's signature object
*
* @param Crypt_GPG_Signature $sig Signature object
*
* @return enigma_signature Signature object
*/
protected function parse_signature($sig)
{
$data = new enigma_signature();
$data->id = $sig->getId() ?: $sig->getKeyId();
$data->valid = $sig->isValid();
$data->fingerprint = $sig->getKeyFingerprint();
$data->created = $sig->getCreationDate();
$data->expires = $sig->getExpirationDate();
// In case of ERRSIG user may not be set
if ($user = $sig->getUserId()) {
$data->name = $user->getName();
$data->comment = $user->getComment();
$data->email = $user->getEmail();
}
return $data;
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
* @param Crypt_GPG_Key $key Key object
*
* @return enigma_key Key object
*/
protected function parse_key($key)
{
$ekey = new enigma_key();
foreach ($key->getUserIds() as $idx => $user) {
$id = new enigma_userid();
$id->name = $user->getName();
$id->comment = $user->getComment();
$id->email = $user->getEmail();
$id->valid = $user->isValid();
$id->revoked = $user->isRevoked();
$ekey->users[$idx] = $id;
}
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
// keep reference to Crypt_GPG's key for performance reasons
$ekey->reference = $key;
foreach ($key->getSubKeys() as $idx => $subkey) {
$skey = new enigma_subkey();
$skey->id = $subkey->getId();
$skey->revoked = $subkey->isRevoked();
$skey->created = $subkey->getCreationDate();
$skey->expires = $subkey->getExpirationDate();
$skey->fingerprint = $subkey->getFingerprint();
$skey->has_private = $subkey->hasPrivate();
$skey->algorithm = $subkey->getAlgorithm();
$skey->length = $subkey->getLength();
$skey->usage = $subkey->usage();
$ekey->subkeys[$idx] = $skey;
};
$ekey->id = $ekey->subkeys[0]->id;
return $ekey;
}
/**
* Syncronize keys database on multi-host setups
*/
protected function db_sync()
{
if (!$this->rc->config->get('enigma_multihost')) {
return;
}
$db = $this->rc->get_dbh();
$table = $db->table_name('filestore', true);
$files = [];
$result = $db->query(
"SELECT `file_id`, `filename`, `mtime` FROM $table WHERE `user_id` = ? AND `context` = ?",
$this->rc->user->ID, 'enigma'
);
while ($record = $db->fetch_assoc($result)) {
$file = $this->homedir . '/' . $record['filename'];
$mtime = @filemtime($file);
$files[] = $record['filename'];
if ($mtime < $record['mtime']) {
$data_result = $db->query("SELECT `data`, `mtime` FROM $table"
. " WHERE `file_id` = ?", $record['file_id']
);
$record = $db->fetch_assoc($data_result);
$data = $record ? base64_decode($record['data']) : null;
if ($data === null || $data === false) {
rcube::raise_error([
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Enigma: Failed to sync $file ({$record['file_id']}). Decode error."
], true, false);
continue;
}
$tmpfile = $file . '.tmp';
if (file_put_contents($tmpfile, $data, LOCK_EX) === strlen($data)) {
rename($tmpfile, $file);
touch($file, $record['mtime']);
if ($this->debug) {
$this->debug("SYNC: Fetched file: $file");
}
}
else {
// error
@unlink($tmpfile);
rcube::raise_error([
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Enigma: Failed to sync $file."
], true, false);
}
}
}
// Remove files not in database
if (!$db->is_error($result)) {
foreach (array_diff($this->db_files_list(), $files) as $file) {
$file = $this->homedir . '/' . $file;
if (unlink($file)) {
if ($this->debug) {
$this->debug("SYNC: Removed file: $file");
}
}
}
}
// No records found, do initial sync if already have the keyring
if (!$db->is_error($result) && empty($file)) {
$this->db_save(true);
}
}
/**
* Save keys database for multi-host setups
*/
protected function db_save($is_empty = false)
{
if (!$this->rc->config->get('enigma_multihost')) {
return true;
}
$db = $this->rc->get_dbh();
$table = $db->table_name('filestore', true);
$records = [];
if (!$is_empty) {
$result = $db->query(
"SELECT `file_id`, `filename`, `mtime` FROM $table WHERE `user_id` = ? AND `context` = ?",
$this->rc->user->ID, 'enigma'
);
while ($record = $db->fetch_assoc($result)) {
$records[$record['filename']] = $record;
}
}
foreach ($this->db_files_list() as $filename) {
$file = $this->homedir . '/' . $filename;
$mtime = @filemtime($file);
$existing = !empty($records[$filename]) ? $records[$filename] : null;
unset($records[$filename]);
if ($mtime && (empty($existing) || $mtime > $existing['mtime'])) {
$data = file_get_contents($file);
$data = base64_encode($data);
$datasize = strlen($data);
if (empty($maxsize)) {
$maxsize = min($db->get_variable('max_allowed_packet', 1048500), 4*1024*1024) - 2000;
}
if ($datasize > $maxsize) {
rcube::raise_error([
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Enigma: Failed to save $file. Size exceeds max_allowed_packet."
], true, false);
continue;
}
$unique = ['user_id' => $this->rc->user->ID, 'context' => 'enigma', 'filename' => $filename];
- $result = $db->insert_or_update($table, $unique, array('mtime', 'data'), array($mtime, $data));
+ $result = $db->insert_or_update($table, $unique, ['mtime', 'data'], [$mtime, $data]);
if ($db->is_error($result)) {
rcube::raise_error([
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Enigma: Failed to save $file into database."
], true, false);
break;
}
if ($this->debug) {
$this->debug("SYNC: Pushed file: $file");
}
}
}
// Delete removed files from database
foreach (array_keys($records) as $filename) {
$file = $this->homedir . '/' . $filename;
$result = $db->query("DELETE FROM $table WHERE `user_id` = ? AND `context` = ? AND `filename` = ?",
$this->rc->user->ID, 'enigma', $filename
);
if ($db->is_error($result)) {
rcube::raise_error([
'code' => 605, 'line' => __LINE__, 'file' => __FILE__,
'message' => "Enigma: Failed to delete $file from database."
], true, false);
break;
}
if ($this->debug) {
$this->debug("SYNC: Removed file: $file");
}
}
}
/**
* Returns list of homedir files to backup
*/
protected function db_files_list()
{
$files = [];
foreach ($this->db_files as $file) {
if (file_exists($this->homedir . '/' . $file)) {
$files[] = $file;
}
}
foreach (glob($this->homedir . '/private-keys-v1.d/*.key') as $file) {
$files[] = ltrim(substr($file, strlen($this->homedir)), '/');
}
return $files;
}
/**
* Write debug info from Crypt_GPG to logs/enigma
*/
public function debug($line)
{
rcube::write_log('enigma', 'GPG: ' . $line);
}
}
diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php
index 74f39bb28..d5438280a 100644
--- a/plugins/enigma/lib/enigma_ui.php
+++ b/plugins/enigma/lib/enigma_ui.php
@@ -1,1360 +1,1360 @@
<?php
/**
+-------------------------------------------------------------------------+
| User Interface for the Enigma Plugin |
| |
| 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. |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_ui
{
private $rc;
private $enigma;
private $home;
private $css_loaded;
private $js_loaded;
private $data;
private $keys_parts = [];
private $keys_bodies = [];
/**
* Object constructor
*
* @param enigma $enigma_plugin The plugin instance
* @param string $home Home directory
*/
function __construct($enigma_plugin, $home = '')
{
$this->enigma = $enigma_plugin;
$this->rc = $enigma_plugin->rc;
$this->home = $home; // we cannot use $enigma_plugin->home here
}
/**
* UI initialization and requests handlers.
*/
function init()
{
$this->add_js();
$action = rcube_utils::get_input_value('_a', rcube_utils::INPUT_GPC);
if ($this->rc->action == 'plugin.enigmakeys') {
switch ($action) {
case 'delete':
$this->key_delete();
break;
/*
case 'edit':
$this->key_edit();
break;
*/
case 'import':
$this->key_import();
break;
case 'import-search':
$this->key_import_search();
break;
case 'export':
$this->key_export();
break;
case 'generate':
$this->key_generate();
break;
case 'create':
$this->key_create();
break;
case 'search':
case 'list':
$this->key_list();
break;
case 'info':
$this->key_info();
break;
}
$this->rc->output->add_handlers([
'keyslist' => [$this, 'tpl_keys_list'],
'countdisplay' => [$this, 'tpl_keys_rowcount'],
'searchform' => [$this->rc->output, 'search_form'],
]);
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
$this->rc->output->send('enigma.keys');
}
/*
// Preferences UI
else if ($this->rc->action == 'plugin.enigmacerts') {
$this->rc->output->add_handlers([
'keyslist' => [$this, 'tpl_certs_list'],
'keyframe' => [$this, 'tpl_cert_frame'],
'countdisplay' => [$this, 'tpl_certs_rowcount'],
'searchform' => [$this->rc->output, 'search_form'],
]);
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
$this->rc->output->send('enigma.certs');
}
*/
// Message composing UI
else if ($this->rc->action == 'compose') {
$this->compose_ui();
}
}
/**
* Adds CSS style file to the page header.
*/
function add_css()
{
if ($this->css_loaded) {
return;
}
$skin_path = $this->enigma->local_skin_path();
$this->enigma->include_stylesheet("$skin_path/enigma.css");
$this->css_loaded = true;
}
/**
* Adds javascript file to the page header.
*/
function add_js()
{
if ($this->js_loaded) {
return;
}
$this->enigma->include_script('enigma.js');
$this->rc->output->set_env('keyservers', $this->rc->config->keyservers());
$this->js_loaded = true;
}
/**
* Initializes key password prompt
*
* @param enigma_error $status Error object with key info
* @param array $params Optional prompt parameters
*/
function password_prompt($status, $params = [])
{
$data = $status->getData('missing');
if (empty($data)) {
$data = $status->getData('bad');
}
$keyid = key($data);
$data = [
'keyid' => !empty($params['keyid']) ? $params['keyid'] : $keyid,
'user' => $data[$keyid]
];
// With GnuPG 2.1 user name may not be specified (e.g. on private
// key export), we'll get the key information and set the name appropriately
if ($keyid && !empty($params['keyid']) && strpos($data['user'], $keyid) !== false) {
$key = $this->enigma->engine->get_key($params['keyid']);
if ($key && $key->name) {
$data['user'] = $key->name;
}
}
if (!empty($params)) {
$data = array_merge($params, $data);
}
if (preg_match('/^(send|plugin.enigmaimport|plugin.enigmakeys)$/', $this->rc->action)) {
$this->rc->output->command('enigma_password_request', $data);
}
else {
$this->rc->output->set_env('enigma_password_request', $data);
}
// add some labels to client
$this->rc->output->add_label('enigma.enterkeypasstitle', 'enigma.enterkeypass',
'save', 'cancel');
$this->add_css();
$this->add_js();
}
/**
* Template object for list of keys.
*
* @param array $attrib Object attributes
*
* @return string HTML content
*/
function tpl_keys_list($attrib)
{
// add id to message list table if not specified
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmenigmakeyslist';
}
// define list of cols to be displayed
$a_show_cols = ['name'];
// create XHTML table
$out = rcmail_action::table_output($attrib, [], $a_show_cols, 'id');
// set client env
$this->rc->output->add_gui_object('keyslist', $attrib['id']);
$this->rc->output->include_script('list.js');
// add some labels to client
$this->rc->output->add_label('enigma.keyremoveconfirm', 'enigma.keyremoving',
'enigma.keyexportprompt', 'enigma.withprivkeys', 'enigma.onlypubkeys',
'enigma.exportkeys', 'enigma.importkeys', 'enigma.keyimportsearchlabel',
'import', 'search'
);
return $out;
}
/**
* Key listing (and searching) request handler
*/
private function key_list()
{
$this->enigma->load_engine();
$pagesize = $this->rc->config->get('pagesize', 100);
$page = max(intval(rcube_utils::get_input_value('_p', rcube_utils::INPUT_GPC)), 1);
$search = rcube_utils::get_input_value('_q', rcube_utils::INPUT_GPC);
// Get the list
$list = $this->enigma->engine->list_keys($search);
$size = 0;
$listsize = 0;
if (!is_array($list)) {
$this->rc->output->show_message('enigma.keylisterror', 'error');
}
else if (empty($list)) {
$this->rc->output->show_message('enigma.nokeysfound', 'notice');
}
else {
// Save the size
$listsize = count($list);
// Sort the list by key (user) name
usort($list, ['enigma_key', 'cmp']);
// Slice current page
$list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
$size = count($list);
// Add rows
foreach ($list as $key) {
$this->rc->output->command('enigma_add_list_row', [
'name' => rcube::Q($key->name),
'id' => $key->id,
'flags' => $key->is_private() ? 'p' : ''
]);
}
}
$this->rc->output->set_env('rowcount', $size);
$this->rc->output->set_env('search_request', $search);
$this->rc->output->set_env('pagecount', ceil($listsize/$pagesize));
$this->rc->output->set_env('current_page', $page);
$this->rc->output->command('set_rowcount', $this->get_rowcount_text($listsize, $size, $page));
$this->rc->output->send();
}
/**
* Template object for list records counter.
*
* @param array $attrib Object attributes
*
* @return string HTML output
*/
function tpl_keys_rowcount($attrib)
{
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmcountdisplay';
}
$this->rc->output->add_gui_object('countdisplay', $attrib['id']);
return html::span($attrib, $this->get_rowcount_text());
}
/**
* Returns text representation of list records counter
*/
private function get_rowcount_text($all = 0, $curr_count = 0, $page = 1)
{
if (!$curr_count) {
$out = $this->enigma->gettext('nokeysfound');
}
else {
$pagesize = $this->rc->config->get('pagesize', 100);
$first = ($page - 1) * $pagesize;
$out = $this->enigma->gettext([
'name' => 'keysfromto',
'vars' => ['from' => $first + 1, 'to' => $first + $curr_count, 'count' => $all]
]);
}
return $out;
}
/**
* Key information page handler
*/
private function key_info()
{
$this->enigma->load_engine();
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
$res = $this->enigma->engine->get_key($id);
if ($res instanceof enigma_key) {
$this->data = $res;
}
else { // error
$this->rc->output->show_message('enigma.keyopenerror', 'error');
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers([
'keyname' => [$this, 'tpl_key_name'],
'keydata' => [$this, 'tpl_key_data'],
]);
$this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
$this->rc->output->send('enigma.keyinfo');
}
/**
* Template object for key name
*
* @param array $attrib Object attributes
*
* @return string HTML output
*/
function tpl_key_name($attrib)
{
return rcube::Q($this->data->name);
}
/**
* Template object for key information page content
*
* @param array $attrib Object attributes
*
* @return string HTML output
*/
function tpl_key_data($attrib)
{
$out = '';
$table = new html_table(['cols' => 2]);
// Key user ID
$table->add('title', html::label(null, $this->enigma->gettext('keyuserid')));
$table->add(null, rcube::Q($this->data->name));
// Key ID
$table->add('title', html::label(null, $this->enigma->gettext('keyid')));
$table->add(null, $this->data->subkeys[0]->get_short_id());
// Key type
$keytype = $this->data->get_type();
$type = null;
if ($keytype == enigma_key::TYPE_KEYPAIR) {
$type = $this->enigma->gettext('typekeypair');
}
else if ($keytype == enigma_key::TYPE_PUBLIC) {
$type = $this->enigma->gettext('typepublickey');
}
$table->add('title', html::label(null, $this->enigma->gettext('keytype')));
$table->add(null, $type);
// Key fingerprint
$table->add('title', html::label(null, $this->enigma->gettext('fingerprint')));
$table->add(null, $this->data->subkeys[0]->get_fingerprint());
$out .= html::tag('fieldset', null,
html::tag('legend', null, $this->enigma->gettext('basicinfo')) . $table->show($attrib)
);
// Subkeys
$table = new html_table(['cols' => 5, 'id' => 'enigmasubkeytable', 'class' => 'records-table']);
$table->add_header('id', $this->enigma->gettext('subkeyid'));
$table->add_header('algo', $this->enigma->gettext('subkeyalgo'));
$table->add_header('created', $this->enigma->gettext('subkeycreated'));
$table->add_header('expires', $this->enigma->gettext('subkeyexpires'));
$table->add_header('usage', $this->enigma->gettext('subkeyusage'));
$now = time();
$date_format = $this->rc->config->get('date_format', 'Y-m-d');
$usage_map = [
enigma_key::CAN_ENCRYPT => $this->enigma->gettext('typeencrypt'),
enigma_key::CAN_SIGN => $this->enigma->gettext('typesign'),
enigma_key::CAN_CERTIFY => $this->enigma->gettext('typecert'),
enigma_key::CAN_AUTHENTICATE => $this->enigma->gettext('typeauth'),
];
foreach ($this->data->subkeys as $subkey) {
$algo = $subkey->get_algorithm();
if ($algo && $subkey->length) {
$algo .= ' (' . $subkey->length . ')';
}
$usage = [];
foreach ($usage_map as $key => $text) {
if ($subkey->usage & $key) {
$usage[] = $text;
}
}
$table->set_row_attribs($subkey->revoked || ($subkey->expires && $subkey->expires < $now) ? 'deleted' : '');
$table->add('id', $subkey->get_short_id());
$table->add('algo', $algo);
$table->add('created', $subkey->created ? $this->rc->format_date($subkey->created, $date_format, false) : '');
$table->add('expires', $subkey->expires ? $this->rc->format_date($subkey->expires, $date_format, false) : $this->enigma->gettext('expiresnever'));
$table->add('usage', implode(',', $usage));
}
$out .= html::tag('fieldset', null,
html::tag('legend', null, $this->enigma->gettext('subkeys')) . $table->show()
);
// Additional user IDs
$table = new html_table(['cols' => 2, 'id' => 'enigmausertable', 'class' => 'records-table']);
$table->add_header('id', $this->enigma->gettext('userid'));
$table->add_header('valid', $this->enigma->gettext('uservalid'));
foreach ($this->data->users as $user) {
// Display domains in UTF8
if ($email = rcube_utils::idn_to_utf8($user->email)) {
$user->email = $email;
}
$username = $user->name;
if (!empty($user->comment)) {
$username .= ' (' . $user->comment . ')';
}
$username .= ' <' . $user->email . '>';
$table->set_row_attribs($user->revoked || !$user->valid ? 'deleted' : '');
$table->add('id', rcube::Q(trim($username)));
$table->add('valid', $this->enigma->gettext($user->valid ? 'valid' : 'unknown'));
}
$out .= html::tag('fieldset', null,
html::tag('legend', null, $this->enigma->gettext('userids')) . $table->show()
);
return $out;
}
/**
* Key(s) export handler
*/
private function key_export()
{
$keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST);
$priv = rcube_utils::get_input_value('_priv', rcube_utils::INPUT_POST);
$engine = $this->enigma->load_engine();
$list = $keys == '*' ? $engine->list_keys() : explode(',', $keys);
if (is_array($list) && ($fp = fopen('php://memory', 'rw'))) {
$filename = 'export.pgp';
if (count($list) == 1) {
$filename = (is_object($list[0]) ? $list[0]->id : $list[0]) . '.pgp';
}
$status = null;
foreach ($list as $key) {
$keyid = is_object($key) ? $key->id : $key;
$status = $engine->export_key($keyid, $fp, (bool) $priv);
if ($status instanceof enigma_error) {
$code = $status->getCode();
if ($code == enigma_error::BADPASS) {
$this->password_prompt($status, [
'input_keys' => $keys,
'input_priv' => 1,
'input_task' => 'settings',
'input_action' => 'plugin.enigmakeys',
'input_a' => 'export',
'action' => '?',
'iframe' => true,
'nolock' => true,
'keyid' => $keyid,
]);
fclose($fp);
$this->rc->output->send('iframe');
}
}
}
// send downlaod headers
header('Content-Type: application/pgp-keys');
header('Content-Disposition: attachment; filename="' . $filename . '"');
rewind($fp);
while (!feof($fp)) {
echo fread($fp, 1024 * 1024);
}
fclose($fp);
}
exit;
}
/**
* Key import (page) handler
*/
private function key_import()
{
// Import process
if ($data = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST)) {
$this->enigma->load_engine();
$this->enigma->engine->password_handler();
$result = $this->enigma->engine->import_key($data);
if (is_array($result)) {
if (rcube_utils::get_input_value('_generated', rcube_utils::INPUT_POST)) {
$this->rc->output->command('enigma_key_create_success');
$this->rc->output->show_message('enigma.keygeneratesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
['new' => $result['imported'], 'old' => $result['unchanged']]);
if ($result['imported'] && !empty($_POST['_refresh'])) {
$this->rc->output->command('enigma_list', 1, false);
}
}
}
else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
$this->rc->output->send();
}
else if (!empty($_FILES['_file']['tmp_name']) && is_uploaded_file($_FILES['_file']['tmp_name'])) {
$this->enigma->load_engine();
$result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true);
if (is_array($result)) {
// reload list if any keys has been added
if ($result['imported']) {
$this->rc->output->command('parent.enigma_list', 1);
}
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
['new' => $result['imported'], 'old' => $result['unchanged']]);
$this->rc->output->command('parent.enigma_import_success');
}
else if ($result instanceof enigma_error && $result->getCode() == enigma_error::BADPASS) {
$this->password_prompt($result);
}
else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
$this->rc->output->send('iframe');
}
else if (!empty($_FILES['_file']['error'])) {
rcmail_action::upload_error($_FILES['_file']['error']);
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers([
'importform' => [$this, 'tpl_key_import_form'],
]);
$this->rc->output->send('enigma.keyimport');
}
/**
* Key import-search (page) handler
*/
private function key_import_search()
{
$this->rc->output->add_handlers([
'importform' => [$this, 'tpl_key_import_form'],
]);
$this->rc->output->send('enigma.keysearch');
}
/**
* Template object for key import (upload) form
*
* @param array $attrib Object attributes
*
* @return string HTML output
*/
function tpl_key_import_form($attrib)
{
$attrib += ['id' => 'rcmKeyImportForm'];
if (empty($attrib['part']) || $attrib['part'] == 'import') {
$title = $this->enigma->gettext('keyimportlabel');
$upload = new html_inputfield([
'type' => 'file',
'name' => '_file',
'id' => 'rcmimportfile',
'size' => 30,
'class' => 'form-control'
]);
$max_filesize = rcmail_action::upload_init();
$upload_button = new html_button([
'class' => 'button import',
'onclick' => "return rcmail.command('plugin.enigma-import','',this,event)",
]);
$form = html::div(null, html::p(null, rcube::Q($this->enigma->gettext('keyimporttext'), 'show'))
. $upload->show()
. html::div('hint', $this->rc->gettext(['id' => 'importfile', 'name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]))
. (empty($attrib['part']) ? html::br() . html::br() . $upload_button->show($this->rc->gettext('import')) : '')
);
if (empty($attrib['part'])) {
$form = html::tag('fieldset', '', html::tag('legend', null, $title) . $form);
}
else {
$this->rc->output->set_pagetitle($title);
}
}
if (empty($attrib['part']) || $attrib['part'] == 'search') {
$title = $this->enigma->gettext('keyimportsearchlabel');
$search = new html_inputfield(['type' => 'text', 'name' => '_search',
'id' => 'rcmimportsearch', 'size' => 30, 'class' => 'form-control']);
$search_button = new html_button([
'class' => 'button search',
'onclick' => "return rcmail.command('plugin.enigma-import-search','',this,event)",
]);
$form = html::div(null,
rcube::Q($this->enigma->gettext('keyimportsearchtext'), 'show')
. html::br() . html::br() . $search->show()
. (empty($attrib['part']) ? html::br() . html::br() . $search_button->show($this->rc->gettext('search')) : '')
);
if (empty($attrib['part'])) {
$form = html::tag('fieldset', '', html::tag('legend', null, $title) . $form);
}
else {
$this->rc->output->set_pagetitle($title);
}
$this->rc->output->include_script('publickey.js');
}
$this->rc->output->add_label('selectimportfile', 'importwait', 'nopubkeyfor', 'nopubkeyforsender',
'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys',
'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired',
'keyrevoked', 'keyimportsuccess', 'keyservererror');
$this->rc->output->add_gui_object('importform', $attrib['id']);
$out = $this->rc->output->form_tag([
'action' => $this->rc->url(['action' => $this->rc->action, 'a' => 'import']),
'method' => 'post',
'enctype' => 'multipart/form-data'
] + $attrib,
isset($form) ? $form : ''
);
return $out;
}
/**
* Server-side key pair generation handler
*/
private function key_generate()
{
// Crypt_GPG does not support key generation for multiple identities
// It is also very slow (which is problematic because it may exceed
// request time limit) and requires entropy generator
// That's why we use only OpenPGP.js method of key generation
return;
$user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST, true);
$pass = rcube_utils::get_input_value('_password', rcube_utils::INPUT_POST, true);
$size = (int) rcube_utils::get_input_value('_size', rcube_utils::INPUT_POST);
if ($size > 4096) {
$size = 4096;
}
$ident = rcube_mime::decode_address_list($user, 1, false);
if (empty($ident)) {
$this->rc->output->show_message('enigma.keygenerateerror', 'error');
$this->rc->output->send();
}
$this->enigma->load_engine();
$result = $this->enigma->engine->generate_key([
'user' => $ident[1]['name'],
'email' => $ident[1]['mailto'],
'password' => $pass,
'size' => $size,
]);
if ($result instanceof enigma_key) {
$this->rc->output->command('enigma_key_create_success');
$this->rc->output->show_message('enigma.keygeneratesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('enigma.keygenerateerror', 'error');
}
$this->rc->output->send();
}
/**
* Key generation page handler
*/
private function key_create()
{
$this->enigma->include_script('openpgp.min.js');
$this->rc->output->add_handlers([
'keyform' => [$this, 'tpl_key_create_form'],
]);
$this->rc->output->set_pagetitle($this->enigma->gettext('keygenerate'));
$this->rc->output->send('enigma.keycreate');
}
/**
* Template object for key generation form
*
* @param array $attrib Object attributes
*
* @return string HTML output
*/
function tpl_key_create_form($attrib)
{
$attrib += ['id' => 'rcmKeyCreateForm'];
$table = new html_table(['cols' => 2]);
// get user's identities
$identities = $this->rc->user->list_identities(null, true);
$checkbox = new html_checkbox(['name' => 'identity[]']);
$plugin = $this->rc->plugins->exec_hook('enigma_user_identities', ['identities' => $identities]);
$identities = $plugin['identities'];
$engine = $this->enigma->load_engine();
foreach ($identities as $idx => $ident) {
$name = format_email_recipient($ident['email'], $ident['name']);
$attr = ['value' => $idx, 'data-name' => $ident['name'], 'data-email' => $ident['email_ascii']];
$identities[$idx] = html::tag('li', null, html::label(null, $checkbox->show($idx, $attr) . rcube::Q($name)));
}
$table->add('title', html::label('key-name', rcube::Q($this->enigma->gettext('newkeyident'))));
$table->add(null, html::tag('ul', 'proplist', implode("\n", $identities)));
// Key size
$select = new html_select(['name' => 'type', 'id' => 'key-type', 'class' => 'custom-select']);
$select->add($this->enigma->gettext('rsa2048'), 'rsa2048');
$select->add($this->enigma->gettext('rsa4096'), 'rsa4096');
if ($engine->is_supported(enigma_driver::SUPPORT_ECC)) {
$select->add($this->enigma->gettext('ecckeypair'), 'ecc');
}
$table->add('title', html::label('key-type', rcube::Q($this->enigma->gettext('newkeytype'))));
$table->add(null, $select->show());
// Password and confirm password
$table->add('title', html::label('key-pass', rcube::Q($this->enigma->gettext('newkeypass'))));
$table->add(null, rcube_output::get_edit_field('password', '', [
'id' => 'key-pass',
'size' => $attrib['size'],
'required' => true,
'autocomplete' => 'new-password',
'oninput' => "this.type = this.value.length ? 'password' : 'text'",
], 'text')
);
$table->add('title', html::label('key-pass-confirm', rcube::Q($this->enigma->gettext('newkeypassconfirm'))));
$table->add(null, rcube_output::get_edit_field('password-confirm', '', [
'id' => 'key-pass-confirm',
'size' => $attrib['size'],
'required' => true,
'autocomplete' => 'new-password',
'oninput' => "this.type = this.value.length ? 'password' : 'text'",
], 'text')
);
$this->rc->output->add_gui_object('keyform', $attrib['id']);
$this->rc->output->add_label('enigma.keygenerating', 'enigma.formerror',
'enigma.passwordsdiffer', 'enigma.keygenerateerror', 'enigma.noidentselected',
'enigma.keygennosupport');
return $this->rc->output->form_tag([], $table->show($attrib));
}
/**
* Key deleting
*/
private function key_delete()
{
$keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST);
$engine = $this->enigma->load_engine();
foreach ((array) $keys as $key) {
$res = $engine->delete_key($key);
if ($res !== true) {
$this->rc->output->show_message('enigma.keyremoveerror', 'error');
$this->rc->output->command('enigma_list');
$this->rc->output->send();
}
}
$this->rc->output->command('enigma_list');
$this->rc->output->show_message('enigma.keyremovesuccess', 'confirmation');
$this->rc->output->send();
}
/**
* Init compose UI (add task button and the menu)
*/
private function compose_ui()
{
$this->add_css();
$this->rc->output->add_label('enigma.sendunencrypted');
// Elastic skin (or a skin based on it)
if (array_key_exists('elastic', (array) $this->rc->output->skins)) {
$this->enigma->api->add_content($this->compose_ui_options(), 'composeoptions');
}
// other skins
else {
// Options menu button
$this->enigma->add_button([
'type' => 'link',
'command' => 'plugin.enigma',
'onclick' => "rcmail.command('menu-open', 'enigmamenu', event.target, event)",
'class' => 'button enigma',
'title' => 'encryptionoptions',
'label' => 'encryption',
'domain' => $this->enigma->ID,
'width' => 32,
'height' => 32,
'aria-owns' => 'enigmamenu',
'aria-haspopup' => 'true',
'aria-expanded' => 'false',
], 'toolbar'
);
// Options menu contents
$this->rc->output->add_footer($this->compose_ui_options(true));
}
}
/**
* Init compose UI (add task button and the menu)
*/
private function compose_ui_options($wrap = false)
{
$locks = (array) $this->rc->config->get('enigma_options_lock');
$chbox = new html_checkbox(['value' => 1]);
$out = html::div('form-group form-check row',
html::label(['for' => 'enigmasignopt', 'class' => 'col-form-label col-6'],
rcube::Q($this->enigma->gettext('signmsg'))
)
. html::div('form-check col-6',
$chbox->show($this->rc->config->get('enigma_sign_all') ? 1 : 0, [
'name' => '_enigma_sign',
'id' => 'enigmasignopt',
'class' => 'form-check-input',
'disabled' => in_array('sign', $locks),
])
)
);
$out .= html::div('form-group form-check row',
html::label(['for' => 'enigmaencryptopt', 'class' => 'col-form-label col-6'],
rcube::Q($this->enigma->gettext('encryptmsg'))
)
. html::div('form-check col-6',
$chbox->show($this->rc->config->get('enigma_encrypt_all') ? 1 : 0, [
'name' => '_enigma_encrypt',
'id' => 'enigmaencryptopt',
'class' => 'form-check-input',
'disabled' => in_array('encrypt', $locks),
])
)
);
$out .= html::div('form-group form-check row',
html::label(['for' => 'enigmaattachpubkeyopt', 'class' => 'col-form-label col-6'],
rcube::Q($this->enigma->gettext('attachpubkeymsg'))
)
. html::div('form-check col-6',
$chbox->show($this->rc->config->get('enigma_attach_pubkey') ? 1 : 0, [
'name' => '_enigma_attachpubkey',
'id' => 'enigmaattachpubkeyopt',
'class' => 'form-check-input',
'disabled' => in_array('pubkey', $locks),
])
)
);
if (!$wrap) {
return $out;
}
return html::div(['id' => 'enigmamenu', 'class' => 'popupmenu'], $out);
}
/**
* Handler for message_body_prefix hook.
* Called for every displayed (content) part of the message.
* Adds infobox about signature verification and/or decryption
* status above the body.
*
* @param array $p Original parameters
*
* @return array Modified parameters
*/
function status_message($p)
{
// skip: not a message part
if ($p['part'] instanceof rcube_message) {
return $p;
}
// skip: message has no signed/encoded content
if (!$this->enigma->engine) {
return $p;
}
$engine = $this->enigma->engine;
$part_id = $p['part']->mime_id;
$messages = [];
// Decryption status
if (($found = $this->find_part_id($part_id, $engine->decryptions)) !== null
&& !empty($engine->decryptions[$found])
) {
$status = $engine->decryptions[$found];
$attach_scripts = true;
// show the message only once
unset($engine->decryptions[$found]);
// display status info
$attrib = ['id' => 'enigma-message'];
if ($status instanceof enigma_error) {
$attrib['class'] = 'boxerror enigmaerror encrypted';
$code = $status->getCode();
if ($code == enigma_error::KEYNOTFOUND) {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->enigma->gettext('decryptnokey')));
}
else if ($code == enigma_error::BADPASS) {
$missing = $status->getData('missing');
$label = 'decrypt' . (!empty($missing) ? 'no' : 'bad') . 'pass';
$msg = rcube::Q($this->enigma->gettext($label));
$this->password_prompt($status);
}
else if ($code == enigma_error::NOMDC) {
$msg = rcube::Q($this->enigma->gettext('decryptnomdc'));
}
else {
$msg = rcube::Q($this->enigma->gettext('decrypterror'));
}
}
else if ($status === enigma_engine::ENCRYPTED_PARTIALLY) {
$attrib['class'] = 'boxwarning enigmawarning encrypted';
$msg = rcube::Q($this->enigma->gettext('decryptpartial'));
}
else {
$attrib['class'] = 'boxconfirmation enigmanotice encrypted';
$msg = rcube::Q($this->enigma->gettext('decryptok'));
}
$attrib['msg'] = $msg;
$messages[] = $attrib;
}
// Signature verification status
if (($found = $this->find_part_id($part_id, $engine->signatures)) !== null
&& !empty($engine->signatures[$found])
) {
$sig = $engine->signatures[$found];
$attach_scripts = true;
// show the message only once
unset($engine->signatures[$found]);
// display status info
$attrib = ['id' => 'enigma-message'];
if ($sig instanceof enigma_signature) {
$sender = $sig->get_sender($engine, $p['message'], $part_id);
if ($sig->valid === enigma_error::UNVERIFIED) {
$attrib['class'] = 'boxwarning enigmawarning signed';
$msg = str_replace('$sender', $sender, $this->enigma->gettext('sigunverified'));
$msg = str_replace('$keyid', $sig->id, $msg);
$msg = rcube::Q($msg);
}
else if ($sig->valid) {
$attrib['class'] = ($sig->partial ? 'boxwarning enigmawarning' : 'boxconfirmation enigmanotice') . ' signed';
$label = 'sigvalid' . ($sig->partial ? 'partial' : '');
$msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext($label)));
}
else {
$attrib['class'] = 'boxwarning enigmawarning signed';
if ($sender) {
$msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext('siginvalid')));
}
else {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->id),
$this->enigma->gettext('signokey')));
}
}
}
else if ($sig && $sig->getCode() == enigma_error::KEYNOTFOUND) {
$attrib['class'] = 'boxwarning enigmawarning signed';
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
$this->enigma->gettext('signokey')));
}
else {
$attrib['class'] = 'boxwarning enigmaerror signed';
$msg = rcube::Q($this->enigma->gettext('sigerror'));
}
$attrib['msg'] = $msg;
$messages[] = $attrib;
}
if ($count = count($messages)) {
if ($count == 2 && $messages[0]['class'] == $messages[1]['class']) {
$p['prefix'] .= html::div($messages[0], $messages[0]['msg'] . ' ' . $messages[1]['msg']);
}
else {
foreach ($messages as $msg) {
$p['prefix'] .= html::div($msg, $msg['msg']);
}
}
}
if (!empty($attach_scripts)) {
// add css and js script
$this->add_css();
$this->add_js();
}
return $p;
}
/**
* Handler for message_load hook.
* Check message bodies and attachments for keys/certs.
*/
function message_load($p)
{
$engine = $this->enigma->load_engine();
// handle keys/certs in attachments
foreach ((array) $p['object']->attachments as $attachment) {
if ($engine->is_keys_part($attachment)) {
$this->keys_parts[] = $attachment->mime_id;
}
}
// the same with message bodies
foreach ((array) $p['object']->parts as $part) {
if ($engine->is_keys_part($part)) {
$this->keys_parts[] = $part->mime_id;
$this->keys_bodies[] = $part->mime_id;
}
}
// @TODO: inline PGP keys
if ($this->keys_parts) {
$this->enigma->add_texts('localization');
}
return $p;
}
/**
* Handler for template_object_messagebody hook.
* This callback function adds a box below the message content
* if there is a key/cert attachment available
*/
function message_output($p)
{
foreach ($this->keys_parts as $part) {
// remove part's body
if (in_array($part, $this->keys_bodies)) {
$p['content'] = '';
}
// add box above the message body
$p['content'] = html::p(['class' => 'enigmaattachment boxinformation aligned-buttons'],
html::span(null, rcube::Q($this->enigma->gettext('keyattfound'))) .
html::tag('button', [
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".enigma_import_attachment('".rcube::JQ($part)."')",
'title' => $this->enigma->gettext('keyattimport'),
'class' => 'import',
], rcube::Q($this->rc->gettext('import'))
)
) . $p['content'];
$attach_scripts = true;
}
if (!empty($attach_scripts)) {
// add css and js script
$this->add_css();
$this->add_js();
}
return $p;
}
/**
* Handle message_ready hook (encryption/signing/attach public key)
*/
function message_ready($p)
{
// The message might have been already encrypted by Mailvelope
if (strpos($p['message']->getParam('ctype'), 'multipart/encrypted') === 0) {
return $p;
}
$savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']);
$sign_enable = (bool) rcube_utils::get_input_value('_enigma_sign', rcube_utils::INPUT_POST);
$encrypt_enable = (bool) rcube_utils::get_input_value('_enigma_encrypt', rcube_utils::INPUT_POST);
$pubkey_enable = (bool) rcube_utils::get_input_value('_enigma_attachpubkey', rcube_utils::INPUT_POST);
$locks = (array) $this->rc->config->get('enigma_options_lock');
if (in_array('sign', $locks)) {
$sign_enable = (bool) $this->rc->config->get('enigma_sign_all');
}
if (in_array('encrypt', $locks)) {
$encrypt_enable = (bool) $this->rc->config->get('enigma_encrypt_all');
}
if (in_array('pubkey', $locks)) {
$pubkey_enable = (bool) $this->rc->config->get('enigma_attach_pubkey');
}
if (!$savedraft && $pubkey_enable) {
$engine = $this->enigma->load_engine();
$engine->attach_public_key($p['message']);
}
$mode = null;
$status = null;
if ($encrypt_enable) {
$engine = $this->enigma->load_engine();
$mode = !$savedraft && $sign_enable ? enigma_engine::ENCRYPT_MODE_SIGN : null;
$status = $engine->encrypt_message($p['message'], $mode, $savedraft);
$mode = 'encrypt';
}
else if (!$savedraft && $sign_enable) {
$engine = $this->enigma->load_engine();
$status = $engine->sign_message($p['message'], enigma_engine::SIGN_MODE_MIME);
$mode = 'sign';
}
if ($mode && ($status instanceof enigma_error)) {
$code = $status->getCode();
- $vars = array();
+ $vars = [];
if ($code == enigma_error::KEYNOTFOUND) {
if ($email = $status->getData('missing')) {
$vars = ['email' => $email];
$msg = 'enigma.' . $mode . 'nokey';
}
else {
$msg = 'enigma.' . ($encrypt_enable ? 'encryptnoprivkey' : 'signnokey');
}
}
else if ($code == enigma_error::BADPASS) {
$this->password_prompt($status);
}
else {
$msg = 'enigma.' . $mode . 'error';
}
if (!empty($msg)) {
if (!empty($vars) && !empty($vars['email'])) {
$this->rc->output->command('enigma_key_not_found', [
'email' => $vars['email'],
'text' => $this->rc->gettext(['name' => $msg, 'vars' => $vars]),
'title' => $this->enigma->gettext('keynotfound'),
'button' => $this->enigma->gettext('findkey'),
'mode' => $mode,
]);
}
else {
$this->rc->output->show_message($msg, 'error', $vars);
}
}
$this->rc->output->send('iframe');
}
return $p;
}
/**
* Handler for message_compose_body hook
* Display error when the message cannot be encrypted
* and provide a way to try again with a password.
*/
function message_compose($p)
{
$engine = $this->enigma->load_engine();
// skip: message has no signed/encoded content
if (!$this->enigma->engine) {
return $p;
}
$engine = $this->enigma->engine;
$locks = (array) $this->rc->config->get('enigma_options_lock');
// Decryption status
foreach ($engine->decryptions as $status) {
if ($status instanceof enigma_error) {
$code = $status->getCode();
if ($code == enigma_error::BADPASS) {
$this->password_prompt($status, ['compose-init' => true]);
return $p;
}
if ($code == enigma_error::KEYNOTFOUND) {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->enigma->gettext('decryptnokey')));
}
else {
$msg = rcube::Q($this->enigma->gettext('decrypterror'));
}
}
}
if (!empty($msg)) {
$this->rc->output->show_message($msg, 'error');
}
// Check sign/ecrypt options for signed/encrypted drafts
if (!in_array('encrypt', $locks)) {
$this->rc->output->set_env('enigma_force_encrypt', !empty($engine->decryptions));
}
if (!in_array('sign', $locks)) {
$this->rc->output->set_env('enigma_force_sign', !empty($engine->signatures));
}
return $p;
}
/**
* Handler for keys/certs import request action
*/
function import_file()
{
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
$engine = $this->enigma->load_engine();
if ($uid && $mime_id) {
// Note: we get the attachment body via rcube_message class
// to support keys inside encrypted messages (#5285)
$message = new rcube_message($uid, $mbox);
// Check if we don't need to ask for password again
foreach ($engine->decryptions as $status) {
if ($status instanceof enigma_error) {
if ($status->getCode() == enigma_error::BADPASS) {
$this->password_prompt($status, [
'input_uid' => $uid,
'input_mbox' => $mbox,
'input_part' => $mime_id,
'input_task' => 'mail',
'input_action' => 'plugin.enigmaimport',
'action' => '?',
'iframe' => true,
]);
$this->rc->output->send($this->rc->output->type == 'html' ? 'iframe' : null);
return;
}
}
}
if ($engine->is_keys_part($message->mime_parts[$mime_id])) {
$part = $message->get_part_body($mime_id);
}
}
if (!empty($part) && is_array($result = $engine->import_key($part))) {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
['new' => $result['imported'], 'old' => $result['unchanged']]);
}
else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
$this->rc->output->send($this->rc->output->type == 'html' ? 'iframe' : null);
}
/**
* Check if the part or its parent exists in the array
* of decryptions/signatures. Returns found ID.
*/
private function find_part_id($part_id, $data)
{
$ids = explode('.', $part_id);
$i = 0;
$count = count($ids);
while ($i < $count && strlen($part = implode('.', array_slice($ids, 0, ++$i)))) {
if (array_key_exists($part, $data)) {
return $part;
}
}
}
}
diff --git a/plugins/krb_authentication/config.inc.php.dist b/plugins/krb_authentication/config.inc.php.dist
index 975cacb85..b1c1ffb9e 100644
--- a/plugins/krb_authentication/config.inc.php.dist
+++ b/plugins/krb_authentication/config.inc.php.dist
@@ -1,20 +1,20 @@
<?php
// Kerberos/GSSAPI Authentication Plugin options
// ---------------------------------------------
// Default mail host to log-in using user/password from HTTP Authentication.
// This is useful if the users are free to choose arbitrary mail hosts (or
// from a list), but have one host they usually want to log into.
// Unlike $config['default_host'] this must be a string!
$config['krb_authentication_host'] = '';
// GSSAPI security context.
// Single value or an array with per-protocol values. Example:
//
-// $config['krb_authentication_context'] = array(
+// $config['krb_authentication_context'] = [
// 'imap' => 'imap/host.fqdn@REALM.NAME',
// 'smtp' => 'smtp/host.fqdn@REALM.NAME',
// 'sieve' => 'sieve/host.fqdn@REALM.NAME',
-// );
+// ];
$config['krb_authentication_context'] = 'principal@REALM.NAME';
diff --git a/plugins/markasjunk/drivers/cmd_learn.php b/plugins/markasjunk/drivers/cmd_learn.php
index 4d81c536a..83ee0abae 100644
--- a/plugins/markasjunk/drivers/cmd_learn.php
+++ b/plugins/markasjunk/drivers/cmd_learn.php
@@ -1,117 +1,117 @@
<?php
/**
* Command line learn driver
*
* @version 3.0
*
* @author Philip Weir
* Patched by Julien Vehent to support DSPAM
* Enhanced support for DSPAM by Stevan Bajic <stevan@bajic.ch>
*
* Copyright (C) 2009-2018 Philip Weir
*
* This driver is part of the MarkASJunk plugin for Roundcube.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Roundcube. If not, see https://www.gnu.org/licenses/.
*/
class markasjunk_cmd_learn
{
public function spam($uids, $src_mbox, $dst_mbox)
{
$this->_do_salearn($uids, true, $src_mbox);
}
public function ham($uids, $src_mbox, $dst_mbox)
{
$this->_do_salearn($uids, false, $src_mbox);
}
private function _do_salearn($uids, $spam, $src_mbox)
{
$rcube = rcube::get_instance();
$temp_dir = realpath($rcube->config->get('temp_dir'));
$command = $rcube->config->get($spam ? 'markasjunk_spam_cmd' : 'markasjunk_ham_cmd');
$debug = $rcube->config->get('markasjunk_debug');
if (!$command) {
return;
}
// backwards compatibility %xds removed in markasjunk v1.12
$command = str_replace('%xds', '%h:x-dspam-signature', $command);
$command = str_replace('%u', $_SESSION['username'], $command);
$command = str_replace('%l', $rcube->user->get_username('local'), $command);
$command = str_replace('%d', $rcube->user->get_username('domain'), $command);
if (strpos($command, '%i') !== false) {
$identity = $rcube->user->get_identity();
$command = str_replace('%i', $identity['email'], $command);
}
foreach ($uids as $uid) {
// reset command for next message
$tmp_command = $command;
if (strpos($tmp_command, '%s') !== false) {
$message = new rcube_message($uid);
$tmp_command = str_replace('%s', escapeshellarg($message->sender['mailto']), $tmp_command);
}
if (strpos($command, '%h') !== false) {
$storage = $rcube->get_storage();
$storage->check_connection();
$storage->conn->select($src_mbox);
preg_match_all('/%h:([\w_-]+)/', $tmp_command, $header_names, PREG_SET_ORDER);
foreach ($header_names as $header) {
$val = null;
- if ($msg = $storage->conn->fetchHeader($src_mbox, $uid, true, false, array($header[1]))) {
+ if ($msg = $storage->conn->fetchHeader($src_mbox, $uid, true, false, [$header[1]])) {
$val = !empty($msg->{$header[1]}) ? $msg->{$header[1]} : $msg->others[$header[1]];
}
if (!empty($val)) {
$tmp_command = str_replace($header[0], escapeshellarg($val), $tmp_command);
}
else {
if ($debug) {
rcube::write_log('markasjunk', 'header ' . $header[1] . ' not found in message ' . $src_mbox . '/' . $uid);
}
continue 2;
}
}
}
if (strpos($command, '%f') !== false) {
$tmpfname = tempnam($temp_dir, 'rcmSALearn');
file_put_contents($tmpfname, $rcube->storage->get_raw_body($uid));
$tmp_command = str_replace('%f', escapeshellarg($tmpfname), $tmp_command);
}
$output = shell_exec($tmp_command);
if ($debug) {
if ($output) {
$tmp_command .= "\n$output";
}
rcube::write_log('markasjunk', $tmp_command);
}
if (isset($tmpfname)) {
unlink($tmpfname);
}
}
}
}
diff --git a/plugins/password/drivers/cpanel_webmail.php b/plugins/password/drivers/cpanel_webmail.php
index d08170c26..47e5896f5 100644
--- a/plugins/password/drivers/cpanel_webmail.php
+++ b/plugins/password/drivers/cpanel_webmail.php
@@ -1,146 +1,146 @@
<?php
/**
* cPanel Webmail Password Driver
*
* It uses Cpanel's Webmail UAPI to change the users password.
*
* This driver has been tested successfully with Digital Pacific hosting.
*
* @author Maikel Linke <maikel@email.org.au>
*
* Copyright (C) The Roundcube Dev Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
class rcube_cpanel_webmail_password
{
/**
* Changes the user's password. It is called by password.php.
* See "Driver API" README and password.php for the interface details.
*
* @param string $curpas current (old) password
* @param string $newpass new requested password
*
* @return mixed int code or assoc array with 'code' and 'message', see
* "Driver API" README and password.php
*/
public function save($curpas, $newpass)
{
$url = self::url();
$user = password::username();
$userpwd = "$user:$curpas";
$data = [
'email' => password::username('%l'),
'password' => $newpass
];
$response = $this->curl_auth_post($userpwd, $url, $data);
return self::decode_response($response);
}
/**
* Provides the UAPI URL of the Email::passwd_pop function.
*
* @return string HTTPS URL
*/
public static function url()
{
$config = rcmail::get_instance()->config;
$storage_host = $_SESSION['storage_host'];
$host = $config->get('password_cpanel_webmail_host', $storage_host);
$port = $config->get('password_cpanel_webmail_port', 2096);
return "https://$host:$port/execute/Email/passwd_pop";
}
/**
* Converts a UAPI response to a password driver response.
*
* @param string $response JSON response by the Cpanel UAPI
*
* @return mixed response code or array, see <code>save</code>
*/
public static function decode_response($response)
{
if (!$response) {
return PASSWORD_CONNECT_ERROR;
}
// $result should be `null` or `stdClass` object
$result = json_decode($response);
// The UAPI may return HTML instead of JSON on missing authentication
if ($result && isset($result->status) && $result->status === 1) {
return PASSWORD_SUCCESS;
}
if ($result && isset($result->errors) && is_array($result->errors) && count($result->errors) > 0) {
return [
'code' => PASSWORD_ERROR,
'message' => $result->errors[0],
];
}
return PASSWORD_ERROR;
}
/**
* Post data to the given URL using basic authentication.
*
* Example:
*
* <code>
- * curl_auth_post('john:Secr3t', 'https://example.org', array(
+ * curl_auth_post('john:Secr3t', 'https://example.org', [
* 'param' => 'value',
* 'param' => 'value'
- * ));
+ * ]);
* </code>
*
* @param string $userpwd user name and password separated by a colon
* <code>:</code>
* @param string $url the URL to post data to
* @param array $postdata the data to post
*
* @return string|false The body of the reply, False on error
*/
private function curl_auth_post($userpwd, $url, $postdata)
{
$ch = curl_init();
$postfields = http_build_query($postdata, '', '&');
// see http://php.net/manual/en/function.curl-setopt.php
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 131072);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
curl_setopt($ch, CURLOPT_USERPWD, $userpwd);
$result = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if ($result === false) {
rcube::raise_error("curl error: $error", true, false);
}
return $result;
}
}
diff --git a/plugins/password/drivers/pwned.php b/plugins/password/drivers/pwned.php
index 0152bd453..2624fbd67 100644
--- a/plugins/password/drivers/pwned.php
+++ b/plugins/password/drivers/pwned.php
@@ -1,225 +1,225 @@
<?php
/**
* Have I Been Pwned Password Strength Driver
*
* Driver to check passwords using HIBP:
* https://haveibeenpwned.com/Passwords
*
* This driver will return a strength of:
* 3: if the password WAS NOT found in HIBP
* 1: if the password WAS found in HIBP
* 2: if there was an ERROR retrieving data.
*
* To use this driver, configure (in ../config.inc.php):
*
* $config['password_strength_driver'] = 'pwned';
* $config['password_minimum_score'] = 3;
*
* Set the minimum score to 3 if you want to make sure that all
* passwords are successfully checked against HIBP (recommended).
*
* Set it to 2 if you still want to accept passwords in case a
* HIBP check fails for some (technical) reason.
*
* Setting the minimum score to 1 or less effectively renders
* the checks useless, as all passwords would be accepted.
* Setting it to 4 or more will effectively reject all passwords.
*
* This driver will only return a maximum score of 3 because not
* being listed in HIBP does not necessarily mean that the
* password is a good one. It is therefore recommended to also
* configure a minimum length for the password.
*
* Background reading (don't worry, your passwords are not sent anywhere):
* https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/#cloudflareprivacyandkanonymity
*
* @version 1.0
* @author Christoph Langguth
*
* Copyright (C) The Roundcube Dev Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
class rcube_pwned_password
{
// API URL. Note: the trailing slash is mandatory.
const API_URL = 'https://api.pwnedpasswords.com/range/';
// See https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
const ENHANCED_PRIVACY_CURL = 1;
// Score constants, these directly correspond to the score that is returned.
const SCORE_LISTED = 1;
const SCORE_ERROR = 2;
const SCORE_NOT_LISTED = 3;
/**
* Rule description.
*
* @return array human-readable description of the check rule.
*/
function strength_rules()
{
$rc = rcmail::get_instance();
return [$rc->gettext('password.pwned_mustnotbedisclosed')];
}
/**
* Password strength check.
* Return values:
* 1 - if password is definitely compromised.
* 2 - if status for password can't be determined (network failures etc.)
* 3 - if password is not publicly known to be compromised.
*
* @param string $passwd Password
*
* @return array password score (1 to 3) and (optional) reason message
*/
function check_strength($passwd)
{
$score = $this->check_pwned($passwd);
$message = null;
if ($score !== self::SCORE_NOT_LISTED) {
$rc = rcmail::get_instance();
if ($score === self::SCORE_LISTED) {
$message = $rc->gettext('password.pwned_isdisclosed');
}
else {
$message = $rc->gettext('password.pwned_fetcherror');
}
}
return [$score, $message];
}
/**
* Check password using HIBP.
*
* @param string $passwd
*
* @return int score, one of the SCORE_* constants (between 1 and 3).
*/
function check_pwned($passwd)
{
// initialize with error score
$result = self::SCORE_ERROR;
if (!$this->can_retrieve()) {
// Log the fact that we cannot check because of configuration error.
rcube::raise_error("Need curl or allow_url_fopen to check password strength with 'pwned'", true, true);
}
else {
list($prefix, $suffix) = $this->hash_split($passwd);
$suffixes = $this->retrieve_suffixes(self::API_URL . $prefix);
if ($suffixes) {
$result = $this->check_suffix_in_list($suffix, $suffixes);
}
}
return $result;
}
function hash_split($passwd)
{
$hash = strtolower(sha1($passwd));
$prefix = substr($hash, 0, 5);
$suffix = substr($hash, 5);
return [$prefix, $suffix];
}
function can_retrieve()
{
return $this->can_curl() || $this->can_fopen();
}
function can_curl()
{
return function_exists('curl_init');
}
function can_fopen()
{
return ini_get('allow_url_fopen');
}
function retrieve_suffixes($url)
{
if ($this->can_curl()) {
return $this->retrieve_curl($url);
}
else {
return $this->retrieve_fopen($url);
}
}
function retrieve_curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if (self::ENHANCED_PRIVACY_CURL == 1) {
- curl_setopt($ch, CURLOPT_HTTPHEADER, array('Add-Padding: true'));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ['Add-Padding: true']);
}
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
function retrieve_fopen($url)
{
$output = '';
$ch = fopen($url, 'r');
while (!feof($ch)) {
$output .= fgets($ch);
}
fclose($ch);
return $output;
}
function check_suffix_in_list($candidate, $list)
{
// initialize to error in case there are no lines at all
$result = self::SCORE_ERROR;
foreach (preg_split('/[\r\n]+/', $list) as $line) {
$line = strtolower($line);
if (preg_match('/^([0-9a-f]{35}):(\d+)$/', $line, $matches)) {
if ($matches[2] > 0 && $matches[1] === $candidate) {
// more than 0 occurrences, and suffix matches
// -> password is compromised
return self::SCORE_LISTED;
}
// valid line, not matching the current password
$result = self::SCORE_NOT_LISTED;
}
else {
// invalid line
return self::SCORE_ERROR;
}
}
return $result;
}
}
diff --git a/plugins/redundant_attachments/redundant_attachments.php b/plugins/redundant_attachments/redundant_attachments.php
index 172bc8914..783c235d7 100644
--- a/plugins/redundant_attachments/redundant_attachments.php
+++ b/plugins/redundant_attachments/redundant_attachments.php
@@ -1,238 +1,240 @@
<?php
/**
* Redundant attachments
*
* This plugin provides a redundant storage for temporary uploaded
* attachment files. They are stored in both the database backend
* as well as on the local file system.
*
* It provides also memcache/redis store as a fallback (see config file).
*
* This plugin relies on the core filesystem_attachments plugin
* and combines it with the functionality of the database_attachments plugin.
*
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) The Roundcube Dev Team
* Copyright (C) Kolab Systems AG
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
require_once(RCUBE_PLUGINS_DIR . 'filesystem_attachments/filesystem_attachments.php');
class redundant_attachments extends filesystem_attachments
{
// A prefix for the cache key used in the session and in the key field of the cache table
const PREFIX = "ATTACH";
// rcube_cache instance for SQL DB
private $cache;
// rcube_cache instance for memcache/redis
private $mem_cache;
private $loaded;
/**
* Loads plugin configuration and initializes cache object(s)
*/
private function _load_drivers()
{
if ($this->loaded) {
return;
}
$rcmail = rcube::get_instance();
// load configuration
$this->load_config();
$ttl = 12 * 60 * 60; // 12 hours
$ttl = $rcmail->config->get('redundant_attachments_cache_ttl', $ttl);
$fallback = $rcmail->config->get('redundant_attachments_fallback');
$prefix = self::PREFIX;
if ($id = session_id()) {
$prefix .= $id;
}
if ($fallback === null) {
$fallback = $rcmail->config->get('redundant_attachments_memcache') ? 'memcache' : null; // BC
}
// Init SQL cache (disable cache data serialization)
$this->cache = $rcmail->get_cache($prefix, 'db', $ttl, false, true);
// Init memcache/redis (fallback) cache
if ($fallback) {
$this->mem_cache = $rcmail->get_cache($prefix, $fallback, $ttl, false, true);
}
$this->loaded = true;
}
/**
* Helper method to generate a unique key for the given attachment file
*/
private function _key($args)
{
- $uname = $args['path'] ?: $args['name'];
+ $uname = !empty($args['path']) ? $args['path'] : $args['name'];
+
return $args['group'] . md5(microtime() . $uname . $_SESSION['user_id']);
}
/**
* Save a newly uploaded attachment
*/
function upload($args)
{
$args = parent::upload($args);
$this->_load_drivers();
$key = $this->_key($args);
$data = base64_encode(file_get_contents($args['path']));
$status = $this->cache->set($key, $data);
if (!$status && $this->mem_cache) {
$status = $this->mem_cache->set($key, $data);
}
if ($status) {
$args['id'] = $key;
$args['status'] = true;
}
return $args;
}
/**
* Save an attachment from a non-upload source (draft or forward)
*/
function save($args)
{
$args = parent::save($args);
$this->_load_drivers();
- $data = $args['path'] ? file_get_contents($args['path']) : $args['data'];
+ $data = !empty($args['path']) ? file_get_contents($args['path']) : $args['data'];
$args['data'] = null;
$key = $this->_key($args);
$data = base64_encode($data);
$status = $this->cache->set($key, $data);
if (!$status && $this->mem_cache) {
$status = $this->mem_cache->set($key, $data);
}
if ($status) {
$args['id'] = $key;
$args['status'] = true;
}
return $args;
}
/**
* Remove an attachment from storage
* This is triggered by the remove attachment button on the compose screen
*/
function remove($args)
{
parent::remove($args);
$this->_load_drivers();
$status = $this->cache->remove($args['id']);
if (!$status && $this->mem_cache) {
$status = $this->cache->remove($args['id']);
}
// we cannot trust the result of any of the methods above
// assume true, attachments will be removed on cleanup
$args['status'] = true;
return $args;
}
/**
* When composing an html message, image attachments may be shown
* For this plugin, $this->get() will check the file and
* return it's contents
*/
function display($args)
{
return $this->get($args);
}
/**
* When displaying or sending the attachment the file contents are fetched
* using this method. This is also called by the attachment_display hook.
*/
function get($args)
{
// attempt to get file from local file system
$args = parent::get($args);
- if ($args['path'] && ($args['status'] = file_exists($args['path'])))
- return $args;
+ if (!empty($args['path']) && ($args['status'] = file_exists($args['path']))) {
+ return $args;
+ }
$this->_load_drivers();
// fetch from database if not found on FS
$data = $this->cache->get($args['id']);
// fetch from memcache if not found on FS and DB
if (($data === false || $data === null) && $this->mem_cache) {
$data = $this->mem_cache->get($args['id']);
}
if ($data) {
$args['data'] = base64_decode($data);
$args['status'] = true;
}
return $args;
}
/**
* Delete all temp files associated with this user
*/
function cleanup($args)
{
$this->_load_drivers();
if ($this->cache) {
$this->cache->remove($args['group'], true);
}
if ($this->mem_cache) {
$this->mem_cache->remove($args['group'], true);
}
parent::cleanup($args);
$args['status'] = true;
return $args;
}
}
diff --git a/plugins/show_additional_headers/show_additional_headers.php b/plugins/show_additional_headers/show_additional_headers.php
index 87de51fca..1a48f8b76 100644
--- a/plugins/show_additional_headers/show_additional_headers.php
+++ b/plugins/show_additional_headers/show_additional_headers.php
@@ -1,50 +1,56 @@
<?php
/**
* Show additional message headers
*
* Proof-of-concept plugin which will fetch additional headers
* and display them in the message view.
*
* Enable the plugin in config.inc.php and add your desired headers:
- * $config['show_additional_headers'] = array('User-Agent');
+ * $config['show_additional_headers'] = ['User-Agent'];
*
* @author Thomas Bruederli
* @license GNU GPLv3+
*/
class show_additional_headers extends rcube_plugin
{
- public $task = 'mail';
-
- function init()
- {
- $rcmail = rcmail::get_instance();
- if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
- $this->add_hook('storage_init', array($this, 'storage_init'));
- $this->add_hook('message_headers_output', array($this, 'message_headers'));
- } else if ($rcmail->action == '') {
- // with enabled_caching we're fetching additional headers before show/preview
- $this->add_hook('storage_init', array($this, 'storage_init'));
+ public $task = 'mail';
+
+ function init()
+ {
+ $rcmail = rcmail::get_instance();
+
+ if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
+ $this->add_hook('storage_init', [$this, 'storage_init']);
+ $this->add_hook('message_headers_output', [$this, 'message_headers']);
+ }
+ else if ($rcmail->action == '') {
+ // with enabled_caching we're fetching additional headers before show/preview
+ $this->add_hook('storage_init', [$this, 'storage_init']);
+ }
}
- }
-
- function storage_init($p)
- {
- $rcmail = rcmail::get_instance();
- if ($add_headers = (array)$rcmail->config->get('show_additional_headers', array()))
- $p['fetch_headers'] = trim($p['fetch_headers'].' ' . strtoupper(join(' ', $add_headers)));
-
- return $p;
- }
-
- function message_headers($p)
- {
- $rcmail = rcmail::get_instance();
- foreach ((array)$rcmail->config->get('show_additional_headers', array()) as $header) {
- if ($value = $p['headers']->get($header))
- $p['output'][$header] = array('title' => $header, 'value' => $value);
+
+ function storage_init($p)
+ {
+ $rcmail = rcmail::get_instance();
+
+ if ($add_headers = (array) $rcmail->config->get('show_additional_headers', [])) {
+ $p['fetch_headers'] = trim($p['fetch_headers']. ' ' . strtoupper(join(' ', $add_headers)));
+ }
+
+ return $p;
}
- return $p;
- }
+ function message_headers($p)
+ {
+ $rcmail = rcmail::get_instance();
+
+ foreach ((array) $rcmail->config->get('show_additional_headers', []) as $header) {
+ if ($value = $p['headers']->get($header)) {
+ $p['output'][$header] = ['title' => $header, 'value' => $value];
+ }
+ }
+
+ return $p;
+ }
}
diff --git a/plugins/show_additional_headers/tests/ShowAdditionalHeaders.php b/plugins/show_additional_headers/tests/ShowAdditionalHeaders.php
index d2a84a709..36dbd74a1 100644
--- a/plugins/show_additional_headers/tests/ShowAdditionalHeaders.php
+++ b/plugins/show_additional_headers/tests/ShowAdditionalHeaders.php
@@ -1,23 +1,22 @@
<?php
class ShowAdditionalHeaders_Plugin extends PHPUnit\Framework\TestCase
{
function setUp()
{
include_once __DIR__ . '/../show_additional_headers.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new show_additional_headers($rcube->plugins);
$this->assertInstanceOf('show_additional_headers', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
}
-
diff --git a/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php b/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
index d8bab8990..e4ba6ef4b 100644
--- a/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
+++ b/plugins/squirrelmail_usercopy/squirrelmail_usercopy.php
@@ -1,270 +1,280 @@
<?php
/**
* Copy a new users identities and contacts from a nearby Squirrelmail installation
*
* @version 1.6
* @author Thomas Bruederli, Johannes Hessellund, pommi, Thomas Lueder
*/
class squirrelmail_usercopy extends rcube_plugin
{
public $task = 'login';
private $prefs = null;
private $identities_level = 0;
- private $abook = array();
+ private $abook = [];
public function init()
{
- $this->add_hook('user_create', array($this, 'create_user'));
- $this->add_hook('identity_create', array($this, 'create_identity'));
+ $this->add_hook('user_create', [$this, 'create_user']);
+ $this->add_hook('identity_create', [$this, 'create_identity']);
}
public function create_user($p)
{
$rcmail = rcmail::get_instance();
// Read plugin's config
$this->initialize();
// read prefs and add email address
$this->read_squirrel_prefs($p['user']);
- if (($this->identities_level == 0 || $this->identities_level == 2)
+
+ if (
+ ($this->identities_level == 0 || $this->identities_level == 2)
&& $rcmail->config->get('squirrelmail_set_alias')
- && $this->prefs['email_address']
+ && !empty($this->prefs['email_address'])
) {
$p['user_email'] = $this->prefs['email_address'];
}
return $p;
}
public function create_identity($p)
{
$rcmail = rcmail::get_instance();
// prefs are set in create_user()
if ($this->prefs) {
- if ($this->prefs['full_name']) {
+ if (!empty($this->prefs['full_name'])) {
$p['record']['name'] = $this->prefs['full_name'];
}
- if (($this->identities_level == 0 || $this->identities_level == 2) && $this->prefs['email_address']) {
+ if (
+ ($this->identities_level == 0 || $this->identities_level == 2)
+ && !empty($this->prefs['email_address'])
+ ) {
$p['record']['email'] = $this->prefs['email_address'];
}
- if ($this->prefs['___signature___']) {
+ if (!empty($this->prefs['___signature___'])) {
$p['record']['signature'] = $this->prefs['___signature___'];
}
- if ($this->prefs['reply_to']) {
+ if (!empty($this->prefs['reply_to'])) {
$p['record']['reply-to'] = $this->prefs['reply_to'];
}
- if (($this->identities_level == 0 || $this->identities_level == 1)
+ if (
+ ($this->identities_level == 0 || $this->identities_level == 1)
&& isset($this->prefs['identities']) && $this->prefs['identities'] > 1
) {
for ($i = 1; $i < $this->prefs['identities']; $i++) {
unset($ident_data);
- $ident_data = array('name' => '', 'email' => ''); // required data
+ $ident_data = ['name' => '', 'email' => '']; // required data
- if ($this->prefs['full_name'.$i]) {
+ if (!empty($this->prefs['full_name'.$i])) {
$ident_data['name'] = $this->prefs['full_name'.$i];
}
- if ($this->identities_level == 0 && $this->prefs['email_address'.$i]) {
+ if ($this->identities_level == 0 && !empty($this->prefs['email_address'.$i])) {
$ident_data['email'] = $this->prefs['email_address'.$i];
}
else {
$ident_data['email'] = $p['record']['email'];
}
- if ($this->prefs['reply_to'.$i]) {
+ if (!empty($this->prefs['reply_to'.$i])) {
$ident_data['reply-to'] = $this->prefs['reply_to'.$i];
}
- if ($this->prefs['___sig'.$i.'___']) {
+ if (!empty($this->prefs['___sig'.$i.'___'])) {
$ident_data['signature'] = $this->prefs['___sig'.$i.'___'];
}
// insert identity
$rcmail->user->insert_identity($ident_data);
}
}
// copy address book
$contacts = $rcmail->get_address_book(null, true);
- $addresses = array();
- $groups = array();
+ $addresses = [];
+ $groups = [];
if ($contacts && !empty($this->abook)) {
foreach ($this->abook as $rec) {
// #1487096: handle multi-address and/or too long items
// #1487858: convert multi-address contacts into groups
$emails = preg_split('/[;,]/', $rec['email'], -1, PREG_SPLIT_NO_EMPTY);
$group_id = null;
// create group for addresses
if (count($emails) > 1) {
- if (!($group_id = $groups[$rec['name']])) {
- if ($group = $contacts->create_group($rec['name'])) {
- $group_id = $group['id'];
- $groups[$rec['name']] = $group_id;
- }
+ if (!empty($groups[$rec['name']])) {
+ $group_id = $groups[$rec['name']];
+ }
+ else if ($group = $contacts->create_group($rec['name'])) {
+ $group_id = $group['id'];
+ $groups[$rec['name']] = $group_id;
}
}
// create contacts
foreach ($emails as $email) {
- if (!($contact_id = $addresses[$email]) && rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
+ $contact_id = null;
+
+ if (!empty($addresses[$email])) {
+ $contact_id = $addresses[$email];
+ }
+ else if (rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
$rec['email'] = rcube_utils::idn_to_utf8($email);
if ($contact_id = $contacts->insert($rec, true)) {
$addresses[$email] = $contact_id;
}
}
- if ($group_id && $contact_id) {
- $contacts->add_to_group($group_id, array($contact_id));
+ if (!empty($group_id) && !empty($contact_id)) {
+ $contacts->add_to_group($group_id, [$contact_id]);
}
}
}
}
// mark identity as complete for following hooks
$p['complete'] = true;
}
return $p;
}
private function initialize()
{
$rcmail = rcmail::get_instance();
// Load plugin's config file
$this->load_config();
// Set identities_level for operations of this plugin
$ilevel = $rcmail->config->get('squirrelmail_identities_level');
if ($ilevel === null) {
$ilevel = $rcmail->config->get('identities_level', 0);
}
$this->identities_level = intval($ilevel);
}
private function read_squirrel_prefs($uname)
{
$rcmail = rcmail::get_instance();
/**** File based backend ****/
if ($rcmail->config->get('squirrelmail_driver') == 'file' && ($srcdir = $rcmail->config->get('squirrelmail_data_dir'))) {
if (($hash_level = $rcmail->config->get('squirrelmail_data_dir_hash_level')) > 0) {
$srcdir = slashify($srcdir).chunk_split(substr(base_convert(crc32($uname), 10, 16), 0, $hash_level), 1, '/');
}
$file_charset = $rcmail->config->get('squirrelmail_file_charset');
$prefsfile = slashify($srcdir) . $uname . '.pref';
$abookfile = slashify($srcdir) . $uname . '.abook';
$sigfile = slashify($srcdir) . $uname . '.sig';
$sigbase = slashify($srcdir) . $uname . '.si';
if (is_readable($prefsfile)) {
- $this->prefs = array();
+ $this->prefs = [];
foreach (file($prefsfile) as $line) {
- list($key, $value) = explode('=', $line);
+ list($key, $value) = rcube_utils::explode('=', $line);
$this->prefs[$key] = $this->convert_charset(rtrim($value), $file_charset);
}
// also read signature file if exists
if (is_readable($sigfile)) {
$sig = file_get_contents($sigfile);
$this->prefs['___signature___'] = $this->convert_charset($sig, $file_charset);
}
if (isset($this->prefs['identities']) && $this->prefs['identities'] > 1) {
for ($i=1; $i < $this->prefs['identities']; $i++) {
// read signature file if exists
if (is_readable($sigbase.$i)) {
$sig = file_get_contents($sigbase.$i);
$this->prefs['___sig'.$i.'___'] = $this->convert_charset($sig, $file_charset);
}
}
}
// parse address book file
if (filesize($abookfile)) {
-
foreach (file($abookfile) as $line) {
-
$line = $this->convert_charset(rtrim($line), $file_charset);
$line = str_getcsv($line, "|");
- $rec = array(
+ $rec = [
'name' => $line[0],
'firstname' => $line[1],
'surname' => $line[2],
'email' => $line[3],
'notes' => $line[4],
- );
+ ];
if ($rec['name'] && $rec['email']) {
$this->abook[] = $rec;
}
}
}
}
}
// Database backend
- else if ($rcmail->config->get('squirrelmail_driver') == 'sql') {
- $this->prefs = array();
+ else if ($rcmail->config->get('squirrelmail_driver') == 'sql') {
+ $this->prefs = [];
// connect to squirrelmail database
$db = rcube_db::factory($rcmail->config->get('squirrelmail_dsn'));
$db->set_debug($rcmail->config->get('sql_debug'));
$db->db_connect('r'); // connect in read mode
// retrieve prefs
$userprefs_table = $rcmail->config->get('squirrelmail_userprefs_table');
$address_table = $rcmail->config->get('squirrelmail_address_table');
$db_charset = $rcmail->config->get('squirrelmail_db_charset');
if ($db_charset) {
- $db->query('SET NAMES '.$db_charset);
+ $db->query('SET NAMES ' . $db_charset);
}
$sql_result = $db->query('SELECT * FROM ' . $db->quote_identifier($userprefs_table)
.' WHERE `user` = ?', $uname); // ? is replaced with emailaddress
while ($sql_array = $db->fetch_assoc($sql_result) ) { // fetch one row from result
$this->prefs[$sql_array['prefkey']] = rcube_charset::convert(rtrim($sql_array['prefval']), $db_charset);
}
// retrieve address table data
$sql_result = $db->query('SELECT * FROM ' . $db->quote_identifier($address_table)
.' WHERE `owner` = ?', $uname); // ? is replaced with emailaddress
// parse address book
while ($sql_array = $db->fetch_assoc($sql_result) ) { // fetch one row from result
$rec['name'] = rcube_charset::convert(rtrim($sql_array['nickname']), $db_charset);
$rec['firstname'] = rcube_charset::convert(rtrim($sql_array['firstname']), $db_charset);
$rec['surname'] = rcube_charset::convert(rtrim($sql_array['lastname']), $db_charset);
$rec['email'] = rcube_charset::convert(rtrim($sql_array['email']), $db_charset);
$rec['notes'] = rcube_charset::convert(rtrim($sql_array['label']), $db_charset);
if ($rec['name'] && $rec['email']) {
$this->abook[] = $rec;
}
}
} // end if 'sql'-driver
}
private function convert_charset($str, $charset = null)
{
if (!$charset) {
return utf8_encode($str);
}
return rcube_charset::convert($str, $charset, RCUBE_CHARSET);
}
}
diff --git a/plugins/squirrelmail_usercopy/tests/SquirrelmailUsercopy.php b/plugins/squirrelmail_usercopy/tests/SquirrelmailUsercopy.php
index f7929af8e..96aa578b2 100644
--- a/plugins/squirrelmail_usercopy/tests/SquirrelmailUsercopy.php
+++ b/plugins/squirrelmail_usercopy/tests/SquirrelmailUsercopy.php
@@ -1,23 +1,22 @@
<?php
class SquirrelmailUsercopy_Plugin extends PHPUnit\Framework\TestCase
{
function setUp()
{
include_once __DIR__ . '/../squirrelmail_usercopy.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new squirrelmail_usercopy($rcube->plugins);
$this->assertInstanceOf('squirrelmail_usercopy', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
}
-
diff --git a/plugins/subscriptions_option/localization/en_US.inc b/plugins/subscriptions_option/localization/en_US.inc
index f4008f8d6..85d624038 100644
--- a/plugins/subscriptions_option/localization/en_US.inc
+++ b/plugins/subscriptions_option/localization/en_US.inc
@@ -1,18 +1,17 @@
<?php
/*
+-----------------------------------------------------------------------+
| Localization file of the Roundcube Webmail Subscriptions plugin |
| |
| 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. |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-subscriptions_option/
*/
-$labels = array();
$labels['useimapsubscriptions'] = 'Use IMAP Subscriptions';
diff --git a/plugins/subscriptions_option/subscriptions_option.php b/plugins/subscriptions_option/subscriptions_option.php
index 06e72e78a..64b7446cd 100644
--- a/plugins/subscriptions_option/subscriptions_option.php
+++ b/plugins/subscriptions_option/subscriptions_option.php
@@ -1,108 +1,129 @@
<?php
/**
* Subscription Options
*
* A plugin which can enable or disable the use of imap subscriptions.
* It includes a toggle on the settings page under "Server Settings".
* The preference can also be locked
*
* Add it to the plugins list in config.inc.php to enable the user option
* The user option can be hidden and set globally by adding 'use_subscriptions'
* to the 'dont_override' configure line:
- * $config['dont_override'] = array('use_subscriptions');
+ * $config['dont_override'] = ['use_subscriptions'];
* and then set the global preference
* $config['use_subscriptions'] = true; // or false
*
* Roundcube caches folder lists. When a user changes this option or visits
* their folder list, this cache is refreshed. If the option is on the
* 'dont_override' list and the global option has changed, don't expect
* to see the change until the folder list cache is refreshed.
*
* @author Ziba Scott
* @license GNU GPLv3+
*/
class subscriptions_option extends rcube_plugin
{
public $task = 'mail|settings';
+ /**
+ * Plugin initialization
+ */
function init()
{
- $dont_override = rcmail::get_instance()->config->get('dont_override', array());
+ $dont_override = rcmail::get_instance()->config->get('dont_override', []);
+
if (!in_array('use_subscriptions', $dont_override)) {
- $this->add_hook('preferences_list', array($this, 'settings_blocks'));
- $this->add_hook('preferences_save', array($this, 'save_prefs'));
+ $this->add_hook('preferences_list', [$this, 'prefs_list']);
+ $this->add_hook('preferences_save', [$this, 'prefs_save']);
}
- $this->add_hook('storage_folders', array($this, 'mailboxes_list'));
- $this->add_hook('folders_list', array($this, 'folders_list'));
+
+ $this->add_hook('storage_folders', [$this, 'mailboxes_list']);
+ $this->add_hook('folders_list', [$this, 'folders_list']);
}
- function settings_blocks($args)
+ /**
+ * Hook to inject plugin-specific user settings
+ *
+ * @param array $args Hook arguments
+ *
+ * @return array Modified hook arguments
+ */
+ function prefs_list($args)
{
if ($args['section'] == 'server') {
$this->add_texts('localization/', false);
$use_subscriptions = rcmail::get_instance()->config->get('use_subscriptions', true);
$field_id = 'rcmfd_use_subscriptions';
- $checkbox = new html_checkbox(array('name' => '_use_subscriptions', 'id' => $field_id, 'value' => 1));
+ $checkbox = new html_checkbox(['name' => '_use_subscriptions', 'id' => $field_id, 'value' => 1]);
- $args['blocks']['main']['options']['use_subscriptions'] = array(
- 'title' => html::label($field_id, rcube::Q($this->gettext('useimapsubscriptions'))),
- 'content' => $checkbox->show($use_subscriptions?1:0),
- );
+ $args['blocks']['main']['options']['use_subscriptions'] = [
+ 'title' => html::label($field_id, rcube::Q($this->gettext('useimapsubscriptions'))),
+ 'content' => $checkbox->show($use_subscriptions ? 1 : 0),
+ ];
}
return $args;
}
- function save_prefs($args)
+ /**
+ * Hook to save plugin-specific user settings
+ *
+ * @param array $args Hook arguments
+ *
+ * @return array Modified hook arguments
+ */
+ function prefs_save($args)
{
if ($args['section'] == 'server') {
$rcmail = rcmail::get_instance();
$use_subscriptions = $rcmail->config->get('use_subscriptions', true);
$args['prefs']['use_subscriptions'] = isset($_POST['_use_subscriptions']);
// if the use_subscriptions preference changes, flush the folder cache
- if (($use_subscriptions && !isset($_POST['_use_subscriptions'])) ||
- (!$use_subscriptions && isset($_POST['_use_subscriptions']))) {
- $storage = $rcmail->get_storage();
- $storage->clear_cache('mailboxes');
+ if (
+ ($use_subscriptions && !isset($_POST['_use_subscriptions']))
+ || (!$use_subscriptions && isset($_POST['_use_subscriptions']))
+ ) {
+ $storage = $rcmail->get_storage();
+ $storage->clear_cache('mailboxes');
}
}
return $args;
}
function mailboxes_list($args)
{
$rcmail = rcmail::get_instance();
if (!$rcmail->config->get('use_subscriptions', true)) {
$storage = $rcmail->get_storage();
if ($folders = $storage->list_folders_direct()) {
$folders = array_filter($folders, function($folder) use ($storage) {
$attrs = $storage->folder_attributes($folder);
return !in_array_nocase('\\Noselect', $attrs);
});
$args['folders'] = $folders;
}
}
return $args;
}
function folders_list($args)
{
$rcmail = rcmail::get_instance();
if (!$rcmail->config->get('use_subscriptions', true)) {
foreach ($args['list'] as $idx => $data) {
$args['list'][$idx]['content'] = preg_replace('/<input [^>]+>/', '', $data['content']);
}
}
return $args;
}
}
diff --git a/plugins/subscriptions_option/tests/SubscriptionsOption.php b/plugins/subscriptions_option/tests/SubscriptionsOption.php
index bbb573fda..a293d1bc0 100644
--- a/plugins/subscriptions_option/tests/SubscriptionsOption.php
+++ b/plugins/subscriptions_option/tests/SubscriptionsOption.php
@@ -1,23 +1,78 @@
<?php
-class SubscriptionsOption_Plugin extends PHPUnit\Framework\TestCase
+class SubscriptionsOption_Plugin extends ActionTestCase
{
function setUp()
{
include_once __DIR__ . '/../subscriptions_option.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new subscriptions_option($rcube->plugins);
$this->assertInstanceOf('subscriptions_option', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
-}
+ /**
+ * Test prefs_list() method
+ */
+ function test_prefs_list()
+ {
+ $rcube = rcube::get_instance();
+ $plugin = new subscriptions_option($rcube->plugins);
+
+ html::$doctype = 'html5';
+
+ $args = ['section' => 'server', 'blocks' => ['main' => ['options' => []]]];
+
+ $result = $plugin->prefs_list($args);
+
+ $this->assertSame(
+ '<label for="rcmfd_use_subscriptions">Use IMAP Subscriptions</label>',
+ $result['blocks']['main']['options']['use_subscriptions']['title']
+ );
+
+ $this->assertSame(
+ '<input name="_use_subscriptions" id="rcmfd_use_subscriptions" value="1" checked type="checkbox">',
+ $result['blocks']['main']['options']['use_subscriptions']['content']
+ );
+ }
+
+ /**
+ * Test prefs_save() method
+ */
+ function test_prefs_save()
+ {
+ self::initStorage();
+
+ $rcube = rcube::get_instance();
+ $plugin = new subscriptions_option($rcube->plugins);
+
+ $_POST = ['_use_subscriptions' => 1];
+ $args = ['section' => 'server', 'prefs' => []];
+
+ $result = $plugin->prefs_save($args);
+
+ $this->assertSame(true, $result['prefs']['use_subscriptions']);
+
+ $storage = $rcube->storage;
+ $storage->registerFunction('clear_cache', true);
+
+ $_POST = [];
+ $args = ['section' => 'server', 'prefs' => []];
+
+ $result = $plugin->prefs_save($args);
+
+ $this->assertSame(false, $result['prefs']['use_subscriptions']);
+ $this->assertCount(1, $storage->methodCalls);
+ $this->assertSame('clear_cache', $storage->methodCalls[0]['name']);
+ $this->assertSame(['mailboxes'], $storage->methodCalls[0]['args']);
+ }
+}
diff --git a/plugins/userinfo/localization/en_US.inc b/plugins/userinfo/localization/en_US.inc
index 070a9e3db..86b5e8ac7 100644
--- a/plugins/userinfo/localization/en_US.inc
+++ b/plugins/userinfo/localization/en_US.inc
@@ -1,23 +1,22 @@
<?php
/*
+-----------------------------------------------------------------------+
| Localization file of the Roundcube Webmail Userinfo plugin |
| |
| 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. |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-userinfo/
*/
-$labels = array();
$labels['userinfo'] = 'User info';
$labels['infoforuser'] = 'Info for $user';
$labels['created'] = 'Created';
$labels['lastlogin'] = 'Last Login';
$labels['defaultidentity'] = 'Default Identity';
$labels['userid'] = 'ID';
diff --git a/plugins/userinfo/userinfo.php b/plugins/userinfo/userinfo.php
index b801e6415..41125d0b2 100644
--- a/plugins/userinfo/userinfo.php
+++ b/plugins/userinfo/userinfo.php
@@ -1,74 +1,75 @@
<?php
/**
* Sample plugin that adds a new tab to the settings section
* to display some information about the current user
*/
class userinfo extends rcube_plugin
{
public $task = 'settings';
public $noajax = true;
public $noframe = true;
function init()
{
- $this->add_texts('localization/', array('userinfo'));
- $this->add_hook('settings_actions', array($this, 'settings_actions'));
- $this->register_action('plugin.userinfo', array($this, 'infostep'));
+ $this->add_texts('localization/', ['userinfo']);
+ $this->add_hook('settings_actions', [$this, 'settings_actions']);
+ $this->register_action('plugin.userinfo', [$this, 'infostep']);
}
function settings_actions($args)
{
- $args['actions'][] = array(
+ $args['actions'][] = [
'action' => 'plugin.userinfo',
'class' => 'userinfo',
'label' => 'userinfo',
'domain' => 'userinfo',
- );
+ ];
return $args;
}
function infostep()
{
- $this->register_handler('plugin.body', array($this, 'infohtml'));
+ $this->register_handler('plugin.body', [$this, 'infohtml']);
$rcmail = rcmail::get_instance();
$rcmail->output->set_pagetitle($this->gettext('userinfo'));
$rcmail->output->send('plugin');
}
function infohtml()
{
$rcmail = rcmail::get_instance();
$user = $rcmail->user;
$identity = $user->get_identity();
- $table = new html_table(array('cols' => 2, 'class' => 'propform'));
+ $table = new html_table(['cols' => 2, 'class' => 'propform']);
$table->add('title', html::label('', rcube::Q($this->gettext('userid'))));
$table->add('', rcube::Q($user->ID));
$table->add('title', html::label('', rcube::Q($this->gettext('username'))));
$table->add('', rcube::Q($user->data['username']));
$table->add('title', html::label('', rcube::Q($this->gettext('server'))));
$table->add('', rcube::Q($user->data['mail_host']));
$table->add('title', html::label('', rcube::Q($this->gettext('created'))));
$table->add('', rcube::Q($user->data['created']));
$table->add('title', html::label('', rcube::Q($this->gettext('lastlogin'))));
$table->add('', rcube::Q($user->data['last_login']));
$table->add('title', html::label('', rcube::Q($this->gettext('defaultidentity'))));
$table->add('', rcube::Q($identity['name'] . ' <' . $identity['email'] . '>'));
- $legend = rcube::Q($this->gettext(array('name' => 'infoforuser', 'vars' => array('user' => $user->get_username()))));
+ $legend = rcube::Q($this->gettext(['name' => 'infoforuser', 'vars' => ['user' => $user->get_username()]]));
$out = html::tag('fieldset', '', html::tag('legend', '', $legend) . $table->show());
- return html::div(array('class' => 'box formcontent'),
- html::div(array('class' => 'boxtitle'), $this->gettext('userinfo'))
- . html::div(array('class' => 'boxcontent'), $out));
+ return html::div(['class' => 'box formcontent'],
+ html::div(['class' => 'boxtitle'], $this->gettext('userinfo'))
+ . html::div(['class' => 'boxcontent'], $out)
+ );
}
}
diff --git a/plugins/vcard_attachments/localization/en_US.inc b/plugins/vcard_attachments/localization/en_US.inc
index e891776a0..d3f3fa019 100644
--- a/plugins/vcard_attachments/localization/en_US.inc
+++ b/plugins/vcard_attachments/localization/en_US.inc
@@ -1,23 +1,22 @@
<?php
/*
+-----------------------------------------------------------------------+
| Localization file of the Roundcube Webmail Vcard Attachments plugin |
| |
| 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. |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-vcard_attachments/
*/
-$labels = array();
$labels['addvcardmsg'] = 'Add vCard to addressbook';
$labels['vcardsavefailed'] = 'Unable to save vCard';
$labels['attachvcard'] = 'Attach vCard';
$labels['vcardattached'] = 'vCard file attached.';
$labels['vcard'] = 'vCard';
$labels['forwardvcard'] = 'Forward vCard';
diff --git a/plugins/vcard_attachments/tests/VcardAttachments.php b/plugins/vcard_attachments/tests/VcardAttachments.php
index 7dde2f224..d51a65ca2 100644
--- a/plugins/vcard_attachments/tests/VcardAttachments.php
+++ b/plugins/vcard_attachments/tests/VcardAttachments.php
@@ -1,23 +1,50 @@
<?php
class VcardAttachments_Plugin extends PHPUnit\Framework\TestCase
{
function setUp()
{
include_once __DIR__ . '/../vcard_attachments.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new vcard_attachments($rcube->plugins);
$this->assertInstanceOf('vcard_attachments', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
-}
+ /**
+ * Test is_vcard()
+ */
+ function test_is_vcard()
+ {
+ $rcube = rcube::get_instance();
+ $plugin = new vcard_attachments($rcube->plugins);
+
+ $part = new rcube_message_part();
+ $this->assertFalse(invokeMethod($plugin, 'is_vcard', [$part]));
+
+ $part->mimetype = 'text/vcard';
+ $this->assertTrue(invokeMethod($plugin, 'is_vcard', [$part]));
+
+ $part->mimetype = 'text/x-vcard';
+ $this->assertTrue(invokeMethod($plugin, 'is_vcard', [$part]));
+
+ $part->mimetype = 'text/directory';
+ $this->assertFalse(invokeMethod($plugin, 'is_vcard', [$part]));
+
+ $part->ctype_parameters['profile'] = 'vcard';
+ $this->assertTrue(invokeMethod($plugin, 'is_vcard', [$part]));
+
+ $part->ctype_parameters['profile'] = 'unknown';
+ $part->filename = 'vcard.vcf';
+ $this->assertTrue(invokeMethod($plugin, 'is_vcard', [$part]));
+ }
+}
diff --git a/plugins/vcard_attachments/vcard_attachments.php b/plugins/vcard_attachments/vcard_attachments.php
index fa8571611..dff39cd18 100644
--- a/plugins/vcard_attachments/vcard_attachments.php
+++ b/plugins/vcard_attachments/vcard_attachments.php
@@ -1,376 +1,384 @@
<?php
/**
* Detects VCard attachments and show a button to add them to address book
* Adds possibility to attach a contact vcard to mail messages
*
* @license GNU GPLv3+
* @author Thomas Bruederli, Aleksander Machniak
*/
class vcard_attachments extends rcube_plugin
{
public $task = 'mail|addressbook';
private $abook;
private $message;
- private $vcard_parts = array();
- private $vcard_bodies = array();
+ private $vcard_parts = [];
+ private $vcard_bodies = [];
+ /**
+ * Plugin initialization
+ */
function init()
{
$rcmail = rcmail::get_instance();
if ($rcmail->task == 'addressbook') {
$skin_path = $this->local_skin_path();
$this->add_texts('localization', !$rcmail->output->ajax_call);
$this->include_stylesheet($skin_path . '/style.css');
$this->include_script('vcardattach.js');
- $this->add_button(
- array(
+ $this->add_button([
'type' => 'link-menuitem',
'label' => 'vcard_attachments.forwardvcard',
'command' => 'attach-vcard',
'class' => 'icon vcard',
'classact' => 'icon vcard active',
'innerclass' => 'icon vcard',
- ),
- 'contactmenu');
+ ],
+ 'contactmenu'
+ );
}
else {
if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
- $this->add_hook('message_load', array($this, 'message_load'));
- $this->add_hook('message_objects', array($this, 'message_objects'));
- $this->add_hook('template_object_messagebody', array($this, 'html_output'));
+ $this->add_hook('message_load', [$this, 'message_load']);
+ $this->add_hook('message_objects', [$this, 'message_objects']);
+ $this->add_hook('template_object_messagebody', [$this, 'html_output']);
}
else if ($rcmail->action == 'upload') {
- $this->add_hook('attachment_from_uri', array($this, 'attach_vcard'));
+ $this->add_hook('attachment_from_uri', [$this, 'attach_vcard']);
}
else if ($rcmail->action == 'compose' && !$rcmail->output->framed) {
$skin_path = $this->local_skin_path();
$btn_class = strpos($skin_path, 'classic') ? 'button' : 'listbutton';
$this->add_texts('localization', true);
$this->include_stylesheet($skin_path . '/style.css');
$this->include_script('vcardattach.js');
- $this->add_button(
- array(
+ $this->add_button([
'type' => 'link',
'label' => 'vcard_attachments.vcard',
'command' => 'attach-vcard',
'class' => $btn_class . ' vcard disabled',
'classact' => $btn_class . ' vcard',
'title' => 'vcard_attachments.attachvcard',
'innerclass' => 'inner',
- ),
- 'compose-contacts-toolbar');
+ ],
+ 'compose-contacts-toolbar'
+ );
- $this->add_hook('message_compose', array($this, 'message_compose'));
+ $this->add_hook('message_compose', [$this, 'message_compose']);
}
else if (!$rcmail->output->framed && (!$rcmail->action || $rcmail->action == 'list')) {
$skin_path = $this->local_skin_path();
$this->include_stylesheet($skin_path . '/style.css');
$this->include_script('vcardattach.js');
}
}
- $this->register_action('plugin.savevcard', array($this, 'save_vcard'));
+ $this->register_action('plugin.savevcard', [$this, 'save_vcard']);
}
/**
* Check message bodies and attachments for vcards
*/
function message_load($p)
{
$this->message = $p['object'];
// handle attachments vcard attachments
foreach ((array)$this->message->attachments as $attachment) {
if ($this->is_vcard($attachment)) {
$this->vcard_parts[] = $attachment->mime_id;
}
}
// the same with message bodies
foreach ((array)$this->message->parts as $part) {
if ($this->is_vcard($part)) {
$this->vcard_parts[] = $part->mime_id;
$this->vcard_bodies[] = $part->mime_id;
}
}
if ($this->vcard_parts) {
$this->add_texts('localization');
}
}
/**
* This callback function adds a box above the message content
* if there is a vcard attachment available
*/
function message_objects($p)
{
$attach_script = false;
$rcmail = rcmail::get_instance();
foreach ($this->vcard_parts as $part) {
$vcards = rcube_vcard::import($this->message->get_part_content($part, null, true));
// successfully parsed vcards?
if (empty($vcards)) {
continue;
}
foreach ($vcards as $idx => $vcard) {
// skip invalid vCards
if (empty($vcard->email) || empty($vcard->email[0])) {
continue;
}
$display = $vcard->displayname . ' <'.$vcard->email[0].'>';
$vid = rcube::JQ($part.':'.$idx);
// add box below message body
- $p['content'][] = html::p(array('class' => 'vcardattachment aligned-buttons boxinformation'),
- html::span(null, rcube::Q($display)) .
- html::tag('button', array(
+ $p['content'][] = html::p(['class' => 'vcardattachment aligned-buttons boxinformation'],
+ html::span(null, rcube::Q($display))
+ . html::tag('button', [
'onclick' => "return plugin_vcard_save_contact('$vid')",
'title' => $this->gettext('addvcardmsg'),
'class' => 'import',
- ), rcube::Q($rcmail->gettext('import')))
+ ], rcube::Q($rcmail->gettext('import'))
+ )
);
}
$attach_script = true;
}
if ($attach_script) {
$this->include_script('vcardattach.js');
$this->include_stylesheet($this->local_skin_path() . '/style.css');
}
return $p;
}
/**
* This callback function adds a vCard to the message when attached from the Address book
*/
function message_compose($p)
{
- if (rcube_utils::get_input_value('_attach_vcard', rcube_utils::INPUT_GET) == '1' && ($uri = rcube_utils::get_input_value('_uri', rcube_utils::INPUT_GET))) {
- if ($attachment = $this->attach_vcard(array('compose_id' => $p['compose_id'], 'uri' => $uri))) {
+ if (
+ rcube_utils::get_input_value('_attach_vcard', rcube_utils::INPUT_GET) == '1'
+ && ($uri = rcube_utils::get_input_value('_uri', rcube_utils::INPUT_GET))
+ ) {
+ if ($attachment = $this->attach_vcard(['compose_id' => $p['compose_id'], 'uri' => $uri])) {
$p['attachments'][] = $attachment;
};
}
return $p;
}
/**
* This callback function removes message part's content
* for parts that are vcards
*/
function html_output($p)
{
foreach ($this->vcard_parts as $part) {
// remove part's body
if (in_array($part, $this->vcard_bodies)) {
$p['content'] = '';
}
}
return $p;
}
/**
* Handler for request action
*/
function save_vcard()
{
$this->add_texts('localization');
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
$rcmail = rcmail::get_instance();
$message = new rcube_message($uid, $mbox);
if ($uid && $mime_id) {
- list($mime_id, $index) = explode(':', $mime_id);
+ list($mime_id, $index) = rcube_utils::explode(':', $mime_id);
$part = $message->get_part_content($mime_id, null, true);
}
$error_msg = $this->gettext('vcardsavefailed');
if (!empty($part) && ($vcards = rcube_vcard::import($part))
&& isset($index) && !empty($vcards[$index])
&& ($vcard = $vcards[$index]) && $vcard->displayname && $vcard->email
) {
$CONTACTS = $this->get_address_book();
$email = $vcard->email[0];
$contact = $vcard->get_assoc();
$valid = true;
// skip entries without an e-mail address or invalid
if (empty($email) || !$CONTACTS->validate($contact, true)) {
$valid = false;
}
else {
// We're using UTF8 internally
$email = rcube_utils::idn_to_utf8($email);
// compare e-mail address
$existing = $CONTACTS->search('email', $email, 1, false);
+
// compare display name
if (!$existing->count && $vcard->displayname) {
$existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
}
if ($existing->count) {
$rcmail->output->command('display_message', $this->gettext('contactexists'), 'warning');
$valid = false;
}
}
if ($valid) {
- $plugin = $rcmail->plugins->exec_hook('contact_create', array('record' => $contact, 'source' => null));
+ $plugin = $rcmail->plugins->exec_hook('contact_create', ['record' => $contact, 'source' => null]);
$contact = $plugin['record'];
if (!$plugin['abort'] && $CONTACTS->insert($contact))
$rcmail->output->command('display_message', $this->gettext('addedsuccessfully'), 'confirmation');
else
$rcmail->output->command('display_message', $error_msg, 'error');
}
}
else {
$rcmail->output->command('display_message', $error_msg, 'error');
}
$rcmail->output->send();
}
/**
* Checks if specified message part is a vcard data
*
* @param rcube_message_part Part object
*
- * @return boolean True if part is of type vcard
+ * @return bool True if part is of type vcard
*/
- function is_vcard($part)
+ private static function is_vcard($part)
{
return (
// Content-Type: text/vcard;
$part->mimetype == 'text/vcard' ||
// Content-Type: text/x-vcard;
$part->mimetype == 'text/x-vcard' ||
// Content-Type: text/directory; profile=vCard;
($part->mimetype == 'text/directory' && (
- ($part->ctype_parameters['profile'] &&
- strtolower($part->ctype_parameters['profile']) == 'vcard')
+ (!empty($part->ctype_parameters['profile']) && strtolower($part->ctype_parameters['profile']) == 'vcard')
// Content-Type: text/directory; (with filename=*.vcf)
- || ($part->filename && preg_match('/\.vcf$/i', $part->filename))
+ || (!empty($part->filename) && preg_match('/\.vcf$/i', $part->filename))
)
)
);
}
/**
* Getter for default (writable) addressbook
*/
private function get_address_book()
{
if (!empty($this->abook)) {
return $this->abook;
}
$rcmail = rcmail::get_instance();
// Get configured addressbook
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
// Get first writeable addressbook if the configured doesn't exist
// This can happen when user deleted the addressbook (e.g. Kolab folder)
if (!is_object($CONTACTS)) {
$source = reset($rcmail->get_address_sources(true));
$CONTACTS = $rcmail->get_address_book($source['id'], true);
}
return $this->abook = $CONTACTS;
}
/**
* Attaches a contact vcard to composed mail
*/
public function attach_vcard($args)
{
if (preg_match('|^vcard://(.+)$|', $args['uri'], $m)) {
list($source, $cid, $email) = explode('-', $m[1]);
- $vcard = $this->get_contact_vcard($source, $cid, $filename);
- $params = array(
- 'filename' => $filename,
- 'mimetype' => 'text/vcard',
- );
+ $vcard = $this->get_contact_vcard($source, $cid, $filename);
if ($vcard) {
+ $params = [
+ 'filename' => $filename,
+ 'mimetype' => 'text/vcard',
+ ];
+
$args['attachment'] = rcmail_action_mail_compose::save_attachment($vcard, null, $args['compose_id'], $params);
}
}
return $args;
}
/**
* Get vcard data for specified contact
*/
private function get_contact_vcard($source, $cid, &$filename = null)
{
$rcmail = rcmail::get_instance();
$source = $rcmail->get_address_book($source);
$contact = $source->get_record($cid, true);
if ($contact) {
$fieldmap = $source ? $source->vcard_map : null;
if (empty($contact['vcard'])) {
$vcard = new rcube_vcard('', RCUBE_CHARSET, false, $fieldmap);
$vcard->reset();
foreach ($contact as $key => $values) {
- list($field, $section) = explode(':', $key);
+ 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 = array($values);
+ $values = [$values];
}
foreach ((array) $values as $value) {
if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) {
$vcard->set($field, $value, strtoupper($section));
}
}
}
$contact['vcard'] = $vcard->export();
}
$name = rcube_addressbook::compose_list_name($contact);
$filename = (self::parse_filename($name) ?: 'contact') . '.vcf';
// fix folding and end-of-line chars
$vcard = preg_replace('/\r|\n\s+/', '', $contact['vcard']);
$vcard = preg_replace('/\n/', rcube_vcard::$eol, $vcard);
return rcube_vcard::rfc2425_fold($vcard) . rcube_vcard::$eol;
}
}
/**
* Helper function to convert contact name into filename
*/
static private function parse_filename($str)
{
$str = preg_replace('/[\t\n\r\0\x0B:\/]+\s*/', ' ', $str);
return trim($str, " ./_");
}
}
diff --git a/plugins/virtuser_file/virtuser_file.php b/plugins/virtuser_file/virtuser_file.php
index 2d78717a5..9b815acce 100644
--- a/plugins/virtuser_file/virtuser_file.php
+++ b/plugins/virtuser_file/virtuser_file.php
@@ -1,104 +1,112 @@
<?php
/**
* File based User-to-Email and Email-to-User lookup
*
* Add it to the plugins list in config.inc.php and set
* path to a virtuser table file to resolve user names and e-mail
* addresses
* $rcmail['virtuser_file'] = '';
*
* @license GNU GPLv3+
* @author Aleksander Machniak
*/
class virtuser_file extends rcube_plugin
{
private $file;
private $app;
+ /**
+ * Plugin initialization
+ */
function init()
{
- $this->app = rcmail::get_instance();
+ $this->app = rcmail::get_instance();
$this->file = $this->app->config->get('virtuser_file');
if ($this->file) {
- $this->add_hook('user2email', array($this, 'user2email'));
- $this->add_hook('email2user', array($this, 'email2user'));
+ $this->add_hook('user2email', [$this, 'user2email']);
+ $this->add_hook('email2user', [$this, 'email2user']);
}
}
/**
* User > Email
*/
function user2email($p)
{
$r = $this->findinvirtual('/\s' . preg_quote($p['user'], '/') . '\s*$/');
- $result = array();
+ $result = [];
for ($i=0; $i<count($r); $i++) {
$arr = preg_split('/\s+/', $r[$i]);
if (count($arr) > 0 && strpos($arr[0], '@')) {
$result[] = rcube_utils::idn_to_ascii(trim(str_replace('\\@', '@', $arr[0])));
- if ($p['first']) {
+ if (!empty($p['first'])) {
$p['email'] = $result[0];
break;
}
}
}
- $p['email'] = empty($result) ? NULL : $result;
+ $p['email'] = empty($result) ? null : $result;
return $p;
}
/**
* Email > User
*/
function email2user($p)
{
$r = $this->findinvirtual('/^' . preg_quote($p['email'], '/') . '\s/');
for ($i=0; $i<count($r); $i++) {
$arr = preg_split('/\s+/', trim($r[$i]));
if (count($arr) > 0) {
$p['user'] = trim($arr[count($arr)-1]);
break;
}
}
return $p;
}
/**
* Find matches of the given pattern in virtuser file
*
- * @param string Regular expression to search for
+ * @param string $pattern Regular expression to search for
+ *
* @return array Matching entries
*/
private function findinvirtual($pattern)
{
- $result = array();
+ $result = [];
$virtual = null;
- if ($this->file)
+ if ($this->file) {
$virtual = file($this->file);
+ }
- if (empty($virtual))
+ if (empty($virtual)) {
return $result;
+ }
// check each line for matches
foreach ($virtual as $line) {
$line = trim($line);
- if (empty($line) || $line[0]=='#')
+ if (empty($line) || $line[0] == '#') {
continue;
+ }
- if (preg_match($pattern, $line))
+ if (preg_match($pattern, $line)) {
$result[] = $line;
+ }
}
return $result;
}
}
diff --git a/plugins/virtuser_query/virtuser_query.php b/plugins/virtuser_query/virtuser_query.php
index 8ebdeb3fc..693b2f993 100644
--- a/plugins/virtuser_query/virtuser_query.php
+++ b/plugins/virtuser_query/virtuser_query.php
@@ -1,165 +1,164 @@
<?php
/**
* DB based User-to-Email and Email-to-User lookup
*
* Add it to the plugins list in config.inc.php and set
* SQL queries to resolve usernames, e-mail addresses and hostnames from the database
* %u will be replaced with the current username for login.
* %m will be replaced with the current e-mail address for login.
*
* Queries should select the user's e-mail address, username or the imap hostname as first column
* The email query could optionally select identity data columns in specified order:
* name, organization, reply-to, bcc, signature, html_signature
*
- * $config['virtuser_query'] = array('email' => '', 'user' => '', 'host' => '', 'alias' => '');
+ * $config['virtuser_query'] = ['email' => '', 'user' => '', 'host' => '', 'alias' => ''];
*
* The email query can return more than one record to create more identities.
* This requires identities_level option to be set to value less than 2.
*
* By default Roundcube database is used. To use different database (or host)
* you can specify DSN string in $config['virtuser_query_dsn'] option.
*
* @author Aleksander Machniak <alec@alec.pl>
* @author Steffen Vogel
* @author Tim Gerundt
* @license GNU GPLv3+
*/
class virtuser_query extends rcube_plugin
{
private $config;
private $app;
private $db;
function init()
{
$this->app = rcmail::get_instance();
$this->config = $this->app->config->get('virtuser_query');
if (!empty($this->config)) {
if (is_string($this->config)) {
- $this->config = array('email' => $this->config);
+ $this->config = ['email' => $this->config];
}
- if ($this->config['email']) {
- $this->add_hook('user2email', array($this, 'user2email'));
+ if (!empty($this->config['email'])) {
+ $this->add_hook('user2email', [$this, 'user2email']);
}
- if ($this->config['user']) {
- $this->add_hook('email2user', array($this, 'email2user'));
+ if (!empty($this->config['user'])) {
+ $this->add_hook('email2user', [$this, 'email2user']);
}
- if ($this->config['host']) {
- $this->add_hook('authenticate', array($this, 'user2host'));
+ if (!empty($this->config['host'])) {
+ $this->add_hook('authenticate', [$this, 'user2host']);
}
- if ($this->config['alias']) {
- $this->add_hook('authenticate', array($this, 'alias2user'));
+ if (!empty($this->config['alias'])) {
+ $this->add_hook('authenticate', [$this, 'alias2user']);
}
}
}
/**
* User > Email
*/
function user2email($p)
{
$dbh = $this->get_dbh();
$sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['email']));
- $result = array();
+ $result = [];
while ($sql_arr = $dbh->fetch_array($sql_result)) {
if (strpos($sql_arr[0], '@')) {
- if ($p['extended'] && count($sql_arr) > 1) {
- $result[] = array(
+ if (!empty($p['extended']) && count($sql_arr) > 1) {
+ $result[] = [
'email' => rcube_utils::idn_to_ascii($sql_arr[0]),
- 'name' => (string) $sql_arr[1],
- 'organization' => (string) $sql_arr[2],
- 'reply-to' => (string) rcube_utils::idn_to_ascii($sql_arr[3]),
- 'bcc' => (string) rcube_utils::idn_to_ascii($sql_arr[4]),
- 'signature' => (string) $sql_arr[5],
- 'html_signature' => (int) $sql_arr[6],
- );
+ 'name' => isset($sql_arr[1]) ? $sql_arr[1] : '',
+ 'organization' => isset($sql_arr[2]) ? $sql_arr[2] : '',
+ 'reply-to' => isset($sql_arr[3]) ? rcube_utils::idn_to_ascii($sql_arr[3]) : '',
+ 'bcc' => isset($sql_arr[4]) ? rcube_utils::idn_to_ascii($sql_arr[4]) : '',
+ 'signature' => isset($sql_arr[5]) ? $sql_arr[5] : '',
+ 'html_signature' => isset($sql_arr[6]) ? intval($sql_arr[6]) : 0,
+ ];
}
else {
$result[] = $sql_arr[0];
}
- if ($p['first']) {
+ if (!empty($p['first'])) {
break;
}
}
}
$p['email'] = $result;
return $p;
}
/**
* EMail > User
*/
function email2user($p)
{
$dbh = $this->get_dbh();
$sql_result = $dbh->query(preg_replace('/%m/', $dbh->escape($p['email']), $this->config['user']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
}
return $p;
}
/**
* User > Host
*/
function user2host($p)
{
$dbh = $this->get_dbh();
$sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['host']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['host'] = $sql_arr[0];
}
return $p;
}
/**
* Alias > User
*/
function alias2user($p)
{
$dbh = $this->get_dbh();
$sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['alias']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
}
return $p;
}
/**
* Initialize database handler
*/
function get_dbh()
{
if (!$this->db) {
if ($dsn = $this->app->config->get('virtuser_query_dsn')) {
// connect to the virtuser database
$this->db = rcube_db::factory($dsn);
$this->db->set_debug((bool)$this->app->config->get('sql_debug'));
$this->db->db_connect('r'); // connect in read mode
}
else {
$this->db = $this->app->get_dbh();
}
}
return $this->db;
}
-
}
diff --git a/plugins/zipdownload/config.inc.php.dist b/plugins/zipdownload/config.inc.php.dist
index 28dee7f4b..019cc8de5 100644
--- a/plugins/zipdownload/config.inc.php.dist
+++ b/plugins/zipdownload/config.inc.php.dist
@@ -1,21 +1,19 @@
<?php
/**
* ZipDownload configuration file
*/
// Zip attachments
// Only show the link when there are more than this many attachments
// -1 to prevent downloading of attachments as zip
$config['zipdownload_attachments'] = 1;
// Zip selection of mail messages
// This option enables downloading of multiple messages as one zip archive.
// The number or string value specifies maximum total size of all messages
// in the archive (not the size of the archive itself).
$config['zipdownload_selection'] = '50MB';
// Charset to use for filenames inside the zip
$config['zipdownload_charset'] = 'ISO-8859-1';
-
-?>
\ No newline at end of file
diff --git a/plugins/zipdownload/localization/en_US.inc b/plugins/zipdownload/localization/en_US.inc
index a5847fba9..9b77ab0c9 100644
--- a/plugins/zipdownload/localization/en_US.inc
+++ b/plugins/zipdownload/localization/en_US.inc
@@ -1,23 +1,22 @@
<?php
/*
+-----------------------------------------------------------------------+
| Localization file of the Roundcube Webmail Zipdownload plugin |
| |
| 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. |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/plugin-zipdownload/
*/
-$labels = array();
$labels['downloadall'] = 'Download all attachments';
$labels['download'] = 'Download...';
$labels['downloadmbox'] = 'Mbox format (.zip)';
$labels['downloadmaildir'] = 'Maildir format (.zip)';
$labels['downloademl'] = 'Source (.eml)';
$labels['sizelimiterror'] = 'Total size of selected messages exceeds the limit ($size)';
diff --git a/plugins/zipdownload/tests/Zipdownload.php b/plugins/zipdownload/tests/Zipdownload.php
index 180866591..7e95a1d60 100644
--- a/plugins/zipdownload/tests/Zipdownload.php
+++ b/plugins/zipdownload/tests/Zipdownload.php
@@ -1,23 +1,22 @@
<?php
class Zipdownload_Plugin extends PHPUnit\Framework\TestCase
{
function setUp()
{
include_once __DIR__ . '/../zipdownload.php';
}
/**
* Plugin object construction test
*/
function test_constructor()
{
$rcube = rcube::get_instance();
$plugin = new zipdownload($rcube->plugins);
$this->assertInstanceOf('zipdownload', $plugin);
$this->assertInstanceOf('rcube_plugin', $plugin);
}
}
-
diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php
index 5a393939f..fb6b72809 100644
--- a/plugins/zipdownload/zipdownload.php
+++ b/plugins/zipdownload/zipdownload.php
@@ -1,413 +1,421 @@
<?php
/**
* ZipDownload
*
* Plugin to allow the download of all message attachments in one zip file
* and also download of many messages in one go.
*
* @requires php_zip extension (including ZipArchive class)
*
* @author Philip Weir
* @author Thomas Bruderli
* @author Aleksander Machniak
*/
class zipdownload extends rcube_plugin
{
public $task = 'mail';
private $charset = 'ASCII';
- private $names = array();
+ private $names = [];
private $default_limit = '50MB';
// RFC4155: mbox date format
const MBOX_DATE_FORMAT = 'D M d H:i:s Y';
/**
* Plugin initialization
*/
public function init()
{
// check requirements first
if (!class_exists('ZipArchive', false)) {
- rcmail::raise_error(array(
- 'code' => 520,
- 'file' => __FILE__,
- 'line' => __LINE__,
- 'message' => "php_zip extension is required for the zipdownload plugin"), true, false);
+ rcmail::raise_error([
+ 'code' => 520,
+ 'file' => __FILE__,
+ 'line' => __LINE__,
+ 'message' => "php-zip extension is required for the zipdownload plugin"
+ ], true, false
+ );
return;
}
$rcmail = rcmail::get_instance();
$this->load_config();
$this->charset = $rcmail->config->get('zipdownload_charset', RCUBE_CHARSET);
if ($rcmail->config->get('zipdownload_attachments', 1) > -1 && ($rcmail->action == 'show' || $rcmail->action == 'preview')) {
$this->add_texts('localization');
- $this->add_hook('template_object_messageattachments', array($this, 'attachment_ziplink'));
+ $this->add_hook('template_object_messageattachments', [$this, 'attachment_ziplink']);
}
- $this->register_action('plugin.zipdownload.attachments', array($this, 'download_attachments'));
- $this->register_action('plugin.zipdownload.messages', array($this, 'download_messages'));
+ $this->register_action('plugin.zipdownload.attachments', [$this, 'download_attachments']);
+ $this->register_action('plugin.zipdownload.messages', [$this, 'download_messages']);
if (!$rcmail->action && $rcmail->config->get('zipdownload_selection', $this->default_limit)) {
$this->add_texts('localization');
$this->download_menu();
}
}
/**
* Place a link/button after attachments listing to trigger download
*/
public function attachment_ziplink($p)
{
$rcmail = rcmail::get_instance();
// only show the link if there is more than the configured number of attachments
if (substr_count($p['content'], '<li') > $rcmail->config->get('zipdownload_attachments', 1)) {
- $href = $rcmail->url(array(
- '_action' => 'plugin.zipdownload.attachments',
- '_mbox' => $rcmail->output->env['mailbox'],
- '_uid' => $rcmail->output->env['uid'],
- ), false, false, true);
-
- $link = html::a(array('href' => $href, 'class' => 'button zipdownload'),
+ $href = $rcmail->url([
+ '_action' => 'plugin.zipdownload.attachments',
+ '_mbox' => $rcmail->output->get_env('mailbox'),
+ '_uid' => $rcmail->output->get_env('uid'),
+ ], false, false, true);
+
+ $link = html::a(
+ ['href' => $href, 'class' => 'button zipdownload'],
rcube::Q($this->gettext('downloadall'))
);
// append link to attachments list, slightly different in some skins
switch (rcmail::get_instance()->config->get('skin')) {
case 'classic':
- $p['content'] = str_replace('</ul>', html::tag('li', array('class' => 'zipdownload'), $link) . '</ul>', $p['content']);
+ $p['content'] = str_replace('</ul>', html::tag('li', ['class' => 'zipdownload'], $link) . '</ul>', $p['content']);
break;
default:
$p['content'] .= $link;
break;
}
$this->include_stylesheet($this->local_skin_path() . '/zipdownload.css');
}
return $p;
}
/**
* Adds download options menu to the page
*/
public function download_menu()
{
$this->include_script('zipdownload.js');
$this->add_label('download');
$rcmail = rcmail::get_instance();
- $menu = array();
- $ul_attr = array('role' => 'menu', 'aria-labelledby' => 'aria-label-zipdownloadmenu');
+ $menu = [];
+ $ul_attr = ['role' => 'menu', 'aria-labelledby' => 'aria-label-zipdownloadmenu'];
+
if ($rcmail->config->get('skin') != 'classic') {
$ul_attr['class'] = 'toolbarmenu menu';
}
- foreach (array('eml', 'mbox', 'maildir') as $type) {
- $menu[] = html::tag('li', null, $rcmail->output->button(array(
+ foreach (['eml', 'mbox', 'maildir'] as $type) {
+ $menu[] = html::tag('li', null, $rcmail->output->button([
'command' => "download-$type",
'label' => "zipdownload.download$type",
'class' => "download $type disabled",
'classact' => "download $type active",
'type' => 'link',
- )));
+ ])
+ );
}
- $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu', 'aria-hidden' => 'true'),
- html::tag('h2', array('class' => 'voice', 'id' => 'aria-label-zipdownloadmenu'), "Message Download Options Menu") .
- html::tag('ul', $ul_attr, implode('', $menu))));
+ $rcmail->output->add_footer(
+ html::div(['id' => 'zipdownload-menu', 'class' => 'popupmenu', 'aria-hidden' => 'true'],
+ html::tag('h2', ['class' => 'voice', 'id' => 'aria-label-zipdownloadmenu'], "Message Download Options Menu")
+ . html::tag('ul', $ul_attr, implode('', $menu))
+ )
+ );
}
/**
* Handler for attachment download action
*/
public function download_attachments()
{
$rcmail = rcmail::get_instance();
// require CSRF protected request
$rcmail->request_security_check(rcube_utils::INPUT_GET);
$tmpfname = rcube_utils::temp_filename('zipdownload');
- $tempfiles = array($tmpfname);
+ $tempfiles = [$tmpfname];
$message = new rcube_message(rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GET));
// open zip file
$zip = new ZipArchive();
$zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
foreach ($message->attachments as $part) {
- $pid = $part->mime_id;
- $part = $message->mime_parts[$pid];
+ $pid = $part->mime_id;
+ $part = $message->mime_parts[$pid];
$disp_name = $this->_create_displayname($part);
$tmpfn = rcube_utils::temp_filename('zipattach');
$tmpfp = fopen($tmpfn, 'w');
$tempfiles[] = $tmpfn;
$message->get_part_body($part->mime_id, false, 0, $tmpfp);
$zip->addFile($tmpfn, $disp_name);
fclose($tmpfp);
}
$zip->close();
$filename = ($this->_filename_from_subject($message->subject) ?: 'attachments') . '.zip';
$this->_deliver_zipfile($tmpfname, $filename);
// delete temporary files from disk
foreach ($tempfiles as $tmpfn) {
unlink($tmpfn);
}
exit;
}
/**
* Handler for message download action
*/
public function download_messages()
{
$rcmail = rcmail::get_instance();
if ($rcmail->config->get('zipdownload_selection', $this->default_limit)) {
$messageset = rcmail::get_uids(null, null, $multi, rcube_utils::INPUT_POST);
if (count($messageset)) {
$this->_download_messages($messageset);
}
}
}
/**
* Create and get display name of attachment part to add on zip file
*
* @param $part stdClass Part of attachment on message
*
* @return string Display name of attachment part
*/
private function _create_displayname($part)
{
- $rcmail = rcmail::get_instance();
+ $rcmail = rcmail::get_instance();
$filename = $part->filename;
if ($filename === null || $filename === '') {
$ext = (array) rcube_mime::get_mime_extensions($part->mimetype);
$ext = array_shift($ext);
$filename = $rcmail->gettext('messagepart') . ' ' . $part->mime_id;
if ($ext) {
$filename .= '.' . $ext;
}
}
$displayname = $this->_convert_filename($filename);
/**
* Adding a number before dot of extension on a name of file with same name on zip
* Ext: attach(1).txt on attach filename that has a attach.txt filename on same zip
*/
if (isset($this->names[$displayname])) {
list($filename, $ext) = preg_split("/\.(?=[^\.]*$)/", $displayname);
$displayname = $filename . '(' . ($this->names[$displayname]++) . ').' . $ext;
$this->names[$displayname] = 1;
}
else {
$this->names[$displayname] = 1;
}
return $displayname;
}
/**
* Helper method to packs all the given messages into a zip archive
*
* @param array List of message UIDs to download
*/
private function _download_messages($messageset)
{
$this->add_texts('localization');
$rcmail = rcmail::get_instance();
$imap = $rcmail->get_storage();
$mode = rcube_utils::get_input_value('_mode', rcube_utils::INPUT_POST);
$limit = $rcmail->config->get('zipdownload_selection', $this->default_limit);
$limit = $limit !== true ? parse_bytes($limit) : -1;
$delimiter = $imap->get_hierarchy_delimiter();
$tmpfname = rcube_utils::temp_filename('zipdownload');
- $tempfiles = array($tmpfname);
+ $tempfiles = [$tmpfname];
$folders = count($messageset) > 1;
$timezone = new DateTimeZone('UTC');
- $messages = array();
+ $messages = [];
$size = 0;
// collect messages metadata (and check size limit)
foreach ($messageset as $mbox => $uids) {
$imap->set_folder($mbox);
if ($uids === '*') {
$index = $imap->index($mbox, null, null, true);
$uids = $index->get();
}
foreach ($uids as $uid) {
$headers = $imap->get_message_headers($uid);
if ($mode == 'mbox') {
// Sender address
$from = rcube_mime::decode_address_list($headers->from, null, true, $headers->charset, true);
$from = array_shift($from);
$from = preg_replace('/\s/', '-', $from);
// Received (internal) date
$date = rcube_utils::anytodatetime($headers->internaldate, $timezone);
if ($date) {
$date = $date->format(self::MBOX_DATE_FORMAT);
}
// Mbox format header (RFC4155)
$header = sprintf("From %s %s\r\n",
$from ?: 'MAILER-DAEMON',
$date ?: ''
);
$messages[$uid . ':' . $mbox] = $header;
}
else { // maildir
$subject = rcube_mime::decode_header($headers->subject, $headers->charset);
$subject = $this->_filename_from_subject(mb_substr($subject, 0, 16));
$subject = $this->_convert_filename($subject);
$path = $folders ? str_replace($delimiter, '/', $mbox) . '/' : '';
$disp_name = $path . $uid . ($subject ? " $subject" : '') . '.eml';
$messages[$uid . ':' . $mbox] = $disp_name;
}
$size += $headers->size;
if ($limit > 0 && $size > $limit) {
unlink($tmpfname);
- $msg = $this->gettext(array(
+ $msg = $this->gettext([
'name' => 'sizelimiterror',
- 'vars' => array('$size' => rcmail_action::show_bytes($limit))
- ));
+ 'vars' => ['$size' => rcmail_action::show_bytes($limit)]
+ ]);
$rcmail->output->show_message($msg, 'error');
$rcmail->output->send('iframe');
exit;
}
}
}
if ($mode == 'mbox') {
$tmpfp = fopen($tmpfname . '.mbox', 'w');
if (!$tmpfp) {
exit;
}
}
// open zip file
$zip = new ZipArchive();
$zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
foreach ($messages as $key => $value) {
list($uid, $mbox) = explode(':', $key, 2);
$imap->set_folder($mbox);
if (!empty($tmpfp)) {
fwrite($tmpfp, $value);
// Use stream filter to quote "From " in the message body
stream_filter_register('mbox_filter', 'zipdownload_mbox_filter');
$filter = stream_filter_append($tmpfp, 'mbox_filter');
$imap->get_raw_body($uid, $tmpfp);
stream_filter_remove($filter);
fwrite($tmpfp, "\r\n");
}
else { // maildir
$tmpfn = rcube_utils::temp_filename('zipmessage');
$fp = fopen($tmpfn, 'w');
$imap->get_raw_body($uid, $fp);
$tempfiles[] = $tmpfn;
fclose($fp);
$zip->addFile($tmpfn, $value);
}
}
$filename = $folders ? 'messages' : $imap->get_folder();
if ($tmpfp) {
$tempfiles[] = $tmpfname . '.mbox';
fclose($tmpfp);
$zip->addFile($tmpfname . '.mbox', $filename . '.mbox');
}
$zip->close();
$this->_deliver_zipfile($tmpfname, $filename . '.zip');
// delete temporary files from disk
foreach ($tempfiles as $tmpfn) {
unlink($tmpfn);
}
exit;
}
/**
* Helper method to send the zip archive to the browser
*/
private function _deliver_zipfile($tmpfname, $filename)
{
$rcmail = rcmail::get_instance();
- $rcmail->output->download_headers($filename, array('length' => filesize($tmpfname)));
+ $rcmail->output->download_headers($filename, ['length' => filesize($tmpfname)]);
readfile($tmpfname);
}
/**
* Helper function to convert filenames to the configured charset
*/
private function _convert_filename($str)
{
- $str = strtr($str, array(':' => '', '/' => '-'));
+ $str = strtr($str, [':' => '', '/' => '-']);
return rcube_charset::convert($str, RCUBE_CHARSET, $this->charset);
}
/**
* Helper function to convert message subject into filename
*/
private function _filename_from_subject($str)
{
$str = preg_replace('/[\t\n\r\0\x0B]+\s*/', ' ', $str);
return trim($str, " ./_");
}
}
class zipdownload_mbox_filter extends php_user_filter
{
function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
// messages are read line by line
if (preg_match('/^>*From /', $bucket->data)) {
$bucket->data = '>' . $bucket->data;
$bucket->datalen += 1;
}
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
diff --git a/tests/StorageMock.php b/tests/StorageMock.php
index 8edc29401..4fdbf3ee9 100644
--- a/tests/StorageMock.php
+++ b/tests/StorageMock.php
@@ -1,66 +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: |
| A class for easier testing of code utilizing rcube_storage |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* A class for easier testing of code utilizing rcube_storage
*
* @package Tests
*/
class StorageMock
{
+ public $methodCalls = [];
+
protected $mocks = [];
public function registerFunction($name, $result = null)
{
$this->mocks[] = [$name, $result];
return $this;
}
public function __call($name, $arguments)
{
foreach ($this->mocks as $idx => $mock) {
if ($mock[0] == $name) {
$result = $mock[1];
+ $this->methodCalls[] = ['name' => $name, 'args' => $arguments];
unset($this->mocks[$idx]);
return $result;
}
}
throw new Exception("Unhandled function call for '$name' in StorageMock");
}
/**
* Close connection. Usually done on script shutdown
*/
public function close()
{
// do nothing
}
public function get_hierarchy_delimiter()
{
return '/';
}
public function get_namespace()
{
return null;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Dec 18, 1:30 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
418863
Default Alt Text
(233 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment