Page MenuHomePhorge

No OneTemporary

Size
233 KB
Referenced Files
None
Subscribers
None
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

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)

Event Timeline