Page MenuHomePhorge

No OneTemporary

diff --git a/bin/installto.sh b/bin/installto.sh
index 291bebf71..1d029115a 100755
--- a/bin/installto.sh
+++ b/bin/installto.sh
@@ -1,142 +1,142 @@
#!/usr/bin/env php
<?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: |
| Update an existing Roundcube installation with files from |
| this version |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
if (!function_exists('system')) {
rcube::raise_error("PHP system() function is required. Check disable_functions in php.ini.", false, true);
}
$target_dir = unslashify($_SERVER['argv'][1]);
if (empty($target_dir) || !is_dir(realpath($target_dir)))
rcube::raise_error("Invalid target: not a directory\nUsage: installto.sh <TARGET>", false, true);
// read version from iniset.php
$iniset = @file_get_contents($target_dir . '/program/include/iniset.php');
if (!preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z-]*)/', $iniset, $m))
rcube::raise_error("No valid Roundcube installation found at $target_dir", false, true);
$oldversion = $m[1];
if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '>'))
rcube::raise_error("Target installation already in version $oldversion.", false, true);
if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '==')) {
echo "Target installation already in version $oldversion. Do you want to update again? (y/N)\n";
}
else {
echo "Upgrading from $oldversion. Do you want to continue? (y/N)\n";
}
$input = trim(fgets(STDIN));
if (strtolower($input) == 'y') {
echo "Copying files to target location...";
$adds = array();
$dirs = array('bin','SQL','plugins','skins','program');
if (is_dir(INSTALL_PATH . 'vendor') && !is_file("$target_dir/composer.json")) {
$dirs[] = 'vendor';
}
if (file_exists("$target_dir/installer")) {
$dirs[] = 'installer';
}
foreach ($dirs as $dir) {
// @FIXME: should we use --delete for all directories?
$delete = in_array($dir, array('program', 'vendor', 'installer')) ? '--delete ' : '';
$command = "rsync -aC --out-format=%n " . $delete . INSTALL_PATH . "$dir/ $target_dir/$dir/";
if (system($command, $ret) === false || $ret > 0) {
rcube::raise_error("Failed to execute command: $command", false, true);
}
}
foreach (array('index.php','config/defaults.inc.php','composer.json-dist','jsdeps.json','CHANGELOG','README.md','UPGRADING','LICENSE','INSTALL') as $file) {
$command = "rsync -a --out-format=%n " . INSTALL_PATH . "$file $target_dir/$file";
if (file_exists(INSTALL_PATH . $file) && (system($command, $ret) === false || $ret > 0)) {
rcube::raise_error("Failed to execute command: $command", false, true);
}
}
// Copy .htaccess or .user.ini if needed
foreach (array('.htaccess','.user.ini') as $file) {
if (file_exists(INSTALL_PATH . $file)) {
if (!file_exists("$target_dir/$file") || file_get_contents(INSTALL_PATH . $file) != file_get_contents("$target_dir/$file")) {
if (copy(INSTALL_PATH . $file, "$target_dir/$file.new")) {
echo "$file.new\n";
$adds[] = "NOTICE: New $file file saved as $file.new.";
}
}
}
}
// remove old (<1.0) .htaccess file
@unlink("$target_dir/program/.htaccess");
echo "done.\n\n";
if (is_dir("$target_dir/skins/default")) {
echo "Removing old default skin...";
system("rm -rf $target_dir/skins/default $target_dir/plugins/jqueryui/themes/default");
foreach (glob(INSTALL_PATH . "plugins/*/skins") as $plugin_skin_dir) {
$plugin_skin_dir = preg_replace('!^.*' . INSTALL_PATH . '!', '', $plugin_skin_dir);
if (is_dir("$target_dir/$plugin_skin_dir/classic")) {
system("rm -rf $target_dir/$plugin_skin_dir/default");
}
}
echo "done.\n\n";
}
// check if js-deps are up-to-date
if (file_exists("$target_dir/jsdeps.json") && file_exists("$target_dir/bin/install-jsdeps.sh")) {
$jsdeps = json_decode(file_get_contents("$target_dir/jsdeps.json"));
$package = $jsdeps->dependencies[0];
$dest_file = $target_dir . '/' . $package->dest;
if (!file_exists($dest_file) || sha1_file($dest_file) !== $package->sha1) {
echo "Installing JavaScript dependencies...";
system("cd $target_dir && bin/install-jsdeps.sh");
echo "done.\n\n";
}
}
else {
$adds[] = "NOTICE: JavaScript dependencies installation skipped...";
}
if (file_exists("$target_dir/installer")) {
$adds[] = "NOTICE: The 'installer' directory still exists. You should remove it after the upgrade.";
}
if (!empty($adds)) {
- echo implode($adds, "\n") . "\n\n";
+ echo implode("\n", $adds) . "\n\n";
}
echo "Running update script at target...\n";
system("cd $target_dir && php bin/update.sh --version=$oldversion");
echo "All done.\n";
}
else {
echo "Update cancelled. See ya!\n";
}
diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php
index 00156c69c..781ad1034 100644
--- a/plugins/enigma/lib/enigma_ui.php
+++ b/plugins/enigma/lib/enigma_ui.php
@@ -1,1315 +1,1315 @@
<?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 = array();
private $keys_bodies = array();
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.
*
* @param string Preferences section
*/
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(array(
'keyslist' => array($this, 'tpl_keys_list'),
'countdisplay' => array($this, 'tpl_keys_rowcount'),
'searchform' => array($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(array(
'keyslist' => array($this, 'tpl_certs_list'),
'keyframe' => array($this, 'tpl_cert_frame'),
'countdisplay' => array($this, 'tpl_certs_rowcount'),
'searchform' => array($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 = array())
{
$data = $status->getData('missing');
if (empty($data)) {
$data = $status->getData('bad');
}
$keyid = key($data);
$data = array(
'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 && $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 Object attributes
*
* @return string HTML content
*/
function tpl_keys_list($attrib)
{
// add id to message list table if not specified
if (!strlen($attrib['id'])) {
$attrib['id'] = 'rcmenigmakeyslist';
}
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table
$out = $this->rc->table_output($attrib, array(), $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);
if ($list && ($list instanceof enigma_error))
$this->rc->output->show_message('enigma.keylisterror', 'error');
else if (empty($list))
$this->rc->output->show_message('enigma.nokeysfound', 'notice');
else if (is_array($list)) {
// Save the size
$listsize = count($list);
// Sort the list by key (user) name
usort($list, array('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', array(
'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 Object attributes
*
* @return string HTML output
*/
function tpl_keys_rowcount($attrib)
{
if (!$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(array(
'name' => 'keysfromto',
'vars' => array(
'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(array(
'keyname' => array($this, 'tpl_key_name'),
'keydata' => array($this, 'tpl_key_data'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
$this->rc->output->send('enigma.keyinfo');
}
/**
* Template object for key name
*/
function tpl_key_name($attrib)
{
return rcube::Q($this->data->name);
}
/**
* Template object for key information page content
*/
function tpl_key_data($attrib)
{
$out = '';
$table = new html_table(array('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();
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(array('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 = array(
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 = array();
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(array('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) {
$username = $user->name;
if ($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, array(
'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',
array('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 ($_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',
array('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 ($err = $_FILES['_file']['error']) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$this->rc->output->show_message('filesizeerror', 'error',
array('size' => $this->rc->show_bytes(rcube_utils::max_upload_size())));
} else {
$this->rc->output->show_message('fileuploaderror', 'error');
}
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers(array(
'importform' => array($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(array(
'importform' => array($this, 'tpl_key_import_form'),
));
$this->rc->output->send('enigma.keysearch');
}
/**
* Template object for key import (upload) form
*/
function tpl_key_import_form($attrib)
{
$attrib += array('id' => 'rcmKeyImportForm');
if (empty($attrib['part']) || $attrib['part'] == 'import') {
$title = $this->enigma->gettext('keyimportlabel');
$upload = new html_inputfield(array('type' => 'file', 'name' => '_file',
'id' => 'rcmimportfile', 'size' => 30));
$max_filesize = $this->rc->upload_init();
$upload_button = new html_button(array(
'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(array('id' => 'importfile', 'name' => 'maxuploadsize', 'vars' => array('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(array('type' => 'text', 'name' => '_search',
'id' => 'rcmimportsearch', 'size' => 30));
$search_button = new html_button(array(
'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(array(
'action' => $this->rc->url(array('action' => $this->rc->action, 'a' => 'import')),
'method' => 'post',
'enctype' => 'multipart/form-data') + $attrib,
$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(array(
'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(array(
'keyform' => array($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
*/
function tpl_key_create_form($attrib)
{
$attrib += array('id' => 'rcmKeyCreateForm');
$table = new html_table(array('cols' => 2));
// get user's identities
$identities = $this->rc->user->list_identities(null, true);
$checkbox = new html_checkbox(array('name' => 'identity[]'));
$plugin = $this->rc->plugins->exec_hook('enigma_user_identities', array('identities' => $identities));
$identities = $plugin['identities'];
foreach ($identities as $idx => $ident) {
$name = empty($ident['name']) ? ($ident['email']) : $ident['ident'];
$attr = array('value' => $idx, 'data-name' => $ident['name'], 'data-email' => $ident['email']);
$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($identities, "\n")));
+ $table->add(null, html::tag('ul', 'proplist', implode("\n", $identities)));
// Key size
$select = new html_select(array('name' => 'size', 'id' => 'key-size'));
$select->add($this->enigma->gettext('key2048'), '2048');
$select->add($this->enigma->gettext('key4096'), '4096');
$table->add('title', html::label('key-size', rcube::Q($this->enigma->gettext('newkeysize'))));
$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', '', array(
'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', '', array(
'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(array(), $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(array(
'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(array('value' => 1));
$out = html::div('form-group form-check row',
html::label(array('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, array(
'name' => '_enigma_sign',
'id' => 'enigmasignopt',
'class' => 'form-check-input',
'disabled' => in_array('sign', $locks),
))));
$out .= html::div('form-group form-check row',
html::label(array('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, array(
'name' => '_enigma_encrypt',
'id' => 'enigmaencryptopt',
'class' => 'form-check-input',
'disabled' => in_array('encrypt', $locks),
))));
$out .= html::div('form-group form-check row',
html::label(array('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, array(
'name' => '_enigma_attachpubkey',
'id' => 'enigmaattachpubkeyopt',
'class' => 'form-check-input',
'disabled' => in_array('pubkey', $locks),
))));
if (!$wrap) {
return $out;
}
return html::div(array('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 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 = array();
// Decryption status
if (($found = $this->find_part_id($part_id, $engine->decryptions)) !== null
&& ($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
&& ($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'));
}
/*
$msg .= '&nbsp;' . html::a(array('href' => "#sigdetails",
'onclick' => rcmail_output::JS_OBJECT_NAME.".command('enigma-sig-details')"),
rcube::Q($this->enigma->gettext('showdetails')));
*/
// test
// $msg .= '<br /><pre>'.$sig->body.'</pre>';
$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 ($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(array('class' => 'enigmaattachment boxinformation aligned-buttons'),
html::span(null, rcube::Q($this->enigma->gettext('keyattfound'))) .
html::tag('button', array(
'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 ($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']);
}
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();
if ($code == enigma_error::KEYNOTFOUND) {
if ($email = $status->getData('missing')) {
$vars = array('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 ($msg) {
if ($vars && $vars['email']) {
$this->rc->output->command('enigma_key_not_found', array(
'email' => $vars['email'],
'text' => $this->rc->gettext(array('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::KEYNOTFOUND) {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->enigma->gettext('decryptnokey')));
}
else if ($code == enigma_error::BADPASS) {
$this->password_prompt($status, array('compose-init' => true));
return $p;
}
else {
$msg = rcube::Q($this->enigma->gettext('decrypterror'));
}
}
}
if ($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);
$storage = $this->rc->get_storage();
$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, array(
'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 ($part && is_array($result = $engine->import_key($part))) {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('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/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 6e0de82d9..237a47648 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -1,1043 +1,1043 @@
<?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: |
| Interface to the local address book database |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
/**
* Model class for the local address book database
*
* @package Framework
* @subpackage Addressbook
*/
class rcube_contacts extends rcube_addressbook
{
// protected for backward compat. with some plugins
protected $db_name = 'contacts';
protected $db_groups = 'contactgroups';
protected $db_groupmembers = 'contactgroupmembers';
protected $vcard_fieldmap = array();
/**
* Store database connection.
*
* @var rcube_db
*/
private $db = null;
private $user_id = 0;
private $filter = null;
private $result = null;
private $cache;
private $table_cols = array('name', 'email', 'firstname', 'surname');
private $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'nickname',
'jobtitle', 'organization', 'department', 'maidenname', 'email', 'phone',
'address', 'street', 'locality', 'zipcode', 'region', 'country', 'website', 'im', 'notes');
// public properties
public $primary_key = 'contact_id';
public $name;
public $readonly = false;
public $groups = true;
public $undelete = true;
public $list_page = 1;
public $page_size = 10;
public $group_id = 0;
public $ready = false;
public $coltypes = array('name', 'firstname', 'surname', 'middlename', 'prefix', 'suffix', 'nickname',
'jobtitle', 'organization', 'department', 'assistant', 'manager',
'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
public $date_cols = array('birthday', 'anniversary');
const SEPARATOR = ',';
/**
* Object constructor
*
* @param object $dbconn Instance of the rcube_db class
* @param integer $user User-ID
*/
function __construct($dbconn, $user)
{
$this->db = $dbconn;
$this->user_id = $user;
$this->ready = $this->db && !$this->db->is_error();
}
/**
* Returns addressbook name
*/
function get_name()
{
return $this->name;
}
/**
* Save a search string for future listings
*
* @param string $filter SQL params to use in listing method
*/
function set_search_set($filter)
{
$this->filter = $filter;
$this->cache = null;
}
/**
* Getter for saved search properties
*
* @return mixed Search properties used by this class
*/
function get_search_set()
{
return $this->filter;
}
/**
* Setter for the current group
* (empty, has to be re-implemented by extending class)
*/
function set_group($gid)
{
$this->group_id = $gid;
$this->cache = null;
}
/**
* Reset all saved results and search parameters
*/
function reset()
{
$this->result = null;
$this->filter = null;
$this->cache = null;
}
/**
* List all active contact groups of this source
*
* @param string $search Search string to match group name
* @param int $mode Matching mode. Sum of rcube_addressbook::SEARCH_*
*
* @return array Indexed list of contact groups, each a hash array
*/
function list_groups($search = null, $mode = 0)
{
$results = array();
if (!$this->groups) {
return $results;
}
if ($search) {
if ($mode & rcube_addressbook::SEARCH_STRICT) {
$sql_filter = $this->db->ilike('name', $search);
}
else if ($mode & rcube_addressbook::SEARCH_PREFIX) {
$sql_filter = $this->db->ilike('name', $search . '%');
}
else {
$sql_filter = $this->db->ilike('name', '%' . $search . '%');
}
$sql_filter = " AND $sql_filter";
}
$sql_result = $this->db->query(
"SELECT * FROM " . $this->db->table_name($this->db_groups, true)
. " WHERE `del` <> 1 AND `user_id` = ?" . $sql_filter
. " ORDER BY `name`",
$this->user_id);
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$sql_arr['ID'] = $sql_arr['contactgroup_id'];
$results[] = $sql_arr;
}
return $results;
}
/**
* Get group properties such as name and email address(es)
*
* @param string $group_id Group identifier
*
* @return array Group properties as hash array
*/
function get_group($group_id)
{
$sql_result = $this->db->query(
"SELECT * FROM " . $this->db->table_name($this->db_groups, true)
. " WHERE `del` <> 1 AND `contactgroup_id` = ? AND `user_id` = ?",
$group_id, $this->user_id);
if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$sql_arr['ID'] = $sql_arr['contactgroup_id'];
return $sql_arr;
}
return null;
}
/**
* List the current set of contact records
*
* @param array List of cols to show, Null means all
* @param int Only return this number of records, use negative values for tail
* @param boolean True to skip the count query (select only)
*
* @return array Indexed list of contact records, each a hash array
*/
function list_records($cols = null, $subset = 0, $nocount = false)
{
if ($nocount || $this->list_page <= 1) {
// create dummy result, we don't need a count now
$this->result = new rcube_result_set();
} else {
// count all records
$this->result = $this->count();
}
$start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
$length = $subset != 0 ? abs($subset) : $this->page_size;
if ($this->group_id)
$join = " LEFT JOIN " . $this->db->table_name($this->db_groupmembers, true) . " AS m".
" ON (m.`contact_id` = c.`".$this->primary_key."`)";
$order_col = (in_array($this->sort_col, $this->table_cols) ? $this->sort_col : 'name');
$order_cols = array("c.`$order_col`");
if ($order_col == 'firstname')
$order_cols[] = 'c.`surname`';
else if ($order_col == 'surname')
$order_cols[] = 'c.`firstname`';
if ($order_col != 'name')
$order_cols[] = 'c.`name`';
$order_cols[] = 'c.`email`';
$sql_result = $this->db->limitquery(
"SELECT * FROM " . $this->db->table_name($this->db_name, true) . " AS c" .
$join .
" WHERE c.`del` <> 1" .
" AND c.`user_id` = ?" .
($this->group_id ? " AND m.`contactgroup_id` = ?" : "").
($this->filter ? " AND ".$this->filter : "") .
" ORDER BY ". $this->db->concat($order_cols) .
" " . $this->sort_order,
$start_row,
$length,
$this->user_id,
$this->group_id);
// determine whether we have to parse the vcard or if only db cols are requested
$read_vcard = !$cols || count(array_intersect($cols, $this->table_cols)) < count($cols);
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$sql_arr['ID'] = $sql_arr[$this->primary_key];
if ($read_vcard)
$sql_arr = $this->convert_db_data($sql_arr);
else {
$sql_arr['email'] = $sql_arr['email'] ? explode(self::SEPARATOR, $sql_arr['email']) : array();
$sql_arr['email'] = array_map('trim', $sql_arr['email']);
}
$this->result->add($sql_arr);
}
$cnt = count($this->result->records);
// update counter
if ($nocount)
$this->result->count = $cnt;
else if ($this->list_page <= 1) {
if ($cnt < $this->page_size && $subset == 0)
$this->result->count = $cnt;
else if (isset($this->cache['count']))
$this->result->count = $this->cache['count'];
else
$this->result->count = $this->_count();
}
return $this->result;
}
/**
* Search contacts
*
* @param mixed $fields The field name or array of field names to search in
* @param mixed $value Search value (or array of values when $fields is array)
* @param int $mode Search mode. Sum of rcube_addressbook::SEARCH_*
* @param boolean $select True if results are requested, False if count only
* @param boolean $nocount True to skip the count query (select only)
* @param array $required List of fields that cannot be empty
*
* @return object rcube_result_set Contact records and 'count' value
*/
function search($fields, $value, $mode = 0, $select = true, $nocount = false, $required = array())
{
if (!is_array($required) && !empty($required)) {
$required = array($required);
}
$where = $and_where = $post_search = array();
$mode = intval($mode);
$WS = ' ';
$AS = self::SEPARATOR;
// direct ID search
if ($fields == 'ID' || $fields == $this->primary_key) {
$ids = !is_array($value) ? explode(self::SEPARATOR, $value) : $value;
$ids = $this->db->array2list($ids, 'integer');
$where[] = 'c.' . $this->primary_key.' IN ('.$ids.')';
}
else if (is_array($value)) {
foreach ((array)$fields as $idx => $col) {
$val = $value[$idx];
if (!strlen($val)) {
continue;
}
// table column
if (in_array($col, $this->table_cols)) {
$where[] = $this->fulltext_sql_where($val, $mode, $col);
}
// vCard field
else {
if (in_array($col, $this->fulltext_cols)) {
$where[] = $this->fulltext_sql_where($val, $mode, 'words');
}
$post_search[$col] = mb_strtolower($val);
}
}
}
// fulltext search in all fields
else if ($fields == '*') {
$where[] = $this->fulltext_sql_where($value, $mode, 'words');
}
else {
// require each word in to be present in one of the fields
$words = ($mode & rcube_addressbook::SEARCH_STRICT) ? array($value) : rcube_utils::tokenize_string($value, 1);
foreach ($words as $word) {
$groups = array();
foreach ((array)$fields as $idx => $col) {
$groups[] = $this->fulltext_sql_where($word, $mode, $col);
}
$where[] = '(' . join(' OR ', $groups) . ')';
}
}
foreach (array_intersect($required, $this->table_cols) as $col) {
$where[] = $this->db->quote_identifier($col).' <> '.$this->db->quote('');
}
$required = array_diff($required, $this->table_cols);
if (!empty($where)) {
// use AND operator for advanced searches
$where = join(" AND ", $where);
}
// Post-searching in vCard data fields
// we will search in all records and then build a where clause for their IDs
if (!empty($post_search) || !empty($required)) {
$ids = array(0);
// build key name regexp
- $regexp = '/^(' . implode(array_keys($post_search), '|') . ')(?:.*)$/';
+ $regexp = '/^(' . implode('|', array_keys($post_search)) . ')(?:.*)$/';
// use initial WHERE clause, to limit records number if possible
if (!empty($where))
$this->set_search_set($where);
// count result pages
$cnt = $this->count()->count;
$pages = ceil($cnt / $this->page_size);
$scnt = !empty($post_search) ? count($post_search) : 0;
// get (paged) result
for ($i=0; $i<$pages; $i++) {
$this->list_records(null, $i, true);
while ($row = $this->result->next()) {
$id = $row[$this->primary_key];
$found = array();
if (!empty($post_search)) {
foreach (preg_grep($regexp, array_keys($row)) as $col) {
$pos = strpos($col, ':');
$colname = $pos ? substr($col, 0, $pos) : $col;
$search = $post_search[$colname];
foreach ((array)$row[$col] as $value) {
if ($this->compare_search_value($colname, $value, $search, $mode)) {
$found[$colname] = true;
break;
}
}
}
}
// check if required fields are present
if (!empty($required)) {
foreach ($required as $req) {
$hit = false;
foreach ($row as $c => $values) {
if ($c === $req || strpos($c, $req.':') === 0) {
if ((is_string($row[$c]) && strlen($row[$c])) || !empty($row[$c])) {
$hit = true;
break;
}
}
}
if (!$hit) {
continue 2;
}
}
}
// all fields match
if (count($found) >= $scnt) {
$ids[] = $id;
}
}
}
// build WHERE clause
$ids = $this->db->array2list($ids, 'integer');
$where = 'c.`' . $this->primary_key.'` IN ('.$ids.')';
// reset counter
unset($this->cache['count']);
// when we know we have an empty result
if ($ids == '0') {
$this->set_search_set($where);
return ($this->result = new rcube_result_set(0, 0));
}
}
if (!empty($where)) {
$this->set_search_set($where);
if ($select)
$this->list_records(null, 0, $nocount);
else
$this->result = $this->count();
}
return $this->result;
}
/**
* Helper method to compose SQL where statements for fulltext searching
*/
private function fulltext_sql_where($value, $mode, $col = 'words', $bool = 'AND')
{
$WS = ' ';
$AS = $col == 'words' ? $WS : self::SEPARATOR;
$words = $col == 'words' ? rcube_utils::normalize_string($value, true) : array($value);
$where = array();
foreach ($words as $word) {
if ($mode & rcube_addressbook::SEARCH_STRICT) {
$where[] = '(' . $this->db->ilike($col, $word)
. ' OR ' . $this->db->ilike($col, $word . $AS . '%')
. ' OR ' . $this->db->ilike($col, '%' . $AS . $word . $AS . '%')
. ' OR ' . $this->db->ilike($col, '%' . $AS . $word) . ')';
}
else if ($mode & rcube_addressbook::SEARCH_PREFIX) {
$where[] = '(' . $this->db->ilike($col, $word . '%')
. ' OR ' . $this->db->ilike($col, '%' . $AS . $word . '%') . ')';
}
else {
$where[] = $this->db->ilike($col, '%' . $word . '%');
}
}
return count($where) ? '(' . join(" $bool ", $where) . ')' : '';
}
/**
* Count number of available contacts in database
*
* @return rcube_result_set Result object
*/
function count()
{
$count = isset($this->cache['count']) ? $this->cache['count'] : $this->_count();
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
}
/**
* Count number of available contacts in database
*
* @return int Contacts count
*/
private function _count()
{
if ($this->group_id)
$join = " LEFT JOIN " . $this->db->table_name($this->db_groupmembers, true) . " AS m".
" ON (m.`contact_id` = c.`".$this->primary_key."`)";
// count contacts for this user
$sql_result = $this->db->query(
"SELECT COUNT(c.`contact_id`) AS cnt".
" FROM " . $this->db->table_name($this->db_name, true) . " AS c".
$join.
" WHERE c.`del` <> 1".
" AND c.`user_id` = ?".
($this->group_id ? " AND m.`contactgroup_id` = ?" : "").
($this->filter ? " AND (".$this->filter.")" : ""),
$this->user_id,
$this->group_id
);
$sql_arr = $this->db->fetch_assoc($sql_result);
$this->cache['count'] = (int) $sql_arr['cnt'];
return $this->cache['count'];
}
/**
* Return the last result set
*
* @return mixed Result array or NULL if nothing selected yet
*/
function get_result()
{
return $this->result;
}
/**
* Get a specific contact record
*
* @param mixed $id Record identifier(s)
* @param bool $assoc Enables returning associative array
*
* @return rcube_result_set|array Result object with all record fields
*/
function get_record($id, $assoc = false)
{
// return cached result
if ($this->result && ($first = $this->result->first()) && $first[$this->primary_key] == $id) {
return $assoc ? $first : $this->result;
}
$this->db->query(
"SELECT * FROM " . $this->db->table_name($this->db_name, true).
" WHERE `contact_id` = ?".
" AND `user_id` = ?".
" AND `del` <> 1",
$id,
$this->user_id
);
$this->result = null;
if ($sql_arr = $this->db->fetch_assoc()) {
$record = $this->convert_db_data($sql_arr);
$this->result = new rcube_result_set(1);
$this->result->add($record);
}
return $assoc && $record ? $record : $this->result;
}
/**
* Get group assignments of a specific contact record
*
* @param mixed $id Record identifier
*
* @return array List of assigned groups as ID=>Name pairs
*/
function get_record_groups($id)
{
$results = array();
if (!$this->groups) {
return $results;
}
$sql_result = $this->db->query(
"SELECT cgm.`contactgroup_id`, cg.`name` "
. " FROM " . $this->db->table_name($this->db_groupmembers, true) . " AS cgm"
. " LEFT JOIN " . $this->db->table_name($this->db_groups, true) . " AS cg"
. " ON (cgm.`contactgroup_id` = cg.`contactgroup_id` AND cg.`del` <> 1)"
. " WHERE cgm.`contact_id` = ?",
$id
);
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$results[$sql_arr['contactgroup_id']] = $sql_arr['name'];
}
return $results;
}
/**
* Check the given data before saving.
* If input not valid, the message to display can be fetched using get_error()
*
* @param array &$save_data Associative array with data to save
* @param boolean $autofix Try to fix/complete record automatically
*
* @return boolean True if input is valid, False if not.
*/
public function validate(&$save_data, $autofix = false)
{
// validate e-mail addresses
$valid = parent::validate($save_data, $autofix);
// require at least some name or email
if ($valid
&& !strlen($save_data['firstname'].$save_data['surname'].$save_data['name'])
&& !count(array_filter($this->get_col_values('email', $save_data, true)))
) {
$this->set_error(self::ERROR_VALIDATE, 'nonamewarning');
$valid = false;
}
return $valid;
}
/**
* Create a new contact record
*
* @param array $save_data Associative array with save data
* @param bool $check Enables validity checks
*
* @return integer|boolean The created record ID on success, False on error
*/
function insert($save_data, $check = false)
{
if (!is_array($save_data)) {
return false;
}
$insert_id = $existing = false;
if ($check) {
foreach ($save_data as $col => $values) {
if (strpos($col, 'email') === 0) {
foreach ((array)$values as $email) {
if ($existing = $this->search('email', $email, false, false))
break 2;
}
}
}
}
$save_data = $this->convert_save_data($save_data);
$a_insert_cols = $a_insert_values = array();
foreach ($save_data as $col => $value) {
$a_insert_cols[] = $this->db->quote_identifier($col);
$a_insert_values[] = $this->db->quote($value);
}
if (!$existing->count && !empty($a_insert_cols)) {
$this->db->query(
"INSERT INTO " . $this->db->table_name($this->db_name, true).
" (`user_id`, `changed`, `del`, ".join(', ', $a_insert_cols).")".
" VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
);
$insert_id = $this->db->insert_id($this->db_name);
}
$this->cache = null;
return $insert_id;
}
/**
* Update a specific contact record
*
* @param mixed $id Record identifier
* @param array $save_cols Associative array with save data
*
* @return boolean True on success, False on error
*/
function update($id, $save_cols)
{
$updated = false;
$write_sql = array();
$record = $this->get_record($id, true);
$save_cols = $this->convert_save_data($save_cols, $record);
foreach ($save_cols as $col => $value) {
$write_sql[] = sprintf("%s=%s", $this->db->quote_identifier($col), $this->db->quote($value));
}
if (!empty($write_sql)) {
$this->db->query(
"UPDATE " . $this->db->table_name($this->db_name, true).
" SET `changed` = ".$this->db->now().", ".join(', ', $write_sql).
" WHERE `contact_id` = ?".
" AND `user_id` = ?".
" AND `del` <> 1",
$id,
$this->user_id
);
$updated = $this->db->affected_rows();
$this->result = null; // clear current result (from get_record())
}
return !empty($updated);
}
/**
* Convert data stored in the database into output format
*/
private function convert_db_data($sql_arr)
{
$record = array();
$record['ID'] = $sql_arr[$this->primary_key];
if ($sql_arr['vcard']) {
unset($sql_arr['email']);
$vcard = new rcube_vcard($sql_arr['vcard'], RCUBE_CHARSET, false, $this->vcard_fieldmap);
$record += $vcard->get_assoc() + $sql_arr;
}
else {
$record += $sql_arr;
$record['email'] = explode(self::SEPARATOR, $record['email']);
$record['email'] = array_map('trim', $record['email']);
}
return $record;
}
/**
* Convert input data for storing in the database
*/
private function convert_save_data($save_data, $record = array())
{
$out = array();
$words = '';
// copy values into vcard object
$vcard = new rcube_vcard($record['vcard'] ?: $save_data['vcard'], RCUBE_CHARSET, false, $this->vcard_fieldmap);
$vcard->reset();
// don't store groups in vCard (#1490277)
$vcard->set('groups', null);
unset($save_data['groups']);
foreach ($save_data as $key => $values) {
list($field, $section) = explode(':', $key);
$fulltext = in_array($field, $this->fulltext_cols);
// avoid casting DateTime objects to array
if (is_object($values) && is_a($values, 'DateTime')) {
$values = array(0 => $values);
}
foreach ((array)$values as $value) {
if (isset($value))
$vcard->set($field, $value, $section);
if ($fulltext && is_array($value))
$words .= ' ' . rcube_utils::normalize_string(join(" ", $value));
else if ($fulltext && strlen($value) >= 3)
$words .= ' ' . rcube_utils::normalize_string($value);
}
}
$out['vcard'] = $vcard->export(false);
foreach ($this->table_cols as $col) {
$key = $col;
if (!isset($save_data[$key]))
$key .= ':home';
if (isset($save_data[$key])) {
if (is_array($save_data[$key]))
$out[$col] = join(self::SEPARATOR, $save_data[$key]);
else
$out[$col] = $save_data[$key];
}
}
// save all e-mails in database column
$out['email'] = join(self::SEPARATOR, $vcard->email);
// join words for fulltext search
$out['words'] = join(" ", array_unique(explode(" ", $words)));
return $out;
}
/**
* Mark one or more contact records as deleted
*
* @param array $ids Record identifiers
* @param boolean $force Remove record(s) irreversible (unsupported)
*/
function delete($ids, $force = true)
{
if (!is_array($ids)) {
$ids = explode(self::SEPARATOR, $ids);
}
$ids = $this->db->array2list($ids, 'integer');
// flag record as deleted (always)
$this->db->query(
"UPDATE " . $this->db->table_name($this->db_name, true).
" SET `del` = 1, `changed` = ".$this->db->now().
" WHERE `user_id` = ?".
" AND `contact_id` IN ($ids)",
$this->user_id
);
$this->cache = null;
return $this->db->affected_rows();
}
/**
* Undelete one or more contact records
*
* @param array $ids Record identifiers
*/
function undelete($ids)
{
if (!is_array($ids)) {
$ids = explode(self::SEPARATOR, $ids);
}
$ids = $this->db->array2list($ids, 'integer');
// clear deleted flag
$this->db->query(
"UPDATE " . $this->db->table_name($this->db_name, true).
" SET `del` = 0, `changed` = ".$this->db->now().
" WHERE `user_id` = ?".
" AND `contact_id` IN ($ids)",
$this->user_id
);
$this->cache = null;
return $this->db->affected_rows();
}
/**
* Remove all records from the database
*
* @param bool $with_groups Remove also groups
*
* @return int Number of removed records
*/
function delete_all($with_groups = false)
{
$this->cache = null;
$now = $this->db->now();
$this->db->query("UPDATE " . $this->db->table_name($this->db_name, true)
. " SET `del` = 1, `changed` = $now"
. " WHERE `user_id` = ?", $this->user_id);
$count = $this->db->affected_rows();
if ($with_groups) {
$this->db->query("UPDATE " . $this->db->table_name($this->db_groups, true)
. " SET `del` = 1, `changed` = $now"
. " WHERE `user_id` = ?", $this->user_id);
$count += $this->db->affected_rows();
}
return $count;
}
/**
* Create a contact group with the given name
*
* @param string $name The group name
*
* @return mixed False on error, array with record props in success
*/
function create_group($name)
{
$result = false;
// make sure we have a unique name
$name = $this->unique_groupname($name);
$this->db->query(
"INSERT INTO " . $this->db->table_name($this->db_groups, true).
" (`user_id`, `changed`, `name`)".
" VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
);
if ($insert_id = $this->db->insert_id($this->db_groups)) {
$result = array('id' => $insert_id, 'name' => $name);
}
return $result;
}
/**
* Delete the given group (and all linked group members)
*
* @param string $gid Group identifier
*
* @return boolean True on success, false if no data was changed
*/
function delete_group($gid)
{
// flag group record as deleted
$this->db->query(
"UPDATE " . $this->db->table_name($this->db_groups, true)
. " SET `del` = 1, `changed` = " . $this->db->now()
. " WHERE `contactgroup_id` = ?"
. " AND `user_id` = ?",
$gid, $this->user_id
);
$this->cache = null;
return $this->db->affected_rows();
}
/**
* Rename a specific contact group
*
* @param string $gid Group identifier
* @param string $name New name to set for this group
* @param string $new_gid (not used)
*
* @return boolean New name on success, false if no data was changed
*/
function rename_group($gid, $name, &$new_gid)
{
// make sure we have a unique name
$name = $this->unique_groupname($name);
$sql_result = $this->db->query(
"UPDATE " . $this->db->table_name($this->db_groups, true).
" SET `name` = ?, `changed` = ".$this->db->now().
" WHERE `contactgroup_id` = ?".
" AND `user_id` = ?",
$name, $gid, $this->user_id
);
return $this->db->affected_rows($sql_result) ? $name : false;
}
/**
* Add the given contact records the a certain group
*
* @param string Group identifier
* @param array|string List of contact identifiers to be added
*
* @return int Number of contacts added
*/
function add_to_group($group_id, $ids)
{
if (!is_array($ids)) {
$ids = explode(self::SEPARATOR, $ids);
}
$added = 0;
$exists = array();
// get existing assignments ...
$sql_result = $this->db->query(
"SELECT `contact_id` FROM " . $this->db->table_name($this->db_groupmembers, true).
" WHERE `contactgroup_id` = ?".
" AND `contact_id` IN (".$this->db->array2list($ids, 'integer').")",
$group_id
);
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$exists[] = $sql_arr['contact_id'];
}
// ... and remove them from the list
$ids = array_diff($ids, $exists);
foreach ($ids as $contact_id) {
$this->db->query(
"INSERT INTO " . $this->db->table_name($this->db_groupmembers, true).
" (`contactgroup_id`, `contact_id`, `created`)".
" VALUES (?, ?, ".$this->db->now().")",
$group_id,
$contact_id
);
if ($error = $this->db->is_error()) {
$this->set_error(self::ERROR_SAVING, $error);
}
else {
$added++;
}
}
return $added;
}
/**
* Remove the given contact records from a certain group
*
* @param string $group_id Group identifier
* @param array|string $ids List of contact identifiers to be removed
*
* @return int Number of deleted group members
*/
function remove_from_group($group_id, $ids)
{
if (!is_array($ids))
$ids = explode(self::SEPARATOR, $ids);
$ids = $this->db->array2list($ids, 'integer');
$sql_result = $this->db->query(
"DELETE FROM " . $this->db->table_name($this->db_groupmembers, true).
" WHERE `contactgroup_id` = ?".
" AND `contact_id` IN ($ids)",
$group_id
);
return $this->db->affected_rows($sql_result);
}
/**
* Check for existing groups with the same name
*
* @param string $name Name to check
*
* @return string A group name which is unique for the current use
*/
private function unique_groupname($name)
{
$checkname = $name;
$num = 2;
$hit = false;
do {
$sql_result = $this->db->query(
"SELECT 1 FROM " . $this->db->table_name($this->db_groups, true).
" WHERE `del` <> 1".
" AND `user_id` = ?".
" AND `name` = ?",
$this->user_id,
$checkname);
// append number to make name unique
if ($hit = $this->db->fetch_array($sql_result)) {
$checkname = $name . ' ' . $num++;
}
}
while ($hit);
return $checkname;
}
}
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 12e3be6a7..e17cfdf02 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -1,1389 +1,1389 @@
<?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: |
| Database wrapper class that implements PHP PDO functions |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Database independent query interface.
* This is a wrapper for the PHP PDO.
*
* @package Framework
* @subpackage Database
*/
class rcube_db
{
public $db_provider;
protected $db_dsnw; // DSN for write operations
protected $db_dsnr; // DSN for read operations
protected $db_connected = false; // Already connected ?
protected $db_mode; // Connection mode
protected $dbh; // Connection handle
protected $dbhs = array();
protected $table_connections = array();
protected $db_error = false;
protected $db_error_msg = '';
protected $conn_failure = false;
protected $db_index = 0;
protected $last_result;
protected $tables;
protected $variables;
protected $options = array(
// column/table quotes
'identifier_start' => '"',
'identifier_end' => '"',
// date/time input format
'datetime_format' => 'Y-m-d H:i:s',
);
const DEBUG_LINE_LENGTH = 4096;
const DEFAULT_QUOTE = '`';
/**
* Factory, returns driver-specific instance of the class
*
* @param string $db_dsnw DSN for read/write operations
* @param string $db_dsnr Optional DSN for read only operations
* @param bool $pconn Enables persistent connections
*
* @return rcube_db Object instance
*/
public static function factory($db_dsnw, $db_dsnr = '', $pconn = false)
{
$driver = strtolower(substr($db_dsnw, 0, strpos($db_dsnw, ':')));
$driver_map = array(
'sqlite2' => 'sqlite',
'sybase' => 'mssql',
'dblib' => 'mssql',
'mysqli' => 'mysql',
'oci' => 'oracle',
'oci8' => 'oracle',
);
$driver = isset($driver_map[$driver]) ? $driver_map[$driver] : $driver;
$class = "rcube_db_$driver";
if (!$driver || !class_exists($class)) {
rcube::raise_error(array('code' => 600, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => "Configuration error. Unsupported database driver: $driver"),
true, true);
}
return new $class($db_dsnw, $db_dsnr, $pconn);
}
/**
* Object constructor
*
* @param string $db_dsnw DSN for read/write operations
* @param string $db_dsnr Optional DSN for read only operations
* @param bool $pconn Enables persistent connections
*/
public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
{
if (empty($db_dsnr)) {
$db_dsnr = $db_dsnw;
}
$this->db_dsnw = $db_dsnw;
$this->db_dsnr = $db_dsnr;
$this->db_pconn = $pconn;
$this->db_dsnw_array = self::parse_dsn($db_dsnw);
$this->db_dsnr_array = self::parse_dsn($db_dsnr);
$config = rcube::get_instance()->config;
$this->options['table_prefix'] = $config->get('db_prefix');
$this->options['dsnw_noread'] = $config->get('db_dsnw_noread', false);
$this->options['table_dsn_map'] = array_map(array($this, 'table_name'), $config->get('db_table_dsn', array()));
}
/**
* Connect to specific database
*
* @param array $dsn DSN for DB connections
* @param string $mode Connection mode (r|w)
*/
protected function dsn_connect($dsn, $mode)
{
$this->db_error = false;
$this->db_error_msg = null;
// return existing handle
if ($this->dbhs[$mode]) {
$this->dbh = $this->dbhs[$mode];
$this->db_mode = $mode;
return $this->dbh;
}
// connect to database
if ($dbh = $this->conn_create($dsn)) {
$this->dbhs[$mode] = $dbh;
$this->db_mode = $mode;
$this->db_connected = true;
}
}
/**
* Create PDO connection
*/
protected function conn_create($dsn)
{
// Get database specific connection options
$dsn_string = $this->dsn_string($dsn);
$dsn_options = $this->dsn_options($dsn);
// Connect
try {
// with this check we skip fatal error on PDO object creation
if (!class_exists('PDO', false)) {
throw new Exception('PDO extension not loaded. See http://php.net/manual/en/intro.pdo.php');
}
$this->conn_prepare($dsn);
$this->dbh = new PDO($dsn_string, $dsn['username'], $dsn['password'], $dsn_options);
// don't throw exceptions or warnings
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$this->conn_configure($dsn, $this->dbh);
}
catch (Exception $e) {
$this->db_error = true;
$this->db_error_msg = $e->getMessage();
rcube::raise_error(array('code' => 500, 'type' => 'db',
'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg), true, false);
return null;
}
return $this->dbh;
}
/**
* Driver-specific preparation of database connection
*
* @param array $dsn DSN for DB connections
*/
protected function conn_prepare($dsn)
{
}
/**
* Driver-specific configuration of database connection
*
* @param array $dsn DSN for DB connections
* @param PDO $dbh Connection handler
*/
protected function conn_configure($dsn, $dbh)
{
}
/**
* Connect to appropriate database depending on the operation
*
* @param string $mode Connection mode (r|w)
* @param boolean $force Enforce using the given mode
*/
public function db_connect($mode, $force = false)
{
// previous connection failed, don't attempt to connect again
if ($this->conn_failure) {
return;
}
// no replication
if ($this->db_dsnw == $this->db_dsnr) {
$mode = 'w';
}
// Already connected
if ($this->db_connected) {
// connected to db with the same or "higher" mode (if allowed)
if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->options['dsnw_noread']) {
return;
}
}
$dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
$this->dsn_connect($dsn, $mode);
// use write-master when read-only fails
if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) {
$this->dsn_connect($this->db_dsnw_array, 'w');
}
$this->conn_failure = !$this->db_connected;
}
/**
* Analyze the given SQL statement and select the appropriate connection to use
*/
protected function dsn_select($query)
{
// no replication
if ($this->db_dsnw == $this->db_dsnr) {
return 'w';
}
// Read or write ?
$mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
$start = '[' . $this->options['identifier_start'] . self::DEFAULT_QUOTE . ']';
$end = '[' . $this->options['identifier_end'] . self::DEFAULT_QUOTE . ']';
$regex = '/(?:^|\s)(from|update|into|join)\s+'.$start.'?([a-z0-9._]+)'.$end.'?\s+/i';
// find tables involved in this query
if (preg_match_all($regex, $query, $matches, PREG_SET_ORDER)) {
foreach ($matches as $m) {
$table = $m[2];
// always use direct mapping
if ($this->options['table_dsn_map'][$table]) {
$mode = $this->options['table_dsn_map'][$table];
break; // primary table rules
}
else if ($mode == 'r') {
// connected to db with the same or "higher" mode for this table
$db_mode = $this->table_connections[$table];
if ($db_mode == 'w' && !$this->options['dsnw_noread']) {
$mode = $db_mode;
}
}
}
// remember mode chosen (for primary table)
$table = $matches[0][2];
$this->table_connections[$table] = $mode;
}
return $mode;
}
/**
* Activate/deactivate debug mode
*
* @param boolean $dbg True if SQL queries should be logged
*/
public function set_debug($dbg = true)
{
$this->options['debug_mode'] = $dbg;
}
/**
* Writes debug information/query to 'sql' log file
*
* @param string $query SQL query
*/
protected function debug($query)
{
if ($this->options['debug_mode']) {
if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) {
$diff = $len - self::DEBUG_LINE_LENGTH;
$query = substr($query, 0, self::DEBUG_LINE_LENGTH)
. "... [truncated $diff bytes]";
}
rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
}
}
/**
* Getter for error state
*
* @param mixed $result Optional query result
*
* @return string Error message
*/
public function is_error($result = null)
{
if ($result !== null) {
return $result === false ? $this->db_error_msg : null;
}
return $this->db_error ? $this->db_error_msg : null;
}
/**
* Connection state checker
*
* @return boolean True if in connected state
*/
public function is_connected()
{
return !is_object($this->dbh) ? false : $this->db_connected;
}
/**
* Is database replication configured?
*
* @return bool Returns true if dsnw != dsnr
*/
public function is_replicated()
{
return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
}
/**
* Get database runtime variables
*
* @param string $varname Variable name
* @param mixed $default Default value if variable is not set
*
* @return mixed Variable value or default
*/
public function get_variable($varname, $default = null)
{
// to be implemented by driver class
return rcube::get_instance()->config->get('db_' . $varname, $default);
}
/**
* Execute a SQL query
*
* @param string SQL query to execute
* @param mixed Values to be inserted in query
*
* @return number Query handle identifier
*/
public function query()
{
$params = func_get_args();
$query = array_shift($params);
// Support one argument of type array, instead of n arguments
if (count($params) == 1 && is_array($params[0])) {
$params = $params[0];
}
return $this->_query($query, 0, 0, $params);
}
/**
* Execute a SQL query with limits
*
* @param string SQL query to execute
* @param int Offset for LIMIT statement
* @param int Number of rows for LIMIT statement
* @param mixed Values to be inserted in query
*
* @return PDOStatement|bool Query handle or False on error
*/
public function limitquery()
{
$params = func_get_args();
$query = array_shift($params);
$offset = array_shift($params);
$numrows = array_shift($params);
return $this->_query($query, $offset, $numrows, $params);
}
/**
* Execute a SQL query with limits
*
* @param string $query SQL query to execute
* @param int $offset Offset for LIMIT statement
* @param int $numrows Number of rows for LIMIT statement
* @param array $params Values to be inserted in query
*
* @return PDOStatement|bool Query handle or False on error
*/
protected function _query($query, $offset, $numrows, $params)
{
$query = ltrim($query);
$this->db_connect($this->dsn_select($query), true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
if ($numrows || $offset) {
$query = $this->set_limit($query, $numrows, $offset);
}
// replace self::DEFAULT_QUOTE with driver-specific quoting
$query = $this->query_parse($query);
// Because in Roundcube we mostly use queries that are
// executed only once, we will not use prepared queries
$pos = 0;
$idx = 0;
if (count($params)) {
while ($pos = strpos($query, '?', $pos)) {
if ($query[$pos+1] == '?') { // skip escaped '?'
$pos += 2;
}
else {
$val = $this->quote($params[$idx++]);
unset($params[$idx-1]);
$query = substr_replace($query, $val, $pos, 1);
$pos += strlen($val);
}
}
}
$query = rtrim($query, " \t\n\r\0\x0B;");
// replace escaped '?' and quotes back to normal, see self::quote()
$query = str_replace(
array('??', self::DEFAULT_QUOTE.self::DEFAULT_QUOTE),
array('?', self::DEFAULT_QUOTE),
$query
);
// log query
$this->debug($query);
return $this->query_execute($query);
}
/**
* Query execution
*/
protected function query_execute($query)
{
// destroy reference to previous result, required for SQLite driver (#1488874)
$this->last_result = null;
$this->db_error_msg = null;
// send query
$result = $this->dbh->query($query);
if ($result === false) {
$result = $this->handle_error($query);
}
return $this->last_result = $result;
}
/**
* Parse SQL query and replace identifier quoting
*
* @param string $query SQL query
*
* @return string SQL query
*/
protected function query_parse($query)
{
$start = $this->options['identifier_start'];
$end = $this->options['identifier_end'];
$quote = self::DEFAULT_QUOTE;
if ($start == $quote) {
return $query;
}
$pos = 0;
$in = false;
while ($pos = strpos($query, $quote, $pos)) {
if ($query[$pos+1] == $quote) { // skip escaped quote
$pos += 2;
}
else {
if ($in) {
$q = $end;
$in = false;
}
else {
$q = $start;
$in = true;
}
$query = substr_replace($query, $q, $pos, 1);
$pos++;
}
}
return $query;
}
/**
* Helper method to handle DB errors.
* This by default logs the error but could be overridden by a driver implementation
*
* @param string $query Query that triggered the error
*
* @return mixed Result to be stored and returned
*/
protected function handle_error($query)
{
$error = $this->dbh->errorInfo();
if (empty($this->options['ignore_key_errors']) || !in_array($error[0], array('23000', '23505'))) {
$this->db_error = true;
$this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
if (empty($this->options['ignore_errors'])) {
rcube::raise_error(array(
'code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
'message' => $this->db_error_msg . " (SQL Query: $query)"
), true, false);
}
}
return false;
}
/**
* Get number of affected rows for the last query
*
* @param mixed $result Optional query handle
*
* @return int Number of (matching) rows
*/
public function affected_rows($result = null)
{
if ($result || ($result === null && ($result = $this->last_result))) {
if ($result !== true) {
return $result->rowCount();
}
}
return 0;
}
/**
* Get number of rows for a SQL query
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Number of rows or false on failure
* @deprecated This method shows very poor performance and should be avoided.
*/
public function num_rows($result = null)
{
if (($result || ($result === null && ($result = $this->last_result))) && $result !== true) {
// repeat query with SELECT COUNT(*) ...
if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) {
$query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM);
return $query ? intval($query->fetchColumn(0)) : false;
}
else {
$num = count($result->fetchAll());
$result->execute(); // re-execute query because there's no seek(0)
return $num;
}
}
return false;
}
/**
* Get last inserted record ID
*
* @param string $table Table name (to find the incremented sequence)
*
* @return mixed ID or false on failure
*/
public function insert_id($table = '')
{
if (!$this->db_connected || $this->db_mode == 'r') {
return false;
}
if ($table) {
// resolve table name
$table = $this->table_name($table);
}
$id = $this->dbh->lastInsertId($table);
return $id;
}
/**
* Get an associative array for one row
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Array with col values or false on failure
*/
public function fetch_assoc($result = null)
{
return $this->_fetch_row($result, PDO::FETCH_ASSOC);
}
/**
* Get an index array for one row
* If no query handle is specified, the last query will be taken as reference
*
* @param mixed $result Optional query handle
*
* @return mixed Array with col values or false on failure
*/
public function fetch_array($result = null)
{
return $this->_fetch_row($result, PDO::FETCH_NUM);
}
/**
* Get col values for a result row
*
* @param mixed $result Optional query handle
* @param int $mode Fetch mode identifier
*
* @return mixed Array with col values or false on failure
*/
protected function _fetch_row($result, $mode)
{
if ($result || ($result === null && ($result = $this->last_result))) {
if ($result !== true) {
return $result->fetch($mode);
}
}
return false;
}
/**
* Adds LIMIT,OFFSET clauses to the query
*
* @param string $query SQL query
* @param int $limit Number of rows
* @param int $offset Offset
*
* @return string SQL query
*/
protected function set_limit($query, $limit = 0, $offset = 0)
{
if ($limit) {
$query .= ' LIMIT ' . intval($limit);
}
if ($offset) {
$query .= ' OFFSET ' . intval($offset);
}
return $query;
}
/**
* Returns list of tables in a database
*
* @return array List of all tables of the current database
*/
public function list_tables()
{
// get tables if not cached
if ($this->tables === null) {
$q = $this->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"
. " WHERE TABLE_TYPE = 'BASE TABLE'"
. " ORDER BY TABLE_NAME");
$this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
}
return $this->tables;
}
/**
* Returns list of columns in database table
*
* @param string $table Table name
*
* @return array List of table cols
*/
public function list_cols($table)
{
$q = $this->query('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?',
array($table));
if ($q) {
return $q->fetchAll(PDO::FETCH_COLUMN, 0);
}
return array();
}
/**
* Start transaction
*
* @return bool True on success, False on failure
*/
public function startTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('BEGIN TRANSACTION');
return $this->last_result = $this->dbh->beginTransaction();
}
/**
* Commit transaction
*
* @return bool True on success, False on failure
*/
public function endTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('COMMIT TRANSACTION');
return $this->last_result = $this->dbh->commit();
}
/**
* Rollback transaction
*
* @return bool True on success, False on failure
*/
public function rollbackTransaction()
{
$this->db_connect('w', true);
// check connection before proceeding
if (!$this->is_connected()) {
return $this->last_result = false;
}
$this->debug('ROLLBACK TRANSACTION');
return $this->last_result = $this->dbh->rollBack();
}
/**
* Release resources related to the last query result.
* When we know we don't need to access the last query result we can destroy it
* and release memory. Useful especially if the query returned big chunk of data.
*/
public function reset()
{
$this->last_result = null;
}
/**
* Terminate database connection.
*/
public function closeConnection()
{
$this->db_connected = false;
$this->db_index = 0;
// release statement and connection resources
$this->last_result = null;
$this->dbh = null;
$this->dbhs = array();
}
/**
* Formats input so it can be safely used in a query
*
* @param mixed $input Value to quote
* @param string $type Type of data (integer, bool, ident)
*
* @return string Quoted/converted string for use in query
*/
public function quote($input, $type = null)
{
// handle int directly for better performance
if ($type == 'integer' || $type == 'int') {
return intval($input);
}
if (is_null($input)) {
return 'NULL';
}
if ($input instanceof DateTime) {
return $this->quote($input->format($this->options['datetime_format']));
}
if ($type == 'ident') {
return $this->quote_identifier($input);
}
// create DB handle if not available
if (!$this->dbh) {
$this->db_connect('r');
}
if ($this->dbh) {
$map = array(
'bool' => PDO::PARAM_BOOL,
'integer' => PDO::PARAM_INT,
);
$type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
return strtr($this->dbh->quote($input, $type),
// escape ? and `
array('?' => '??', self::DEFAULT_QUOTE => self::DEFAULT_QUOTE.self::DEFAULT_QUOTE)
);
}
return 'NULL';
}
/**
* Escapes a string so it can be safely used in a query
*
* @param string $str A string to escape
*
* @return string Escaped string for use in a query
*/
public function escape($str)
{
if (is_null($str)) {
return 'NULL';
}
return substr($this->quote($str), 1, -1);
}
/**
* Quotes a string so it can be safely used as a table or column name
*
* @param string $str Value to quote
*
* @return string Quoted string for use in query
* @deprecated Replaced by rcube_db::quote_identifier
* @see rcube_db::quote_identifier
*/
public function quoteIdentifier($str)
{
return $this->quote_identifier($str);
}
/**
* Escapes a string so it can be safely used in a query
*
* @param string $str A string to escape
*
* @return string Escaped string for use in a query
* @deprecated Replaced by rcube_db::escape
* @see rcube_db::escape
*/
public function escapeSimple($str)
{
return $this->escape($str);
}
/**
* Quotes a string so it can be safely used as a table or column name
*
* @param string $str Value to quote
*
* @return string Quoted string for use in query
*/
public function quote_identifier($str)
{
$start = $this->options['identifier_start'];
$end = $this->options['identifier_end'];
$name = array();
foreach (explode('.', $str) as $elem) {
$elem = str_replace(array($start, $end), '', $elem);
$name[] = $start . $elem . $end;
}
- return implode($name, '.');
+ return implode('.', $name);
}
/**
* Return SQL function for current time and date
*
* @param int $interval Optional interval (in seconds) to add/subtract
*
* @return string SQL function to use in query
*/
public function now($interval = 0)
{
if ($interval) {
$add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL ';
$add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
$add .= ' SECOND';
}
return "now()" . $add;
}
/**
* Return list of elements for use with SQL's IN clause
*
* @param array $arr Input array
* @param string $type Type of data (integer, bool, ident)
*
* @return string Comma-separated list of quoted values for use in query
*/
public function array2list($arr, $type = null)
{
if (!is_array($arr)) {
return $this->quote($arr, $type);
}
foreach ($arr as $idx => $item) {
$arr[$idx] = $this->quote($item, $type);
}
return implode(',', $arr);
}
/**
* Return SQL statement to convert a field value into a unix timestamp
*
* This method is deprecated and should not be used anymore due to limitations
* of timestamp functions in Mysql (year 2038 problem)
*
* @param string $field Field name
*
* @return string SQL statement to use in query
* @deprecated
*/
public function unixtimestamp($field)
{
return "UNIX_TIMESTAMP($field)";
}
/**
* Return SQL statement to convert from a unix timestamp
*
* @param int $timestamp Unix timestamp
*
* @return string Date string in db-specific format
* @deprecated
*/
public function fromunixtime($timestamp)
{
return $this->quote(date($this->options['datetime_format'], $timestamp));
}
/**
* Return SQL statement for case insensitive LIKE
*
* @param string $column Field name
* @param string $value Search value
*
* @return string SQL statement to use in query
*/
public function ilike($column, $value)
{
return $this->quote_identifier($column).' LIKE '.$this->quote($value);
}
/**
* Abstract SQL statement for value concatenation
*
* @return string SQL statement to be used in query
*/
public function concat(/* col1, col2, ... */)
{
$args = func_get_args();
if (is_array($args[0])) {
$args = $args[0];
}
return '(' . join(' || ', $args) . ')';
}
/**
* Encodes non-UTF-8 characters in string/array/object (recursive)
*
* @param mixed $input Data to fix
* @param bool $serialized Enable serialization
*
* @return mixed Properly UTF-8 encoded data
*/
public static function encode($input, $serialized = false)
{
// use Base64 encoding to workaround issues with invalid
// or null characters in serialized string (#1489142)
if ($serialized) {
return base64_encode(serialize($input));
}
if (is_object($input)) {
foreach (get_object_vars($input) as $idx => $value) {
$input->$idx = self::encode($value);
}
return $input;
}
else if (is_array($input)) {
foreach ($input as $idx => $value) {
$input[$idx] = self::encode($value);
}
return $input;
}
return utf8_encode($input);
}
/**
* Decodes encoded UTF-8 string/object/array (recursive)
*
* @param mixed $input Input data
* @param bool $serialized Enable serialization
*
* @return mixed Decoded data
*/
public static function decode($input, $serialized = false)
{
// use Base64 encoding to workaround issues with invalid
// or null characters in serialized string (#1489142)
if ($serialized) {
// Keep backward compatybility where base64 wasn't used
if (strpos(substr($input, 0, 16), ':') !== false) {
return self::decode(@unserialize($input));
}
return @unserialize(base64_decode($input));
}
if (is_object($input)) {
foreach (get_object_vars($input) as $idx => $value) {
$input->$idx = self::decode($value);
}
return $input;
}
else if (is_array($input)) {
foreach ($input as $idx => $value) {
$input[$idx] = self::decode($value);
}
return $input;
}
return utf8_decode($input);
}
/**
* Return correct name for a specific database table
*
* @param string $table Table name
* @param bool $quoted Quote table identifier
*
* @return string Translated table name
*/
public function table_name($table, $quoted = false)
{
// let plugins alter the table name (#1489837)
$plugin = rcube::get_instance()->plugins->exec_hook('db_table_name', array('table' => $table));
$table = $plugin['table'];
// add prefix to the table name if configured
if (($prefix = $this->options['table_prefix']) && strpos($table, $prefix) !== 0) {
$table = $prefix . $table;
}
if ($quoted) {
$table = $this->quote_identifier($table);
}
return $table;
}
/**
* Set class option value
*
* @param string $name Option name
* @param mixed $value Option value
*/
public function set_option($name, $value)
{
$this->options[$name] = $value;
}
/**
* Set DSN connection to be used for the given table
*
* @param string $table Table name
* @param string $mode DSN connection ('r' or 'w') to be used
*/
public function set_table_dsn($table, $mode)
{
$this->options['table_dsn_map'][$this->table_name($table)] = $mode;
}
/**
* MDB2 DSN string parser
*
* @param string $sequence Secuence name
*
* @return array DSN parameters
*/
public static function parse_dsn($dsn)
{
if (empty($dsn)) {
return null;
}
// Find phptype and dbsyntax
if (($pos = strpos($dsn, '://')) !== false) {
$str = substr($dsn, 0, $pos);
$dsn = substr($dsn, $pos + 3);
}
else {
$str = $dsn;
$dsn = null;
}
// Get phptype and dbsyntax
// $str => phptype(dbsyntax)
if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
$parsed['phptype'] = $arr[1];
$parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
}
else {
$parsed['phptype'] = $str;
$parsed['dbsyntax'] = $str;
}
if (empty($dsn)) {
return $parsed;
}
// Get (if found): username and password
// $dsn => username:password@protocol+hostspec/database
if (($at = strrpos($dsn,'@')) !== false) {
$str = substr($dsn, 0, $at);
$dsn = substr($dsn, $at + 1);
if (($pos = strpos($str, ':')) !== false) {
$parsed['username'] = rawurldecode(substr($str, 0, $pos));
$parsed['password'] = rawurldecode(substr($str, $pos + 1));
}
else {
$parsed['username'] = rawurldecode($str);
}
}
// Find protocol and hostspec
// $dsn => proto(proto_opts)/database
if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
$proto = $match[1];
$proto_opts = $match[2] ? $match[2] : false;
$dsn = $match[3];
}
// $dsn => protocol+hostspec/database (old format)
else {
if (strpos($dsn, '+') !== false) {
list($proto, $dsn) = explode('+', $dsn, 2);
}
if ( strpos($dsn, '//') === 0
&& strpos($dsn, '/', 2) !== false
&& $parsed['phptype'] == 'oci8'
) {
//oracle's "Easy Connect" syntax:
//"username/password@[//]host[:port][/service_name]"
//e.g. "scott/tiger@//mymachine:1521/oracle"
$proto_opts = $dsn;
$pos = strrpos($proto_opts, '/');
$dsn = substr($proto_opts, $pos + 1);
$proto_opts = substr($proto_opts, 0, $pos);
}
else if (strpos($dsn, '/') !== false) {
list($proto_opts, $dsn) = explode('/', $dsn, 2);
}
else {
$proto_opts = $dsn;
$dsn = null;
}
}
// process the different protocol options
$parsed['protocol'] = $proto ?: 'tcp';
$proto_opts = rawurldecode($proto_opts);
if (strpos($proto_opts, ':') !== false) {
list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
}
if ($parsed['protocol'] == 'tcp') {
$parsed['hostspec'] = $proto_opts;
}
else if ($parsed['protocol'] == 'unix') {
$parsed['socket'] = $proto_opts;
}
// Get dabase if any
// $dsn => database
if ($dsn) {
// /database
if (($pos = strpos($dsn, '?')) === false) {
$parsed['database'] = rawurldecode($dsn);
// /database?param1=value1&param2=value2
}
else {
$parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
$dsn = substr($dsn, $pos + 1);
if (strpos($dsn, '&') !== false) {
$opts = explode('&', $dsn);
}
else { // database?param1=value1
$opts = array($dsn);
}
foreach ($opts as $opt) {
list($key, $value) = explode('=', $opt);
if (!array_key_exists($key, $parsed) || false === $parsed[$key]) {
// don't allow params overwrite
$parsed[$key] = rawurldecode($value);
}
}
}
}
return $parsed;
}
/**
* Returns PDO DSN string from DSN array
*
* @param array $dsn DSN parameters
*
* @return string DSN string
*/
protected function dsn_string($dsn)
{
$params = array();
$result = $dsn['phptype'] . ':';
if ($dsn['hostspec']) {
$params[] = 'host=' . $dsn['hostspec'];
}
if ($dsn['port']) {
$params[] = 'port=' . $dsn['port'];
}
if ($dsn['database']) {
$params[] = 'dbname=' . $dsn['database'];
}
if (!empty($params)) {
$result .= implode(';', $params);
}
return $result;
}
/**
* Returns driver-specific connection options
*
* @param array $dsn DSN parameters
*
* @return array Connection options
*/
protected function dsn_options($dsn)
{
$result = array();
if ($this->db_pconn) {
$result[PDO::ATTR_PERSISTENT] = true;
}
if (!empty($dsn['prefetch'])) {
$result[PDO::ATTR_PREFETCH] = (int) $dsn['prefetch'];
}
if (!empty($dsn['timeout'])) {
$result[PDO::ATTR_TIMEOUT] = (int) $dsn['timeout'];
}
return $result;
}
/**
* Execute the given SQL script
*
* @param string $sql SQL queries to execute
*
* @return boolen True on success, False on error
*/
public function exec_script($sql)
{
$sql = $this->fix_table_names($sql);
$buff = '';
$exec = '';
foreach (explode("\n", $sql) as $line) {
$trimmed = trim($line);
if ($trimmed == '' || preg_match('/^--/', $trimmed)) {
continue;
}
if ($trimmed == 'GO') {
$exec = $buff;
}
else if ($trimmed[strlen($trimmed)-1] == ';') {
$exec = $buff . substr(rtrim($line), 0, -1);
}
if ($exec) {
$this->query($exec);
$buff = '';
$exec = '';
if ($this->db_error) {
break;
}
}
else {
$buff .= $line . "\n";
}
}
return !$this->db_error;
}
/**
* Parse SQL file and fix table names according to table prefix
*/
protected function fix_table_names($sql)
{
if (!$this->options['table_prefix']) {
return $sql;
}
$sql = preg_replace_callback(
'/((TABLE|TRUNCATE|(?<!ON )UPDATE|INSERT INTO|FROM'
. '| ON(?! (DELETE|UPDATE))|REFERENCES|CONSTRAINT|FOREIGN KEY|INDEX)'
. '\s+(IF (NOT )?EXISTS )?[`"]*)([^`"\( \r\n]+)/',
array($this, 'fix_table_names_callback'),
$sql
);
return $sql;
}
/**
* Preg_replace callback for fix_table_names()
*/
protected function fix_table_names_callback($matches)
{
return $matches[1] . $this->options['table_prefix'] . $matches[count($matches)-1];
}
}
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index a8147d6bf..1564a3eeb 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -1,348 +1,348 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Search action (and form) for address book contacts |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
if ($RCMAIL->action == 'search-create') {
$id = rcube_utils::get_input_value('_search', rcube_utils::INPUT_POST);
$name = rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST, true);
if (($params = $_SESSION['search_params']) && $params['id'] == $id) {
$data = array(
'type' => rcube_user::SEARCH_ADDRESSBOOK,
'name' => $name,
'data' => array(
'fields' => $params['data'][0],
'search' => $params['data'][1],
),
);
$plugin = $RCMAIL->plugins->exec_hook('saved_search_create', array('data' => $data));
if (!$plugin['abort'])
$result = $RCMAIL->user->insert_search($plugin['data']);
else
$result = $plugin['result'];
}
if ($result) {
$OUTPUT->show_message('savedsearchcreated', 'confirmation');
$OUTPUT->command('insert_saved_search', rcube::Q($name), rcube::Q($result));
}
else
$OUTPUT->show_message($plugin['message'] ?: 'savedsearchcreateerror', 'error');
$OUTPUT->send();
}
if ($RCMAIL->action == 'search-delete') {
$id = rcube_utils::get_input_value('_sid', rcube_utils::INPUT_POST);
$plugin = $RCMAIL->plugins->exec_hook('saved_search_delete', array('id' => $id));
if (!$plugin['abort'])
$result = $RCMAIL->user->delete_search($id);
else
$result = $plugin['result'];
if ($result) {
$OUTPUT->show_message('savedsearchdeleted', 'confirmation');
$OUTPUT->command('remove_search_item', rcube::Q($id));
// contact list will be cleared, clear also page counter
$OUTPUT->command('set_rowcount', $RCMAIL->gettext('nocontactsfound'));
$OUTPUT->set_env('pagecount', 0);
}
else
$OUTPUT->show_message($plugin['message'] ?: 'savedsearchdeleteerror', 'error');
$OUTPUT->send();
}
if (!isset($_GET['_form'])) {
rcmail_contact_search();
}
$OUTPUT->add_handler('searchform', 'rcmail_contact_search_form');
$OUTPUT->send('contactsearch');
function rcmail_contact_search()
{
global $RCMAIL, $OUTPUT, $SEARCH_MODS_DEFAULT, $PAGE_SIZE;
$adv = isset($_POST['_adv']);
$sid = rcube_utils::get_input_value('_sid', rcube_utils::INPUT_GET);
// get search criteria from saved search
if ($sid && ($search = $RCMAIL->user->get_search($sid))) {
$fields = $search['data']['fields'];
$search = $search['data']['search'];
}
// get fields/values from advanced search form
else if ($adv) {
foreach (array_keys($_POST) as $key) {
$s = trim(rcube_utils::get_input_value($key, rcube_utils::INPUT_POST, true));
if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
$search[] = $s;
$fields[] = $m[1];
}
}
if (empty($fields)) {
// do nothing, show the form again
return;
}
}
// quick-search
else {
$search = trim(rcube_utils::get_input_value('_q', rcube_utils::INPUT_GET, true));
$fields = explode(',', rcube_utils::get_input_value('_headers', rcube_utils::INPUT_GET));
if (empty($fields)) {
$fields = array_keys($SEARCH_MODS_DEFAULT);
}
else {
$fields = array_filter($fields);
}
// update search_mods setting
$old_mods = $RCMAIL->config->get('addressbook_search_mods');
$search_mods = array_fill_keys($fields, 1);
if ($old_mods != $search_mods) {
$RCMAIL->user->save_prefs(array('addressbook_search_mods' => $search_mods));
}
if (in_array('*', $fields)) {
$fields = '*';
}
}
// Values matching mode
$mode = (int) $RCMAIL->config->get('addressbook_search_mode');
$mode |= rcube_addressbook::SEARCH_GROUPS;
// get sources list
$sources = $RCMAIL->get_address_sources();
$search_set = array();
$records = array();
$sort_col = $RCMAIL->config->get('addressbook_sort_col', 'name');
$afields = $RCMAIL->config->get('contactlist_fields');
foreach ($sources as $s) {
$source = $RCMAIL->get_address_book($s['id']);
// check if search fields are supported....
if (is_array($fields)) {
$cols = $source->coltypes[0] ? array_flip($source->coltypes) : $source->coltypes;
$supported = 0;
foreach ($fields as $f) {
if (array_key_exists($f, $cols)) {
$supported ++;
}
}
// in advanced search we require all fields (AND operator)
// in quick search we require at least one field (OR operator)
if (($adv && $supported < count($fields)) || (!$adv && !$supported)) {
continue;
}
}
// reset page
$source->set_page(1);
$source->set_pagesize(9999);
// get contacts count
$result = $source->search($fields, $search, $mode, false);
if (!$result->count) {
continue;
}
// get records
$result = $source->list_records($afields);
while ($row = $result->next()) {
$row['sourceid'] = $s['id'];
$key = rcube_addressbook::compose_contact_key($row, $sort_col);
$records[$key] = $row;
}
unset($result);
$search_set[$s['id']] = $source->get_search_set();
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
// cut first-page records
if ($PAGE_SIZE < $count) {
$records = array_slice($records, 0, $PAGE_SIZE);
}
$result->records = array_values($records);
// search request ID
$search_request = md5('addr'
- .(is_array($fields) ? implode($fields, ',') : $fields)
- .(is_array($search) ? implode($search, ',') : $search));
+ .(is_array($fields) ? implode(',', $fields) : $fields)
+ .(is_array($search) ? implode(',', $search) : $search));
// save search settings in session
$_SESSION['search'][$search_request] = $search_set;
$_SESSION['search_params'] = array('id' => $search_request, 'data' => array($fields, $search));
$_SESSION['page'] = 1;
if ($adv)
$OUTPUT->command('list_contacts_clear');
if ($result->count > 0) {
// create javascript list
rcmail_js_contacts_list($result);
$OUTPUT->show_message('contactsearchsuccessful', 'confirmation', array('nr' => $result->count));
}
else {
$OUTPUT->show_message('nocontactsfound', 'notice');
}
// update message count display
$OUTPUT->set_env('search_request', $search_request);
$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// Re-set current source
$OUTPUT->set_env('search_id', $sid);
$OUTPUT->set_env('source', '');
$OUTPUT->set_env('group', '');
// Re-set list header
$OUTPUT->command('set_group_prop', null);
if (!$sid) {
// unselect currently selected directory/group
$OUTPUT->command('unselect_directory');
// enable "Save search" command
$OUTPUT->command('enable_command', 'search-create', true);
}
$OUTPUT->command('update_group_commands');
// send response
$OUTPUT->send();
}
function rcmail_contact_search_form($attrib)
{
global $RCMAIL, $CONTACT_COLTYPES;
$i_size = $attrib['size'] ?: 30;
$short_labels = rcube_utils::get_boolean($attrib['short-legend-labels']);
$form = array(
'main' => array(
'name' => $RCMAIL->gettext('properties'),
'content' => array(
),
),
'personal' => array(
'name' => $RCMAIL->gettext($short_labels ? 'personal' : 'personalinfo'),
'content' => array(
),
),
'other' => array(
'name' => $RCMAIL->gettext('other'),
'content' => array(
),
),
);
// get supported coltypes from all address sources
$sources = $RCMAIL->get_address_sources();
$coltypes = array();
foreach ($sources as $s) {
$CONTACTS = $RCMAIL->get_address_book($s['id']);
if (is_array($CONTACTS->coltypes)) {
$contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
$coltypes = array_merge($coltypes, $contact_cols);
}
}
// merge supported coltypes with $CONTACT_COLTYPES
foreach ($coltypes as $col => $colprop) {
$coltypes[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], (array)$colprop) : (array)$colprop;
}
// build form fields list
foreach ($coltypes as $col => $colprop) {
if ($colprop['type'] != 'image' && !$colprop['nosearch']) {
$ftype = $colprop['type'] == 'select' ? 'select' : 'text';
$label = isset($colprop['label']) ? $colprop['label'] : $RCMAIL->gettext($col);
$category = $colprop['category'] ?: 'other';
// load jquery UI datepicker for date fields
if ($colprop['type'] == 'date') {
$colprop['class'] .= ($colprop['class'] ? ' ' : '') . 'datepicker';
}
else if ($ftype == 'text') {
$colprop['size'] = $i_size;
}
$colprop['id'] = '_search_' . $col;
$content = html::div('row',
html::label(array('class' => 'contactfieldlabel label', 'for' => $colprop['id']), rcube::Q($label))
. html::div('contactfieldcontent', rcube_output::get_edit_field('search_'.$col, '', $colprop, $ftype)));
$form[$category]['content'][] = $content;
}
}
$hiddenfields = new html_hiddenfield();
$hiddenfields->add(array('name' => '_adv', 'value' => 1));
$out = $RCMAIL->output->request_form(array(
'name' => 'form',
'method' => 'post',
'task' => $RCMAIL->task,
'action' => 'search',
'noclose' => true,
) + $attrib, $hiddenfields->show());
$RCMAIL->output->add_gui_object('editform', $attrib['id']);
unset($attrib['name']);
unset($attrib['id']);
foreach ($form as $f) {
if (!empty($f['content'])) {
$content = html::div('contactfieldgroup', join("\n", $f['content']));
$out .= html::tag('fieldset', $attrib,
html::tag('legend', null, rcube::Q($f['name']))
. $content) . "\n";
}
}
return $out . '</form>';
}
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 94b450253..cfcaab6ea 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -1,359 +1,359 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Compose a new mail message and send it or store as draft |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
// remove all scripts and act as called in frame
$OUTPUT->reset();
$OUTPUT->framed = true;
$COMPOSE_ID = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
// Sanity checks
if (!isset($COMPOSE['id'])) {
rcube::raise_error(array('code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid compose ID"), true, false);
$OUTPUT->show_message('internalerror', 'error');
$OUTPUT->send('iframe');
}
$saveonly = !empty($_GET['_saveonly']);
$savedraft = !empty($_POST['_draft']) && !$saveonly;
$SENDMAIL = new rcmail_sendmail($COMPOSE, array(
'sendmail' => true,
'saveonly' => $saveonly,
'savedraft' => $savedraft,
'error_handler' => function() use ($OUTPUT) {
call_user_func_array(array($OUTPUT, 'show_message'), func_get_args());
$OUTPUT->send('iframe');
}
));
// Collect input for message headers
$headers = $SENDMAIL->headers_input();
$COMPOSE['param']['message-id'] = $headers['Message-ID'];
$message_id = $headers['Message-ID'];
$message_charset = $SENDMAIL->options['charset'];
$message_body = rcube_utils::get_input_value('_message', rcube_utils::INPUT_POST, true, $message_charset);
$isHtml = (bool) rcube_utils::get_input_value('_is_html', rcube_utils::INPUT_POST);
// Reset message body and attachments in Mailvelope mode
if (isset($_POST['_pgpmime'])) {
$pgp_mime = rcube_utils::get_input_value('_pgpmime', rcube_utils::INPUT_POST);
$isHtml = false;
$message_body = '';
// clear unencrypted attachments
foreach ((array) $COMPOSE['attachments'] as $attach) {
$RCMAIL->plugins->exec_hook('attachment_delete', $attach);
}
$COMPOSE['attachments'] = array();
}
if ($isHtml) {
$bstyle = array();
if ($font_size = $RCMAIL->config->get('default_font_size')) {
$bstyle[] = 'font-size: ' . $font_size;
}
if ($font_family = $RCMAIL->config->get('default_font')) {
$bstyle[] = 'font-family: ' . rcmail::font_defs($font_family);
}
// append doctype and html/body wrappers
- $bstyle = !empty($bstyle) ? (" style='" . implode($bstyle, '; ') . "'") : '';
+ $bstyle = !empty($bstyle) ? (" style='" . implode('; ', $bstyle) . "'") : '';
$message_body = '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset='
. ($message_charset ?: RCUBE_CHARSET) . '" /></head>'
. "<body" . $bstyle . ">\r\n" . $message_body;
}
if (!$savedraft) {
if ($isHtml) {
$b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
$pre_style = 'margin: 0; padding: 0; font-family: monospace';
$message_body = preg_replace(
array(
// remove empty signature div
'/<div id="_rc_sig">(&nbsp;)?<\/div>[\s\r\n]*$/',
// replace signature's div ID (#6073)
'/ id="_rc_sig"/',
// add inline css for blockquotes and container
'/<blockquote>/',
'/<div class="pre">/',
// convert TinyMCE's new-line sequences (#1490463)
'/<p>&nbsp;<\/p>/',
),
array(
'',
' id="signature"',
'<blockquote type="cite" style="'.$b_style.'">',
'<div class="pre" style="'.$pre_style.'">',
'<p><br /></p>',
),
$message_body);
rcube_utils::preg_error(array(
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not format HTML!"
), true);
}
// Check spelling before send
if ($RCMAIL->config->get('spellcheck_before_send')
&& $RCMAIL->config->get('enable_spellcheck')
&& empty($COMPOSE['spell_checked'])
&& !empty($message_body)
) {
$message_body = str_replace("\r\n", "\n", $message_body);
$spellchecker = new rcube_spellchecker(rcube_utils::get_input_value('_lang', rcube_utils::INPUT_GPC));
$spell_result = $spellchecker->check($message_body, $isHtml);
$COMPOSE['spell_checked'] = true;
if (!$spell_result) {
if ($isHtml) {
$result['words'] = $spellchecker->get();
$result['dictionary'] = (bool) $RCMAIL->config->get('spellcheck_dictionary');
}
else {
$result = $spellchecker->get_xml();
}
$OUTPUT->show_message('mispellingsfound', 'error');
$OUTPUT->command('spellcheck_resume', $result);
$OUTPUT->send('iframe');
}
}
// generic footer for all messages
if ($footer = $SENDMAIL->generic_message_footer($isHtml)) {
$message_body .= "\r\n" . $footer;
}
}
if ($isHtml) {
$message_body .= "\r\n</body></html>\r\n";
}
// sort attachments to make sure the order is the same as in the UI (#1488423)
if ($files = rcube_utils::get_input_value('_attachments', rcube_utils::INPUT_POST)) {
$files = explode(',', $files);
$files = array_flip($files);
foreach ($files as $idx => $val) {
$files[$idx] = $COMPOSE['attachments'][$idx];
unset($COMPOSE['attachments'][$idx]);
}
$COMPOSE['attachments'] = array_merge(array_filter($files), $COMPOSE['attachments']);
}
// Since we can handle big messages with disk usage, we need more time to work
@set_time_limit(360);
// create PEAR::Mail_mime instance, set headers, body and params
$MAIL_MIME = $SENDMAIL->create_message($headers, $message_body, $isHtml, $COMPOSE['attachments']);
// add stored attachments, if any
if (is_array($COMPOSE['attachments'])) {
foreach ($COMPOSE['attachments'] as $id => $attachment) {
// This hook retrieves the attachment contents from the file storage backend
$attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment);
$is_inline = false;
if ($isHtml) {
$dispurl = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
$message_body = $MAIL_MIME->getHTMLBody();
$is_inline = preg_match($dispurl, $message_body);
}
// inline image
if ($is_inline) {
// Mail_Mime does not support many inline attachments with the same name (#1489406)
// we'll generate cid: urls here to workaround this
$cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $SENDMAIL->options['from'], $matches)) {
$cid .= $matches[1];
}
else {
$cid .= '@localhost';
}
$message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
rcube_utils::preg_error(array(
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not replace an image reference!"
), true);
$MAIL_MIME->setHTMLBody($message_body);
if ($attachment['data'])
$MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false, $cid);
else
$MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true, $cid);
}
else {
$ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
$file = $attachment['data'] ?: $attachment['path'];
$folding = (int) $RCMAIL->config->get('mime_param_folding');
$MAIL_MIME->addAttachment($file,
$ctype,
$attachment['name'],
$attachment['data'] ? false : true,
$ctype == 'message/rfc822' ? '8bit' : 'base64',
'attachment',
$attachment['charset'],
'', '',
$folding ? 'quoted-printable' : NULL,
$folding == 2 ? 'quoted-printable' : NULL,
'', RCUBE_CHARSET
);
}
}
}
// compose PGP/Mime message
if ($pgp_mime) {
$MAIL_MIME->addAttachment(new Mail_mimePart('Version: 1', array(
'content_type' => 'application/pgp-encrypted',
'description' => 'PGP/MIME version identification',
)));
$MAIL_MIME->addAttachment(new Mail_mimePart($pgp_mime, array(
'content_type' => 'application/octet-stream',
'filename' => 'encrypted.asc',
'disposition' => 'inline',
)));
$MAIL_MIME->setContentType('multipart/encrypted', array('protocol' => 'application/pgp-encrypted'));
$MAIL_MIME->setParam('preamble', 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)');
}
// This hook allows to modify the message before send or save action
$plugin = $RCMAIL->plugins->exec_hook('message_ready', array('message' => $MAIL_MIME));
$MAIL_MIME = $plugin['message'];
// Deliver the message over SMTP
if (!$savedraft && !$saveonly) {
$sent = $SENDMAIL->deliver_message($MAIL_MIME);
}
// Save the message in Drafts/Sent
$saved = $SENDMAIL->save_message($MAIL_MIME);
// raise error if saving failed
if (!$saved && $savedraft) {
$RCMAIL->display_server_error('errorsaving');
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
$OUTPUT->send('iframe');
}
$store_target = $SENDMAIL->options['store_target'];
$store_folder = $SENDMAIL->options['store_folder'];
// delete previous saved draft
$drafts_mbox = $RCMAIL->config->get('drafts_mbox');
$old_id = rcube_utils::get_input_value('_draft_saveid', rcube_utils::INPUT_POST);
if ($old_id && ($sent || $saved)) {
$deleted = $RCMAIL->storage->delete_message($old_id, $drafts_mbox);
// raise error if deletion of old draft failed
if (!$deleted) {
rcube::raise_error(array('code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not delete message from $drafts_mbox"), true, false);
}
}
if ($savedraft) {
// remember new draft-uid ($saved could be an UID or true/false here)
if ($saved && is_bool($saved)) {
$index = $RCMAIL->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
$saved = @max($index->get());
}
if ($saved) {
$plugin = $RCMAIL->plugins->exec_hook('message_draftsaved',
array('msgid' => $message_id, 'uid' => $saved, 'folder' => $store_target));
// display success
$OUTPUT->show_message($plugin['message'] ?: 'messagesaved', 'confirmation');
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
$COMPOSE['param']['draft_uid'] = $plugin['uid'];
$OUTPUT->command('set_draft_id', $plugin['uid']);
$OUTPUT->command('compose_field_hash', true);
}
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
}
else {
// Collect folders which could contain the composed message,
// we'll refresh the list if currently opened folder is one of them (#1490238)
$folders = array();
if (!$saveonly) {
if (in_array($COMPOSE['mode'], array('reply', 'forward', 'draft'))) {
$folders[] = $COMPOSE['mailbox'];
}
if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
$folders[] = $drafts_mbox;
}
}
if ($store_folder && !$saved) {
$params = $saveonly ? null : array('prefix' => true);
$RCMAIL->display_server_error('errorsavingsent', null, null, $params);
if ($saveonly) {
$OUTPUT->send('iframe');
}
$save_error = true;
}
else {
$RCMAIL->plugins->exec_hook('attachments_cleanup', array('group' => $COMPOSE_ID));
$RCMAIL->session->remove('compose_data_' . $COMPOSE_ID);
$_SESSION['last_compose_session'] = $COMPOSE_ID;
$OUTPUT->command('remove_compose_data', $COMPOSE_ID);
if ($store_folder) {
$folders[] = $store_target;
}
}
$msg = $RCMAIL->gettext($saveonly ? 'successfullysaved' : 'messagesent');
$OUTPUT->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
}
$OUTPUT->send('iframe');

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 25, 6:49 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
257747
Default Alt Text
(158 KB)

Event Timeline