Import Ruty

This commit is contained in:
2024-03-11 00:58:34 +01:00
parent 34a31bb184
commit 985f1ab418
618 changed files with 225414 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
#!/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: |
| Finally remove all db records marked as deleted some time ago |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require INSTALL_PATH.'program/include/clisetup.php';
if (!empty($_SERVER['argv'][1])) {
$days = intval($_SERVER['argv'][1]);
}
else {
$days = 7;
}
rcmail_utils::db_clean($days);
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
set -e
PWD=`dirname "$0"`
do_shrink() {
rm -f "$2"
csso $1 -o $2 --no-restructure
}
if which csso > /dev/null 2>&1; then
:
else
echo "csso not found. Please install e.g. 'npm install -g csso-cli'."
exit 1
fi
# compress single file from argument
if [ $# -gt 0 ]; then
CSS_FILE="$1"
echo "Shrinking $CSS_FILE"
minfile=`echo $CSS_FILE | sed -e 's/\.css$/\.min\.css/'`
do_shrink "$CSS_FILE" "$minfile"
exit
fi
DIRS="$PWD/../skins/* $PWD/../plugins/* $PWD/../plugins/*/skins/* $PWD/../plugins/*/themes/*"
# default: compress application scripts
for dir in $DIRS; do
for file in $dir/*.css; do
if echo "$file" | grep -q -e '.min.css$'; then
continue
fi
if [ ! -f "$file" ]; then
continue
fi
echo "Shrinking $file"
minfile=`echo $file | sed -e 's/\.css$/\.min\.css/'`
do_shrink "$file" "$minfile"
done
done
+65
View File
@@ -0,0 +1,65 @@
#!/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: |
| Decrypt the encrypted parts of the HTTP Received: headers |
+-----------------------------------------------------------------------+
| Author: Tomas Tevesz <ice@extreme.hu> |
+-----------------------------------------------------------------------+
*/
/**
* If http_received_header_encrypt is configured, the IP address and the
* host name of the added Received: header is encrypted with 3DES, to
* protect information that some could consider sensitive, yet their
* availability is a must in some circumstances.
*
* Such an encrypted Received: header might look like:
*
* Received: from DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==
* [my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4]
* with HTTP/1.1 (POST); Thu, 14 May 2009 19:17:28 +0200
*
* In this example, the two encrypted components are the sender host name
* (DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==) and the IP
* address (my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4).
*
* Using this tool, they can be decrypted into plain text:
*
* $ bin/decrypt.sh 'my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4' \
* > 'DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ=='
* 84.3.187.208
* 5403BBD0.catv.pool.telekom.hu
* $
*
* Thus it is known that this particular message was sent by 84.3.187.208,
* having, at the time of sending, the name of 5403BBD0.catv.pool.telekom.hu.
*
* If (most likely binary) junk is shown, then
* - either the encryption password has, between the time the mail was sent
* and 'now', changed, or
* - you are dealing with counterfeit header data.
*/
define('INSTALL_PATH', realpath(__DIR__ .'/..') . '/');
require INSTALL_PATH . 'program/include/clisetup.php';
if ($argc < 2) {
die("Usage: " . basename($argv[0]) . " encrypted-hdr-part [encrypted-hdr-part ...]\n");
}
$RCMAIL = rcube::get_instance();
for ($i = 1; $i < $argc; $i++) {
printf("%s\n", $RCMAIL->decrypt($argv[$i]));
};
+141
View File
@@ -0,0 +1,141 @@
#!/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: |
| Utility script to remove all data related to a certain user |
| from the local database. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <thomas@roundcube.net> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
function print_usage()
{
print "Usage: deluser.sh [--host=HOST][--age=DAYS][--dry-run] [username]\n";
print "--host=HOST The IMAP hostname or IP the given user is related to\n";
print "--age=DAYS Delete all users who have not logged in for more than X days\n";
print "--dry-run List users but do not delete them (for use with --age)\n";
}
function _die($msg, $usage=false)
{
fwrite(STDERR, $msg . "\n");
if ($usage) print_usage();
exit(1);
}
$rcmail = rcube::get_instance();
// get arguments
$args = rcube_utils::get_opt(['h' => 'host', 'a' => 'age', 'd' => 'dry-run:bool']);
if (!empty($args['age']) && ($age = intval($args['age']))) {
$db = $rcmail->get_dbh();
$db->db_connect('r');
$query = $db->query("SELECT `username`, `mail_host` FROM " . $db->table_name('users', true)
. " WHERE `last_login` < " . $db->now($age * -1 * 86400)
. ($args['host'] ? " AND `mail_host` = " . $db->quote($args['host']) : '')
);
while ($user = $db->fetch_assoc($query)) {
if (!empty($args['dry-run'])) {
printf("%s (%s)\n", $user['username'], $user['mail_host']);
continue;
}
system(sprintf("php %s/deluser.sh --host=%s %s", INSTALL_PATH . 'bin', escapeshellarg($user['mail_host']), escapeshellarg($user['username'])));
}
exit(0);
}
$username = isset($args[0]) ? trim($args[0]) : null;
if (empty($username)) {
_die("Missing required parameters", true);
}
if (empty($args['host'])) {
$hosts = $rcmail->config->get('imap_host', '');
if (is_string($hosts)) {
$args['host'] = $hosts;
}
else if (is_array($hosts) && count($hosts) == 1) {
$args['host'] = reset($hosts);
}
else {
_die("Specify a host name", true);
}
// host can be a URL like tls://192.168.12.44
$host_url = parse_url($args['host']);
if ($host_url['host']) {
$args['host'] = $host_url['host'];
}
}
// connect to DB
$db = $rcmail->get_dbh();
$db->db_connect('w');
$transaction = false;
if (!$db->is_connected() || $db->is_error()) {
_die("No DB connection\n" . $db->is_error());
}
// find user in local database
$user = rcube_user::query($username, $args['host']);
if (!$user) {
die("User not found.\n");
}
// inform plugins about approaching user deletion
$plugin = $rcmail->plugins->exec_hook('user_delete_prepare', ['user' => $user, 'username' => $username, 'host' => $args['host']]);
// let plugins cleanup their own user-related data
if (!$plugin['abort']) {
$transaction = $db->startTransaction();
$plugin = $rcmail->plugins->exec_hook('user_delete', $plugin);
}
if ($plugin['abort']) {
unset($plugin['abort']);
if ($transaction) {
$db->rollbackTransaction();
}
_die("User deletion aborted by plugin");
}
$db->query('DELETE FROM ' . $db->table_name('users', true) . ' WHERE `user_id` = ?', $user->ID);
if ($db->is_error()) {
$rcmail->plugins->exec_hook('user_delete_rollback', $plugin);
_die("DB error occurred: " . $db->is_error());
}
else {
// inform plugins about executed user deletion
$plugin = $rcmail->plugins->exec_hook('user_delete_commit', $plugin);
if ($plugin['abort']) {
unset($plugin['abort']);
$db->rollbackTransaction();
$rcmail->plugins->exec_hook('user_delete_rollback', $plugin);
}
else {
$db->endTransaction();
echo "Successfully deleted user $user->ID\n";
}
}
+37
View File
@@ -0,0 +1,37 @@
#!/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: |
| Trigger garbage collecting routines manually (e.g. via cronjob) |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require INSTALL_PATH.'program/include/clisetup.php';
$rcmail = rcube::get_instance();
$session_driver = $rcmail->config->get('session_storage', 'db');
$session_lifetime = $rcmail->config->get('session_lifetime', 0) * 60 * 2;
// Clean expired SQL sessions
if ($session_driver == 'db' && $session_lifetime) {
$db = $rcmail->get_dbh();
$db->query("DELETE FROM " . $db->table_name('session')
. " WHERE changed < " . $db->now(-$session_lifetime));
}
// Clean caches and temp directory
$rcmail->gc();
+26
View File
@@ -0,0 +1,26 @@
#!/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 the fulltext index for all contacts of the internal |
| address book. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH.'program/include/clisetup.php';
ini_set('memory_limit', -1);
rcmail_utils::indexcontacts();
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env php
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Create database schema |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
// get arguments
$opts = rcube_utils::get_opt([
'd' => 'dir',
'u' => 'update'
]);
if (empty($opts['dir'])) {
rcube::raise_error("Database schema directory not specified (--dir).", false, true);
}
// Check if directory exists
if (!file_exists($opts['dir'])) {
rcube::raise_error("Specified database schema directory doesn't exist.", false, true);
}
$db = rcmail_utils::db();
if (!empty($opts['update']) && in_array($db->table_name('system'), (array)$db->list_tables())) {
echo "Checking for database schema updates..." . PHP_EOL;
rcmail_utils::db_update($opts['dir'], 'roundcube', null, ['errors' => true]);
} else {
rcmail_utils::db_init($opts['dir']);
}
+148
View File
@@ -0,0 +1,148 @@
#!/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(end($_SERVER['argv']));
$accept = in_array('-y', $_SERVER['argv']) ? 'y' : null;
if (empty($target_dir) || !is_dir(realpath($target_dir))) {
rcube::raise_error("Invalid target: not a directory\nUsage: installto.sh [-y] <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-z0-9-]*)/', $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 = $accept ?: trim(fgets(STDIN));
if (strtolower($input) == 'y') {
echo "Copying files to target location...";
$adds = [];
$dirs = ['bin','SQL','plugins','skins','program'];
if (is_dir(INSTALL_PATH . 'vendor') && (!is_file("$target_dir/composer.json") || rcmail_install::vendor_dir_untouched($target_dir))) {
$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, ['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 (['index.php','config/defaults.inc.php','composer.json-dist','jsdeps.json','CHANGELOG.md','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 (['.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";
}
// Warn about situation when using "complete" package to update "custom" installation (#7087)
// Note: "Complete" package do not include jsdeps.json nor install-jsdeps.sh
if (file_exists("$target_dir/jsdeps.json") && !file_exists(INSTALL_PATH . "jsdeps.json")) {
$adds[] = "WARNING: JavaScript dependencies update skipped. New jsdeps.json file not found.";
}
// check if js-deps are up-to-date
else 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";
}
}
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("\n", $adds) . "\n\n";
}
echo "Running update script at target...\n";
system("cd $target_dir && php bin/update.sh --version=$oldversion" . ($accept ? ' -y' : ''));
echo "All done.\n";
}
else {
echo "Update cancelled. See ya!\n";
}
+51
View File
@@ -0,0 +1,51 @@
#!/bin/sh
set -e
PWD=`dirname "$0"`
LANG_IN='ECMASCRIPT5'
do_shrink() {
rm -f "$2"
# copy the first comment block with license information for LibreJS
grep -q '@lic' $1 && sed -n '/\/\*/,/\*\// { p; /\*\//q; }' $1 > $2
uglifyjs --compress --mangle -- $1 >> $2
}
if which uglifyjs > /dev/null 2>&1; then
:
else
echo "uglifyjs not found. Please install e.g. 'npm install -g uglify-js'."
exit 1
fi
# compress single file from argument
if [ $# -gt 0 ]; then
JS_FILE="$1"
if [ $# -gt 1 ]; then
LANG_IN="$2"
fi
echo "Shrinking $JS_FILE"
minfile=`echo $JS_FILE | sed -e 's/\.js$/\.min\.js/'`
do_shrink "$JS_FILE" "$minfile" "$LANG_IN"
exit
fi
DIRS="$PWD/../program/js $PWD/../skins/* $PWD/../plugins/* $PWD/../plugins/*/skins/* $PWD/../plugins/managesieve/codemirror/lib"
# default: compress application scripts
for dir in $DIRS; do
for file in $dir/*.js; do
if echo "$file" | grep -q -e '.min.js$'; then
continue
fi
if [ ! -f "$file" ]; then
continue
fi
echo "Shrinking $file"
minfile=`echo $file | sed -e 's/\.js$/\.min\.js/'`
do_shrink "$file" "$minfile" "$LANG_IN"
done
done
+24
View File
@@ -0,0 +1,24 @@
#!/bin/sh
set -x
BIN_PHPDOC=`/usr/bin/which phpdoc`
if [ ! -x "$BIN_PHPDOC" ]
then
echo "phpdoc not found"
exit 1
fi
INSTALL_PATH="`dirname $0`/.."
PATH_PROJECT=$INSTALL_PATH/program/include
PATH_FRAMEWORK=$INSTALL_PATH/program/lib/Roundcube
PATH_DOCS=$INSTALL_PATH/doc/phpdoc
TITLE="Roundcube Webmail"
PACKAGES="Webmail"
OUTPUTFORMAT=HTML
TEMPLATE=responsive-twig
# make documentation
$BIN_PHPDOC -d $PATH_PROJECT,$PATH_FRAMEWORK -t $PATH_DOCS --title "$TITLE" \
--defaultpackagename $PACKAGES --template=$TEMPLATE
+68
View File
@@ -0,0 +1,68 @@
#!/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: |
| Bulk-change settings stored in user preferences |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH.'program/include/clisetup.php';
function print_usage()
{
print "Usage: moduserprefs.sh [options] pref-name [pref-value]\n";
print "Options:\n";
print " --user=user-id User ID in local database\n";
print " --config=path Location of additional configuration file\n";
print " --delete Unset the given preference\n";
print " --type=type Pref-value type: int, bool, string\n";
}
// get arguments
$args = rcube_utils::get_opt([
'u' => 'user',
'd' => 'delete:bool',
't' => 'type',
'c' => 'config',
]);
if (empty($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') {
print_usage();
exit;
}
else if (empty($args[0]) || (empty($args[1]) && empty($args['delete']))) {
print "Missing required parameters.\n";
print_usage();
exit;
}
$pref_name = trim($args[0]);
$pref_value = !empty($args['delete']) ? null : trim($args[1]);
if ($pref_value === null) {
$args['type'] = null;
}
if (!empty($args['config'])) {
$rcube = rcube::get_instance();
$rcube->config->load_from_file($args['config']);
}
$type = isset($args['type']) ? $args['type'] : null;
$user = isset($args['user']) ? $args['user'] : null;
rcmail_utils::mod_pref($pref_name, $pref_value, $user, $type);
+149
View File
@@ -0,0 +1,149 @@
#!/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. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <thomas@roundcube.net> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
ini_set('memory_limit', -1);
require_once INSTALL_PATH.'program/include/clisetup.php';
function print_usage()
{
print "Usage: msgexport.sh -h imap-host -u user-name -m mailbox name\n";
print "--host IMAP host\n";
print "--user IMAP user name\n";
print "--mbox Folder name, set to '*' for all\n";
print "--file Output file\n";
}
function vputs($str)
{
$out = !empty($GLOBALS['args']['file']) ? STDOUT : STDERR;
fwrite($out, $str);
}
function progress_update($pos, $max)
{
$percent = round(100 * $pos / $max);
vputs(sprintf("%3d%% [%-51s] %d/%d\033[K\r", $percent, @str_repeat('=', $percent / 2) . '>', $pos, $max));
}
function export_mailbox($mbox, $filename)
{
global $IMAP;
$IMAP->set_folder($mbox);
vputs("Getting message list of {$mbox}...");
$index = $IMAP->index($mbox, null, 'ASC');
$count = $index->count();
$index = $index->get();
vputs("$count messages\n");
if ($filename) {
if (!($out = fopen($filename, 'w'))) {
vputs("Cannot write to output file\n");
return;
}
vputs("Writing to $filename\n");
}
else {
$out = STDOUT;
}
for ($i = 0; $i < $count; $i++) {
$headers = $IMAP->get_message_headers($index[$i]);
$from = current(rcube_mime::decode_address_list($headers->from, 1, false));
fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid));
$IMAP->get_raw_body($headers->uid, $out);
fwrite($out, "\n\n\n");
progress_update($i+1, $count);
}
vputs("\ncomplete.\n");
if ($filename) {
fclose($out);
}
}
// get arguments
$opts = ['h' => 'host', 'u' => 'user', 'p' => 'pass', 'm' => 'mbox', 'f' => 'file'];
$args = rcube_utils::get_opt($opts) + ['host' => 'localhost', 'mbox' => 'INBOX'];
if (!isset($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') {
print_usage();
exit;
}
else if (!$args['host']) {
vputs("Missing required parameters.\n");
print_usage();
exit;
}
// prompt for username if not set
if (empty($args['user'])) {
vputs("IMAP user: ");
$args['user'] = trim(fgets(STDIN));
}
// prompt for password
$args['pass'] = rcube_utils::prompt_silent("Password: ");
// parse $host URL
$a_host = parse_url($args['host']);
if (!empty($a_host['host'])) {
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? TRUE : FALSE;
$imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143);
}
else {
$host = $args['host'];
$imap_port = 143;
$imap_ssl = false;
}
// instantiate IMAP class
$IMAP = new rcube_imap(null);
// try to connect to IMAP server
if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl)) {
vputs("IMAP login successful.\n");
$filename = null;
$mailboxes = $args['mbox'] == '*' ? $IMAP->list_folders(null) : [$args['mbox']];
foreach ($mailboxes as $mbox) {
if (!empty($args['file'])) {
$filename = preg_replace('/\.[a-z0-9]{3,4}$/i', '', $args['file']) . asciiwords($mbox) . '.mbox';
}
else if ($args['mbox'] == '*') {
$filename = asciiwords($mbox) . '.mbox';
}
if ($args['mbox'] == '*' && in_array(strtolower($mbox), ['junk','spam','trash'])) {
continue;
}
export_mailbox($mbox, $filename);
}
}
else {
vputs("IMAP login failed.\n");
}
+117
View File
@@ -0,0 +1,117 @@
#!/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. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <thomas@roundcube.net> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
ini_set('memory_limit', -1);
require_once INSTALL_PATH.'program/include/clisetup.php';
function print_usage()
{
print "Usage: msgimport.sh -h imap-host -u user-name -m mailbox -f message-file\n";
print "--host IMAP host\n";
print "--user IMAP user name\n";
print "--mbox Target mailbox\n";
print "--file Message file to upload\n";
}
// get arguments
$opts = ['h' => 'host', 'u' => 'user', 'p' => 'pass', 'm' => 'mbox', 'f' => 'file'];
$args = rcube_utils::get_opt($opts) + ['host' => 'localhost', 'mbox' => 'INBOX'];
if (!isset($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') {
print_usage();
exit;
}
else if (empty($args['host']) || empty($args['file'])) {
print "Missing required parameters.\n";
print_usage();
exit;
}
else if (!is_file($args['file'])) {
rcube::raise_error("Cannot read message file.", false, true);
}
// prompt for username if not set
if (empty($args['user'])) {
//fwrite(STDOUT, "Please enter your name\n");
echo "IMAP user: ";
$args['user'] = trim(fgets(STDIN));
}
// prompt for password
if (empty($args['pass'])) {
$args['pass'] = rcube_utils::prompt_silent("Password: ");
}
// parse $host URL
$a_host = parse_url($args['host']);
if (!empty($a_host['host'])) {
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? TRUE : FALSE;
$imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143);
}
else {
$host = $args['host'];
$imap_port = 143;
$imap_ssl = false;
}
// instantiate IMAP class
$IMAP = new rcube_imap(null);
// try to connect to IMAP server
if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl)) {
print "IMAP login successful.\n";
print "Uploading messages...\n";
$count = 0;
$message = $lastline = '';
$fp = fopen($args['file'], 'r');
while (($line = fgets($fp)) !== false) {
if (preg_match('/^From\s+-/', $line) && $lastline == '') {
if (!empty($message)) {
if ($IMAP->save_message($args['mbox'], rtrim($message))) {
$count++;
}
else {
rcube::raise_error("Failed to save message to {$args['mbox']}", false, true);
}
$message = '';
}
continue;
}
$message .= $line;
$lastline = rtrim($line);
}
if (!empty($message) && $IMAP->save_message($args['mbox'], rtrim($message))) {
$count++;
}
// upload message from file
if ($count) {
print "$count messages successfully added to {$args['mbox']}.\n";
}
else {
print "Adding messages failed!\n";
}
}
else {
rcube::raise_error("IMAP login failed.", false, true);
}
+328
View File
@@ -0,0 +1,328 @@
#!/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: |
| Check local configuration and database schema after upgrading |
| to a new version |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
// get arguments
$opts = rcube_utils::get_opt(['v' => 'version', 'y' => 'accept:bool']);
// ask user if no version is specified
if (empty($opts['version'])) {
echo "What version are you upgrading from? Type '?' if you don't know.\n";
if (($input = trim(fgets(STDIN))) && preg_match('/^[0-9.]+[a-z0-9-]*$/', $input)) {
$opts['version'] = $input;
}
else {
$opts['version'] = RCMAIL_VERSION;
}
}
$RCI = rcmail_install::get_instance();
$RCI->load_config();
if ($RCI->configured) {
$success = true;
if (($messages = $RCI->check_config($opts['version'])) || $RCI->legacy_config) {
$success = false;
$err = 0;
// list old/replaced config options
if (!empty($messages['replaced'])) {
echo "WARNING: Replaced config options:\n";
echo "(These config options have been replaced or renamed)\n";
foreach ($messages['replaced'] as $msg) {
echo "- '" . $msg['prop'] . "' was replaced by '" . $msg['replacement'] . "'\n";
$err++;
}
}
// list obsolete config options (just a notice)
if (!empty($messages['obsolete'])) {
echo "NOTICE: Obsolete config options:\n";
echo "(You still have some obsolete or inexistent properties set."
. " This isn't a problem but should be noticed)\n";
foreach ($messages['obsolete'] as $msg) {
echo "- '" . $msg['prop'] . (!empty($msg['explain']) ? "': " . $msg['explain'] : "'") . "\n";
$err++;
}
}
if (!$err && $RCI->legacy_config) {
echo "WARNING: Your configuration needs to be migrated!\n";
echo "We changed the configuration files structure and your two config files "
. "main.inc.php and db.inc.php have to be merged into one single file.\n";
$err++;
}
// ask user to update config files
if ($err) {
if (empty($opts['accept'])) {
echo "Do you want me to fix your local configuration? (y/N)\n";
$input = trim(fgets(STDIN));
}
// positive: merge the local config with the defaults
if (!empty($opts['accept']) || strtolower($input) == 'y') {
$error = $written = false;
echo "- backing up the current config file(s)...\n";
foreach (['config', 'main', 'db'] as $file) {
if (file_exists(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php')) {
if (!copy(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php', RCMAIL_CONFIG_DIR . '/' . $file . '.old.php')) {
$error = true;
}
}
}
if (!$error) {
$RCI->merge_config();
echo "- writing " . RCMAIL_CONFIG_DIR . "/config.inc.php...\n";
$written = $RCI->save_configfile($RCI->create_config(false));
}
// Success!
if ($written) {
echo "Done.\n";
echo "Your configuration files are now up-to-date!\n";
if (!empty($messages['missing'])) {
echo "But you still need to add the following missing options:\n";
foreach ($messages['missing'] as $msg) {
echo "- '" . $msg['prop'] . ($msg['name'] ? "': " . $msg['name'] : "'") . "\n";
}
}
if ($RCI->legacy_config) {
foreach (['main', 'db'] as $file) {
@unlink(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php');
}
}
}
else {
echo "Failed to write config file(s)!\n";
echo "Grant write privileges to the current user or update the files manually "
. "according to the above messages.\n";
}
}
else {
echo "Please update your config files manually according to the above messages.\n";
}
}
// list of config options with changed default (just a notice)
if (!empty($messages['defaults'])) {
echo "WARNING: Changed defaults (These config options have new default values):\n";
foreach ($messages['defaults'] as $opt) {
echo "- '{$opt}'\n";
}
}
// check dependencies based on the current configuration
if (!empty($messages['dependencies'])) {
echo "WARNING: Dependency check failed!\n";
echo "(Some of your configuration settings require other options to be configured "
. "or additional PHP modules to be installed)\n";
foreach ($messages['dependencies'] as $msg) {
echo "- " . $msg['prop'] . ': ' . $msg['explain'] . "\n";
}
echo "Please fix your config files and run this script again!\n";
echo "See ya.\n";
}
}
// check file type detection
if ($RCI->check_mime_detection()) {
echo "WARNING: File type detection doesn't work properly!\n";
echo "Please check the 'mime_magic' config option or the finfo functions of PHP and run this script again.\n";
}
if ($RCI->check_mime_extensions()) {
echo "WARNING: Mimetype to file extension mapping doesn't work properly!\n";
echo "Please check the 'mime_types' config option and run this script again.\n";
}
// check database schema
if (!empty($RCI->config['db_dsnw'])) {
echo "Executing database schema update.\n";
$success = rcmail_utils::db_update(INSTALL_PATH . 'SQL', 'roundcube', $opts['version'], ['errors' => true]);
}
// update composer dependencies
if (is_file(INSTALL_PATH . 'composer.json') && is_readable(INSTALL_PATH . 'composer.json-dist')) {
$composer_data = json_decode(file_get_contents(INSTALL_PATH . 'composer.json'), true);
$composer_template = json_decode(file_get_contents(INSTALL_PATH . 'composer.json-dist'), true);
$composer_json = null;
// update the require section with the new dependencies
if (!empty($composer_data['require']) && !empty($composer_template['require'])) {
$composer_data['require'] = array_merge($composer_data['require'], $composer_template['require']);
// remove obsolete packages
$old_packages = [
'pear-pear.php.net/net_socket',
'pear-pear.php.net/auth_sasl',
'pear-pear.php.net/net_idna2',
'pear-pear.php.net/mail_mime',
'pear-pear.php.net/net_smtp',
'pear-pear.php.net/crypt_gpg',
'pear-pear.php.net/net_sieve',
'pear/mail_mime-decode',
'roundcube/net_sieve',
'endroid/qrcode',
'endroid/qr-code',
];
foreach ($old_packages as $pkg) {
if (array_key_exists($pkg, $composer_data['require'])) {
unset($composer_data['require'][$pkg]);
}
}
}
// update the repositories section with the new dependencies
if (!empty($composer_template['repositories'])) {
if (empty($composer_data['repositories'])) {
$composer_data['repositories'] = [];
}
foreach ($composer_template['repositories'] as $repo) {
$rkey = repo_key($repo);
$existing = false;
foreach ($composer_data['repositories'] as $k => $_repo) {
if ($rkey == repo_key($_repo)) {
// switch to https://
if (isset($_repo['url']) && strpos($_repo['url'], 'http://') === 0) {
$composer_data['repositories'][$k]['url'] = 'https:' . substr($_repo['url'], 5);
}
$existing = true;
break;
}
// remove old repos
if (isset($_repo['url']) && strpos($_repo['url'], 'git://git.kolab.org') === 0) {
unset($composer_data['repositories'][$k]);
}
else if (
$_repo['type'] == 'package'
&& !empty($_repo['package']['name'])
&& $_repo['package']['name'] == 'Net_SMTP'
) {
unset($composer_data['repositories'][$k]);
}
}
if (!$existing) {
$composer_data['repositories'][] = $repo;
}
}
$composer_data['repositories'] = array_values($composer_data['repositories']);
}
$composer_json = json_encode($composer_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// write updated composer.json back to disk
if ($composer_json && is_writeable(INSTALL_PATH . 'composer.json')) {
$success &= (bool)file_put_contents(INSTALL_PATH . 'composer.json', $composer_json);
}
else {
echo "WARNING: unable to update composer.json!\n";
echo "Please replace the 'require' section in your composer.json with the following:\n";
$require_json = '';
foreach ($composer_data['require'] as $pkg => $ver) {
$require_json .= sprintf(' "%s": "%s",'."\n", $pkg, $ver);
}
echo ' "require": {'."\n";
echo rtrim($require_json, ",\n");
echo "\n }\n\n";
}
if (!rcmail_install::vendor_dir_untouched(INSTALL_PATH)) {
$exit_code = 1;
if ($composer_bin = find_composer()) {
echo "Executing " . $composer_bin . " to update dependencies...\n";
echo system("$composer_bin update -d " . escapeshellarg(INSTALL_PATH) . " --no-dev", $exit_code);
}
if ($exit_code != 0) {
echo "-----------------------------------------------------------------------------\n";
echo "ATTENTION: Update dependencies by running `php composer.phar update --no-dev`\n";
echo "-----------------------------------------------------------------------------\n";
}
}
}
// index contacts for fulltext searching
if ($opts['version'] && version_compare(version_parse($opts['version']), '0.6.0', '<')) {
rcmail_utils::indexcontacts();
}
if ($success) {
echo "This instance of Roundcube is up-to-date.\n";
echo "Have fun!\n";
}
}
else {
echo "This instance of Roundcube is not yet configured!\n";
echo "Open http://url-to-roundcube/installer/ in your browser and follow the instructions.\n";
}
function repo_key($repo)
{
$key = $repo['type'];
if (!empty($repo['url'])) {
$key .= preg_replace('/^https?:/', '', $repo['url']);
}
if (!empty($repo['package']['name'])) {
$key .= $repo['package']['name'];
}
return $key;
}
function find_composer()
{
if (is_file(INSTALL_PATH . 'composer.phar')) {
return 'php composer.phar';
}
foreach (['composer', 'composer.phar'] as $check_file) {
$which = trim(system("which $check_file"));
if (!empty($which)) {
return $which;
}
}
return null;
}
+117
View File
@@ -0,0 +1,117 @@
#!/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 cache-baster marks for css background images |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
// get arguments
$opts = rcube_utils::get_opt(['d' => 'dir']);
if (empty($opts['dir'])) {
print "Skin directory not specified (--dir). Using skins/ and plugins/*/skins/.\n";
$dir = INSTALL_PATH . 'skins';
$dir_p = INSTALL_PATH . 'plugins';
$skins = glob("$dir/*", GLOB_ONLYDIR);
$skins_p = glob("$dir_p/*/skins/*", GLOB_ONLYDIR);
$dirs = array_merge($skins, $skins_p);
}
// Check if directory exists
else if (!file_exists($opts['dir'])) {
rcube::raise_error("Specified directory doesn't exist.", false, true);
}
else {
$dirs = [$opts['dir']];
}
foreach ($dirs as $dir) {
$img_dir = $dir . '/images';
if (!file_exists($img_dir)) {
continue;
}
$files = get_files($dir);
$images = get_images($img_dir);
$find = [];
$replace = [];
// build regexps array
foreach ($images as $path => $sum) {
$path_ex = str_replace('.', '\\.', $path);
$find[] = "#url\(['\"]?images/$path_ex(\?v=[a-f0-9-\.]+)?['\"]?\)#";
$replace[] = "url(images/$path?v=$sum)";
}
foreach ($files as $file) {
$file = $dir . '/' . $file;
print "File: $file\n";
$content = file_get_contents($file);
$content = preg_replace($find, $replace, $content, -1, $count);
if ($count) {
file_put_contents($file, $content);
}
}
}
function get_images($dir)
{
$images = [];
$dh = opendir($dir);
while ($file = readdir($dh)) {
if (preg_match('/^(.+)\.(gif|ico|png|jpg|jpeg)$/', $file, $m)) {
$filepath = "$dir/$file";
$images[$file] = substr(md5_file($filepath), 0, 4) . '.' . filesize($filepath);
print "Image: $filepath ({$images[$file]})\n";
}
else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) {
foreach (get_images($dir . '/' . $file) as $img => $sum) {
$images[$file . '/' . $img] = $sum;
}
}
}
closedir($dh);
return $images;
}
function get_files($dir)
{
$files = [];
$dh = opendir($dir);
while ($file = readdir($dh)) {
if (preg_match('/^(.+)\.(css|html)$/', $file, $m)) {
$files[] = $file;
}
else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) {
foreach (get_files($dir . '/' . $file) as $f) {
$files[] = $file . '/' . $f;
}
}
}
closedir($dh);
return $files;
}
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env php
<?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: |
| Update database schema |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' );
require_once INSTALL_PATH . 'program/include/clisetup.php';
// get arguments
$opts = rcube_utils::get_opt([
'v' => 'version',
'd' => 'dir',
'p' => 'package',
]);
if (empty($opts['dir'])) {
rcube::raise_error("Database schema directory not specified (--dir).", false, true);
}
if (empty($opts['package'])) {
rcube::raise_error("Database schema package name not specified (--package).", false, true);
}
rcmail_utils::db_update($opts['dir'], $opts['package'], $opts['version'] ?? null, ['errors' => true]);
+7
View File
@@ -0,0 +1,7 @@
# deny webserver access to this directory
<ifModule mod_authz_core.c>
Require all denied
</ifModule>
<ifModule !mod_authz_core.c>
Deny from all
</ifModule>
+89
View File
@@ -0,0 +1,89 @@
<?php
/* Local configuration for Roundcube Webmail */
// ----------------------------------
// SQL DATABASE
// ----------------------------------
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// Note: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
// or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
// Note: Various drivers support various additional arguments for connection,
// for Mysql: key, cipher, cert, capath, ca, verify_server_cert,
// for Postgres: application_name, sslmode, sslcert, sslkey, sslrootcert, sslcrl, sslcompression, service.
// e.g. 'mysql://roundcube:@localhost/roundcubemail?verify_server_cert=false'
$config['db_dsnw'] = 'mysql://root:@localhost/roundcubemail';
// Syslog ident string to use, if using the 'syslog' log driver.
$config['syslog_id'] = 'rutymail';
// ----------------------------------
// IMAP
// ----------------------------------
// The IMAP host (and optionally port number) chosen to perform the log-in.
// Leave blank to show a textbox at login, give a list of hosts
// to display a pulldown menu or set one host as string.
// Enter hostname with prefix ssl:// to use Implicit TLS, or use
// prefix tls:// to use STARTTLS.
// If port number is omitted it will be set to 993 (for ssl://) or 143 otherwise.
// Supported replacement variables:
// %n - hostname ($_SERVER['SERVER_NAME'])
// %t - hostname without the first part
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %s - domain name after the '@' from e-mail address provided at login screen
// For example %n = mail.domain.tld, %t = domain.tld
// WARNING: After hostname change update of mail_host column in users table is
// required to match old user data records with the new host.
$config['imap_host'] = 'localhost:143';
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = '';
// This key is used for encrypting purposes, like storing of imap password
// in the session. For historical reasons it's called DES_key, but it's used
// with any configured cipher_method (see below).
// For the default cipher_method a required key length is 24 characters.
$config['des_key'] = 'Sx7kK03ueoGCZU8Q58J8Kopv';
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Ruty - Webmail';
// ----------------------------------
// PLUGINS
// ----------------------------------
// List of active plugins (in plugins/ directory)
$config['plugins'] = ['acl', 'additional_message_headers', 'archive', 'attachment_reminder', 'autologon', 'autologout', 'database_attachments', 'debug_logger', 'emoticons', 'enigma', 'example_addressbook', 'filesystem_attachments', 'help', 'hide_blockquote', 'http_authentication', 'identicon', 'identity_select', 'jqueryui', 'krb_authentication', 'managesieve', 'markasjunk', 'new_user_dialog', 'new_user_identity', 'newmail_notifier', 'password', 'reconnect', 'redundant_attachments', 'show_additional_headers', 'squirrelmail_usercopy', 'subscriptions_option', 'userinfo', 'vcard_attachments', 'virtuser_file', 'virtuser_query', 'zipdownload'];
// the default locale setting (leave empty for auto-detection)
// RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR
$config['language'] = 'fr_FR';
// store draft message is this mailbox
// leave blank if draft messages should not be stored
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['drafts_mbox'] = 'Brouillons';
// store spam messages in this mailbox
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['junk_mbox'] = 'Spam';
// store sent message is this mailbox
// leave blank if sent messages should not be stored
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['sent_mbox'] = 'Envoyés';
// move messages to this folder when deleting them
// leave blank if they should be deleted directly
// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP)
$config['trash_mbox'] = 'Poubelle';
// Encoding of long/non-ascii attachment names:
// 0 - Full RFC 2231 compatible
// 1 - RFC 2047 for 'name' and RFC 2231 for 'filename' parameter (Thunderbird's default)
// 2 - Full 2047 compatible
$config['mime_param_folding'] = 0;
+66
View File
@@ -0,0 +1,66 @@
<?php
/*
+-----------------------------------------------------------------------+
| Local configuration for the Roundcube Webmail installation. |
| |
| This is a sample configuration file only containing the minimum |
| setup required for a functional installation. Copy more options |
| from defaults.inc.php to this file to override the defaults. |
| |
| 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. |
+-----------------------------------------------------------------------+
*/
$config = [];
// Database connection string (DSN) for read+write operations
// Format (compatible with PEAR MDB2): db_provider://user:password@host/database
// Currently supported db_providers: mysql, pgsql, sqlite, mssql, sqlsrv, oracle
// For examples see http://pear.php.net/manual/en/package.database.mdb2.intro-dsn.php
// NOTE: for SQLite use absolute path (Linux): 'sqlite:////full/path/to/sqlite.db?mode=0646'
// or (Windows): 'sqlite:///C:/full/path/to/sqlite.db'
$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail';
// IMAP host chosen to perform the log-in.
// See defaults.inc.php for the option description.
$config['imap_host'] = 'localhost:143';
// SMTP server host (for sending mails).
// See defaults.inc.php for the option description.
$config['smtp_host'] = 'localhost:587';
// SMTP username (if required) if you use %u as the username Roundcube
// will use the current username for login
$config['smtp_user'] = '%u';
// SMTP password (if required) if you use %p as the password Roundcube
// will use the current user's password for login
$config['smtp_pass'] = '%p';
// provide an URL where a user can get support for this Roundcube installation
// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
$config['support_url'] = '';
// Name your service. This is displayed on the login screen and in the window title
$config['product_name'] = 'Roundcube Webmail';
// This key is used to encrypt the users imap password which is stored
// in the session record. For the default cipher method it must be
// exactly 24 characters long.
// YOUR KEY MUST BE DIFFERENT THAN THE SAMPLE VALUE FOR SECURITY REASONS
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
// List of active plugins (in plugins/ directory)
$config['plugins'] = [
'archive',
'zipdownload',
];
// skin name: folder from skins/
$config['skin'] = 'elastic';
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
<?php
/**
* Local mapping file to specify mime-types based on common file-name extensions
*
* Please note that this mapping takes precedence over the content-based mime-type detection
* and should only contain mappings which cannot be detected properly from the file contents.
*/
return [
'xls' => 'application/vnd.ms-excel',
'xlm' => 'application/vnd.ms-excel',
'xla' => 'application/vnd.ms-excel',
'xlc' => 'application/vnd.ms-excel',
'xlt' => 'application/vnd.ms-excel',
'xlw' => 'application/vnd.ms-excel',
'pdf' => 'application/pdf',
'ppt' => 'application/vnd.ms-powerpoint',
'pps' => 'application/vnd.ms-powerpoint',
'pot' => 'application/vnd.ms-powerpoint',
'doc' => 'application/msword',
'dot' => 'application/msword',
'odc' => 'application/vnd.oasis.opendocument.chart',
'otc' => 'application/vnd.oasis.opendocument.chart-template',
'odf' => 'application/vnd.oasis.opendocument.formula',
'otf' => 'application/vnd.oasis.opendocument.formula-template',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'oti' => 'application/vnd.oasis.opendocument.image-template',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'otm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xps' => 'application/vnd.ms-xpsdocument',
'rar' => 'application/x-rar-compressed',
'7z' => 'application/x-7z-compressed',
's7z' => 'application/x-7z-compressed',
'vcf' => 'text/vcard',
'ics' => 'text/calendar',
];
+290
View File
@@ -0,0 +1,290 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
if (!class_exists('rcmail_install', false) || !isset($RCI)) {
die("Not allowed! Please open installer/index.php instead.");
}
$required_php_exts = [
'PCRE' => 'pcre',
'DOM' => 'dom',
'Session' => 'session',
'XML' => 'xml',
'Intl' => 'intl',
'JSON' => 'json',
'PDO' => 'PDO',
'Multibyte' => 'mbstring',
'OpenSSL' => 'openssl',
'Filter' => 'filter',
'Ctype' => 'ctype',
];
$optional_php_exts = [
'cURL' => 'curl',
'FileInfo' => 'fileinfo',
'Exif' => 'exif',
'Iconv' => 'iconv',
'LDAP' => 'ldap',
'GD' => 'gd',
'Imagick' => 'imagick',
'XMLWriter' => 'xmlwriter',
'Zip' => 'zip',
];
$required_libs = [
'PEAR' => 'pear.php.net',
'Auth_SASL' => 'pear.php.net',
'Net_SMTP' => 'pear.php.net',
'Mail_mime' => 'pear.php.net',
'GuzzleHttp\Client' => 'github.com/guzzle/guzzle',
];
$optional_libs = [
'Net_LDAP3' => 'git.kolab.org',
];
$ini_checks = [
'file_uploads' => 1,
'session.auto_start' => 0,
'mbstring.func_overload' => 0,
'suhosin.session.encrypt' => 0,
];
$optional_checks = [
'date.timezone' => '-VALID-',
];
$source_urls = [
'cURL' => 'https://www.php.net/manual/en/book.curl.php',
'Sockets' => 'https://www.php.net/manual/en/book.sockets.php',
'Session' => 'https://www.php.net/manual/en/book.session.php',
'PCRE' => 'https://www.php.net/manual/en/book.pcre.php',
'FileInfo' => 'https://www.php.net/manual/en/book.fileinfo.php',
'Multibyte' => 'https://www.php.net/manual/en/book.mbstring.php',
'OpenSSL' => 'https://www.php.net/manual/en/book.openssl.php',
'JSON' => 'https://www.php.net/manual/en/book.json.php',
'DOM' => 'https://www.php.net/manual/en/book.dom.php',
'Iconv' => 'https://www.php.net/manual/en/book.iconv.php',
'Intl' => 'https://www.php.net/manual/en/book.intl.php',
'Exif' => 'https://www.php.net/manual/en/book.exif.php',
'oci8' => 'https://www.php.net/manual/en/book.oci8.php',
'PDO' => 'https://www.php.net/manual/en/book.pdo.php',
'LDAP' => 'https://www.php.net/manual/en/book.ldap.php',
'GD' => 'https://www.php.net/manual/en/book.image.php',
'Imagick' => 'https://www.php.net/manual/en/book.imagick.php',
'XML' => 'https://www.php.net/manual/en/book.xml.php',
'XMLWriter' => 'https://www.php.net/manual/en/book.xmlwriter.php',
'Zip' => 'https://www.php.net/manual/en/book.zip.php',
'Filter' => 'https://www.php.net/manual/en/book.filter.php',
'Ctype' => 'https://www.php.net/manual/en/book.ctype.php',
'pdo_mysql' => 'https://www.php.net/manual/en/ref.pdo-mysql.php',
'pdo_pgsql' => 'https://www.php.net/manual/en/ref.pdo-pgsql.php',
'pdo_sqlite' => 'https://www.php.net/manual/en/ref.pdo-sqlite.php',
'pdo_sqlite2' => 'https://www.php.net/manual/en/ref.pdo-sqlite.php',
'pdo_sqlsrv' => 'https://www.php.net/manual/en/ref.pdo-sqlsrv.php',
'pdo_dblib' => 'https://www.php.net/manual/en/ref.pdo-dblib.php',
'PEAR' => 'https://pear.php.net',
'Net_SMTP' => 'https://pear.php.net/package/Net_SMTP',
'Mail_mime' => 'https://pear.php.net/package/Mail_mime',
'Net_LDAP3' => 'https://git.kolab.org/diffusion/PNL',
];
?>
<form action="index.php" method="get">
<?php
echo '<input type="hidden" name="_step" value="' . ($RCI->configured ? 3 : 2) . '" />';
?>
<h3>Checking PHP version</h3>
<?php
define('MIN_PHP_VERSION', '7.3.0');
if (version_compare(PHP_VERSION, MIN_PHP_VERSION, '>=')) {
$RCI->pass('Version', 'PHP ' . PHP_VERSION . ' detected');
}
else {
$RCI->fail('Version', 'PHP Version ' . MIN_PHP_VERSION . ' or greater is required ' . PHP_VERSION . ' detected');
}
?>
<h3>Checking PHP extensions</h3>
<p class="hint">The following modules/extensions are <em>required</em> to run Roundcube:</p>
<?php
// get extensions location
$ext_dir = ini_get('extension_dir');
$prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
foreach ($required_php_exts as $name => $ext) {
if (extension_loaded($ext)) {
$RCI->pass($name);
}
else {
$_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX;
$msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : '';
$RCI->fail($name, $msg, $source_urls[$name]);
}
echo '<br />';
}
?>
<p class="hint">The next couple of extensions are <em>optional</em> and recommended to get the best performance:</p>
<?php
foreach ($optional_php_exts as $name => $ext) {
if (extension_loaded($ext)) {
$RCI->pass($name);
}
else {
$_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX;
$msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : '';
$RCI->na($name, $msg, $source_urls[$name]);
}
echo '<br />';
}
?>
<h3>Checking available databases</h3>
<p class="hint">Check which of the supported extensions are installed. At least one of them is required.</p>
<?php
$prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
foreach ($RCI->supported_dbs as $database => $ext) {
if (extension_loaded($ext)) {
$RCI->pass($database);
$found_db_driver = true;
}
else {
$_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX;
$msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : '';
$RCI->na($database, $msg, $source_urls[$ext]);
}
echo '<br />';
}
if (empty($found_db_driver)) {
$RCI->failures++;
}
?>
<h3>Check for required 3rd party libs</h3>
<p class="hint">This also checks if the include path is set correctly.</p>
<?php
foreach ($required_libs as $classname => $vendor) {
if (class_exists($classname)) {
$RCI->pass($classname);
}
else {
$RCI->fail($classname, "Failed to load class $classname from $vendor", $source_urls[$classname]);
}
echo "<br />";
}
foreach ($optional_libs as $classname => $vendor) {
if (class_exists($classname)) {
$RCI->pass($classname);
}
else {
$RCI->na($classname, "Recommended to install $classname from $vendor", $source_urls[$classname]);
}
echo "<br />";
}
?>
<h3>Checking php.ini/.htaccess settings</h3>
<p class="hint">The following settings are <em>required</em> to run Roundcube:</p>
<?php
foreach ($ini_checks as $var => $val) {
$status = ini_get($var);
if ($val === '-NOTEMPTY-') {
if (empty($status)) {
$RCI->fail($var, "empty value detected");
}
else {
$RCI->pass($var);
}
}
else if (filter_var($status, FILTER_VALIDATE_BOOLEAN) == $val) {
$RCI->pass($var);
}
else {
$RCI->fail($var, "is '$status', should be '$val'");
}
echo '<br />';
}
?>
<p class="hint">The following settings are <em>optional</em> and recommended:</p>
<?php
foreach ($optional_checks as $var => $val) {
$status = ini_get($var);
if ($val === '-NOTEMPTY-') {
if (empty($status)) {
$RCI->optfail($var, "Could be set");
}
else {
$RCI->pass($var);
}
echo '<br />';
continue;
}
if ($val === '-VALID-') {
if ($var == 'date.timezone') {
try {
$tz = new DateTimeZone($status);
$RCI->pass($var);
}
catch (Exception $e) {
$RCI->optfail($var, empty($status) ? "not set" : "invalid value detected: $status");
}
}
else {
$RCI->pass($var);
}
}
else if (filter_var($status, FILTER_VALIDATE_BOOLEAN) == $val) {
$RCI->pass($var);
}
else {
$RCI->optfail($var, "is '$status', could be '$val'");
}
echo '<br />';
}
?>
<?php
if ($RCI->failures) {
echo '<p class="warning">Sorry but your webserver does not meet the requirements for Roundcube!<br />
Please install the missing modules or fix the php.ini settings according to the above check results.<br />
Hint: only checks showing <span class="fail">NOT OK</span> need to be fixed.</p>';
}
echo '<p><br /><input type="submit" value="NEXT" ' . ($RCI->failures ? 'disabled' : '') . ' /></p>';
?>
</form>
+48
View File
@@ -0,0 +1,48 @@
/*
+-----------------------------------------------------------------------+
| Roundcube installer client function |
| |
| 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. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
function toggleblock(id, link)
{
var block = document.getElementById(id);
return false;
}
function addhostfield()
{
var container = document.getElementById('defaulthostlist');
var row = document.createElement('div');
var input = document.createElement('input');
var link = document.createElement('a');
input.name = '_imap_host[]';
input.size = '30';
link.href = '#';
link.onclick = function() { removehostfield(this.parentNode); return false };
link.className = 'removelink';
link.innerHTML = 'remove';
row.appendChild(input);
row.appendChild(link);
container.appendChild(row);
}
function removehostfield(row)
{
var container = document.getElementById('defaulthostlist');
container.removeChild(row);
}
+647
View File
@@ -0,0 +1,647 @@
<?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. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
if (!class_exists('rcmail_install', false) || !isset($RCI)) {
die("Not allowed! Please open installer/index.php instead.");
}
// allow the current user to get to the next step
$_SESSION['allowinstaller'] = true;
if (!empty($_POST['submit'])) {
$_SESSION['config'] = $RCI->create_config();
if ($RCI->save_configfile($_SESSION['config'])) {
echo '<p class="notice">The config file was saved successfully into'
. ' <tt>'.RCMAIL_CONFIG_DIR.'</tt> directory of your Roundcube installation.';
if ($RCI->legacy_config) {
echo '<br/><br/>Afterwards, please <b>remove</b> the old configuration files'
. ' <tt>main.inc.php</tt> and <tt>db.inc.php</tt> from the config directory.';
}
echo '</p>';
}
else {
$save_button = '';
if (($dir = sys_get_temp_dir()) && @is_writable($dir)) {
echo '<iframe name="getconfig" style="display:none"></iframe>';
echo '<form id="getconfig_form" action="index.php" method="get" target="getconfig" style="display:none">';
echo '<input name="_getconfig" value="2" /></form>';
$button_txt = html::quote('Save in ' . $dir);
$save_button = '&nbsp;<input type="button" onclick="document.getElementById(\'getconfig_form\').submit()" value="' . $button_txt . '" />';
}
echo '<p class="notice">Copy or download the following configuration and save it';
echo ' as <tt><b>config.inc.php</b></tt> within the <tt>'.RCUBE_CONFIG_DIR.'</tt> directory of your Roundcube installation.<br/>';
echo ' Make sure that there are no characters before the <tt>&lt;?php</tt> bracket when saving the file.';
echo '&nbsp;<input type="button" onclick="location.href=\'index.php?_getconfig=1\'" value="Download" />';
echo $save_button;
if ($RCI->legacy_config) {
echo '<br/><br/>Afterwards, please <b>remove</b> the old configuration files'
. ' <tt>main.inc.php</tt> and <tt>db.inc.php</tt> from the config directory.';
}
echo '</p>';
$textbox = new html_textarea(['rows' => 16, 'cols' => 60, 'class' => 'configfile']);
echo $textbox->show(($_SESSION['config']));
}
echo '<p class="hint">Of course there are more options to configure.
Have a look at the defaults.inc.php file or visit <a href="https://github.com/roundcube/roundcubemail/wiki/Configuration" target="_blank">Howto_Config</a> to find out.</p>';
echo '<p><input type="button" onclick="location.href=\'./index.php?_step=3\'" value="CONTINUE" /></p>';
// echo '<style type="text/css"> .configblock { display:none } </style>';
echo "\n<hr style='margin-bottom:1.6em' />\n";
}
?>
<form action="index.php" method="post">
<input type="hidden" name="_step" value="2" />
<fieldset>
<legend>General configuration</legend>
<dl class="configblock">
<dt class="propname">product_name</dt>
<dd>
<?php
$input_prodname = new html_inputfield(['name' => '_product_name', 'size' => 30, 'id' => 'cfgprodname']);
echo $input_prodname->show($RCI->getprop('product_name'));
?>
<div>The name of your service (used to compose page titles)</div>
</dd>
<dt class="propname">support_url</dt>
<dd>
<?php
$input_support = new html_inputfield(['name' => '_support_url', 'size' => 50, 'id' => 'cfgsupporturl']);
echo $input_support->show($RCI->getprop('support_url'));
?>
<div>Provide a URL where a user can get support for this Roundcube installation.<br/>PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!</div>
<p class="hint">Enter an absolute URL (including http://) to a support page/form or a mailto: link.</p>
</dd>
<dt class="propname">temp_dir</dt>
<dd>
<?php
$input_tempdir = new html_inputfield(['name' => '_temp_dir', 'size' => 30, 'id' => 'cfgtempdir']);
echo $input_tempdir->show($RCI->getprop('temp_dir'));
?>
<div>Use this folder to store temp files (must be writeable for webserver)</div>
</dd>
<dt class="propname">des_key</dt>
<dd>
<?php
$input_deskey = new html_inputfield(['name' => '_des_key', 'size' => 30, 'id' => 'cfgdeskey']);
echo $input_deskey->show($RCI->getprop('des_key'));
?>
<div>This key is used to encrypt the users imap password before storing in the session record</div>
<p class="hint">It's a random generated string to ensure that every installation has its own key.</p>
</dd>
<dt class="propname">ip_check</dt>
<dd>
<?php
$check_ipcheck = new html_checkbox(['name' => '_ip_check', 'id' => 'cfgipcheck']);
echo $check_ipcheck->show(intval($RCI->getprop('ip_check')), array('value' => 1));
?>
<label for="cfgipcheck">Check client IP in session authorization</label><br />
<p class="hint">This increases security but can cause sudden logouts when someone uses a proxy with changing IPs.</p>
</dd>
<dt class="propname">enable_spellcheck</dt>
<dd>
<?php
$check_spell = new html_checkbox(['name' => '_enable_spellcheck', 'id' => 'cfgspellcheck']);
echo $check_spell->show(intval($RCI->getprop('enable_spellcheck')), ['value' => 1]);
?>
<label for="cfgspellcheck">Make use of the spell checker</label><br />
</dd>
<dt class="propname">spellcheck_engine</dt>
<dd>
<?php
$select_spell = new html_select(['name' => '_spellcheck_engine', 'id' => 'cfgspellcheckengine']);
if (extension_loaded('pspell')) {
$select_spell->add('Pspell', 'pspell');
}
if (extension_loaded('enchant')) {
$select_spell->add('Enchant', 'enchant');
}
$select_spell->add('Googie', 'googie');
$select_spell->add('ATD', 'atd');
echo $select_spell->show($RCI->is_post ? $_POST['_spellcheck_engine'] : 'pspell');
?>
<div>Which spell checker to use</div>
<p class="hint">Googie implies that the message content will be sent to external server to check the spelling.</p>
</dd>
<dt class="propname">identities_level</dt>
<dd>
<?php
$input_ilevel = new html_select(['name' => '_identities_level', 'id' => 'cfgidentitieslevel']);
$input_ilevel->add('many identities with possibility to edit all params', 0);
$input_ilevel->add('many identities with possibility to edit all params but not email address', 1);
$input_ilevel->add('one identity with possibility to edit all params', 2);
$input_ilevel->add('one identity with possibility to edit all params but not email address', 3);
$input_ilevel->add('one identity with possibility to edit only signature', 4);
echo $input_ilevel->show($RCI->getprop('identities_level'), 0);
?>
<div>Level of identities access</div>
<p class="hint">Defines what users can do with their identities.</p>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Logging & Debugging</legend>
<dl class="loggingblock">
<dt class="propname">log_driver</dt>
<dd>
<?php
$select_log_driver = new html_select(['name' => '_log_driver', 'id' => 'cfglogdriver']);
$select_log_driver->add(['file', 'syslog', 'stdout'], ['file', 'syslog', 'stdout']);
echo $select_log_driver->show($RCI->getprop('log_driver', 'file'));
?>
<div>How to do logging? 'file' - write to files in the log directory, 'syslog' - use the syslog facility, 'stdout' writes to the process' STDOUT file descriptor.</div>
</dd>
<dt class="propname">log_dir</dt>
<dd>
<?php
$input_logdir = new html_inputfield(['name' => '_log_dir', 'size' => 30, 'id' => 'cfglogdir']);
echo $input_logdir->show($RCI->getprop('log_dir'));
?>
<div>Use this folder to store log files (must be writeable for webserver). Note that this only applies if you are using the 'file' log_driver.</div>
</dd>
<dt class="propname">syslog_id</dt>
<dd>
<?php
$input_syslogid = new html_inputfield(['name' => '_syslog_id', 'size' => 30, 'id' => 'cfgsyslogid']);
echo $input_syslogid->show($RCI->getprop('syslog_id', 'roundcube'));
?>
<div>What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.</div>
</dd>
<dt class="propname">syslog_facility</dt>
<dd>
<?php
$input_syslogfacility = new html_select(['name' => '_syslog_facility', 'id' => 'cfgsyslogfacility']);
$input_syslogfacility->add('user-level messages', LOG_USER);
if (defined('LOG_MAIL')) {
$input_syslogfacility->add('mail subsystem', LOG_MAIL);
}
if (defined('LOG_LOCAL0')) {
$input_syslogfacility->add('local level 0', LOG_LOCAL0);
$input_syslogfacility->add('local level 1', LOG_LOCAL1);
$input_syslogfacility->add('local level 2', LOG_LOCAL2);
$input_syslogfacility->add('local level 3', LOG_LOCAL3);
$input_syslogfacility->add('local level 4', LOG_LOCAL4);
$input_syslogfacility->add('local level 5', LOG_LOCAL5);
$input_syslogfacility->add('local level 6', LOG_LOCAL6);
$input_syslogfacility->add('local level 7', LOG_LOCAL7);
}
echo $input_syslogfacility->show($RCI->getprop('syslog_facility'), LOG_USER);
?>
<div>What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.</div>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Database setup</legend>
<dl class="configblock" id="cgfblockdb">
<dt class="propname">db_dsnw</dt>
<dd>
<p>Database settings for read/write operations:</p>
<?php
$select_dbtype = new html_select(['name' => '_dbtype', 'id' => 'cfgdbtype']);
foreach ($RCI->supported_dbs as $database => $ext) {
if (extension_loaded($ext)) {
$select_dbtype->add($database, substr($ext, 4));
}
}
$input_dbhost = new html_inputfield(['name' => '_dbhost', 'size' => 20, 'id' => 'cfgdbhos']);
$input_dbname = new html_inputfield(['name' => '_dbname', 'size' => 20, 'id' => 'cfgdbname']);
$input_dbuser = new html_inputfield(['name' => '_dbuser', 'size' => 20, 'id' => 'cfgdbuser']);
$input_dbpass = new html_inputfield(['name' => '_dbpass', 'size' => 20, 'id' => 'cfgdbpass']);
$dsnw = rcube_db::parse_dsn($RCI->getprop('db_dsnw'));
echo $select_dbtype->show($RCI->is_post ? $_POST['_dbtype'] : ($dsnw['phptype'] ?? ''));
echo '<label for="cfgdbtype">Database type</label><br />';
echo $input_dbhost->show($RCI->is_post ? $_POST['_dbhost'] : ($dsnw['hostspec'] ?? ''));
echo '<label for="cfgdbhost">Database server (omit for sqlite)</label><br />';
echo $input_dbname->show($RCI->is_post ? $_POST['_dbname'] : ($dsnw['database'] ?? ''));
echo '<label for="cfgdbname">Database name (use absolute path and filename for sqlite)</label><br />';
echo $input_dbuser->show($RCI->is_post ? $_POST['_dbuser'] : ($dsnw['username'] ?? ''));
echo '<label for="cfgdbuser">Database user name (needs write permissions) (omit for sqlite)</label><br />';
echo $input_dbpass->show($RCI->is_post ? $_POST['_dbpass'] : ($dsnw['password'] ?? ''));
echo '<label for="cfgdbpass">Database password (omit for sqlite)</label><br />';
?>
</dd>
<dt class="propname">db_prefix</dt>
<dd>
<?php
$input_prefix = new html_inputfield(['name' => '_db_prefix', 'size' => 20, 'id' => 'cfgdbprefix']);
echo $input_prefix->show($RCI->getprop('db_prefix'));
?>
<div>Optional prefix that will be added to database object names (tables and sequences).</div>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>IMAP Settings</legend>
<dl class="configblock" id="cgfblockimap">
<dt class="propname">imap_host</dt>
<dd>
<div id="defaulthostlist">
<?php
$text_imaphost = new html_inputfield(['name' => '_imap_host[]', 'size' => 30]);
$default_hosts = $RCI->get_hostlist();
if (empty($default_hosts)) {
$default_hosts = [''];
}
$i = 0;
foreach ($default_hosts as $host) {
echo '<div id="defaulthostentry'.$i.'">' . $text_imaphost->show($host);
if ($i++ > 0) {
echo '<a href="#" onclick="removehostfield(this.parentNode);return false" class="removelink" title="Remove this entry">remove</a>';
}
echo '</div>';
}
?>
</div>
<div><a href="javascript:addhostfield()" class="addlink" title="Add another field">add</a></div>
<div>The IMAP host(s) chosen to perform the log-in</div>
<p class="hint">Leave blank to show a textbox at login. To use SSL/STARTTLS connection add ssl:// or tls:// prefix. It can also contain the port number, e.g. tls://imap.domain.tld:143.
</dd>
<dt class="propname">username_domain</dt>
<dd>
<?php
$text_userdomain = new html_inputfield(['name' => '_username_domain', 'size' => 30, 'id' => 'cfguserdomain']);
echo $text_userdomain->show($RCI->getprop('username_domain'));
?>
<div>Automatically add this domain to user names for login</div>
<p class="hint">Only for IMAP servers that require full e-mail addresses for login</p>
</dd>
<dt class="propname">auto_create_user</dt>
<dd>
<?php
$check_autocreate = new html_checkbox(['name' => '_auto_create_user', 'id' => 'cfgautocreate']);
echo $check_autocreate->show(intval($RCI->getprop('auto_create_user')), ['value' => 1]);
?>
<label for="cfgautocreate">Automatically create a new Roundcube user when log-in the first time</label><br />
<p class="hint">A user is authenticated by the IMAP server but it requires a local record to store settings
and contacts. With this option enabled a new user record will automatically be created once the IMAP login succeeds.</p>
<p class="hint">If this option is disabled, the login only succeeds if there's a matching user-record in the local Roundcube database
what means that you have to create those records manually or disable this option after the first login.</p>
</dd>
<dt class="propname">sent_mbox</dt>
<dd>
<?php
$text_sentmbox = new html_inputfield(['name' => '_sent_mbox', 'size' => 20, 'id' => 'cfgsentmbox']);
echo $text_sentmbox->show($RCI->getprop('sent_mbox'));
?>
<div>Store sent messages in this folder</div>
<p class="hint">Leave blank if sent messages should not be stored. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">trash_mbox</dt>
<dd>
<?php
$text_trashmbox = new html_inputfield(['name' => '_trash_mbox', 'size' => 20, 'id' => 'cfgtrashmbox']);
echo $text_trashmbox->show($RCI->getprop('trash_mbox'));
?>
<div>Move messages to this folder when deleting them</div>
<p class="hint">Leave blank if they should be deleted directly. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">drafts_mbox</dt>
<dd>
<?php
$text_draftsmbox = new html_inputfield(['name' => '_drafts_mbox', 'size' => 20, 'id' => 'cfgdraftsmbox']);
echo $text_draftsmbox->show($RCI->getprop('drafts_mbox'));
?>
<div>Store draft messages in this folder</div>
<p class="hint">Leave blank if they should not be stored. Note: folder must include namespace prefix if any.</p>
</dd>
<dt class="propname">junk_mbox</dt>
<dd>
<?php
$text_junkmbox = new html_inputfield(['name' => '_junk_mbox', 'size' => 20, 'id' => 'cfgjunkmbox']);
echo $text_junkmbox->show($RCI->getprop('junk_mbox'));
?>
<div>Store spam messages in this folder</div>
<p class="hint">Note: folder must include namespace prefix if any.</p>
</dd>
</dd>
</dl>
</fieldset>
<fieldset>
<legend>SMTP Settings</legend>
<dl class="configblock" id="cgfblocksmtp">
<dt class="propname">smtp_host</dt>
<dd>
<?php
$text_smtphost = new html_inputfield(['name' => '_smtp_host', 'size' => 30, 'id' => 'cfgsmtphost']);
echo $text_smtphost->show($RCI->getprop('smtp_host', 'localhost:587'));
?>
<div>Use this host for sending mails</div>
<p class="hint">To use SSL/STARTTLS connection add ssl:// or tls:// prefix. It can also contain the port number, e.g. tls://smtp.domain.tld:587.</p>
</dd>
<dt class="propname">smtp_user/smtp_pass</dt>
<dd>
<?php
$text_smtpuser = new html_inputfield(['name' => '_smtp_user', 'size' => 20, 'id' => 'cfgsmtpuser']);
$text_smtppass = new html_inputfield(['name' => '_smtp_pass', 'size' => 20, 'id' => 'cfgsmtppass']);
echo $text_smtpuser->show($RCI->getprop('smtp_user'));
echo $text_smtppass->show($RCI->getprop('smtp_pass'));
?>
<div>SMTP username and password (if required)</div>
<p>
<?php
$check_smtpuser = new html_checkbox(['name' => '_smtp_user_u', 'id' => 'cfgsmtpuseru']);
echo $check_smtpuser->show($RCI->getprop('smtp_user') == '%u' || !empty($_POST['_smtp_user_u']) ? 1 : 0, ['value' => 1]);
?>
<label for="cfgsmtpuseru">Use the current IMAP username and password for SMTP authentication</label>
</p>
</dd>
<dt class="propname">smtp_log</dt>
<dd>
<?php
$check_smtplog = new html_checkbox(['name' => '_smtp_log', 'id' => 'cfgsmtplog']);
echo $check_smtplog->show(intval($RCI->getprop('smtp_log')), ['value' => 1]);
?>
<label for="cfgsmtplog">Log sent messages in <tt>{log_dir}/sendmail</tt> or to syslog.</label><br />
</dd>
</dl>
</fieldset>
<fieldset>
<legend>Display settings &amp; user prefs</legend>
<dl class="configblock" id="cgfblockdisplay">
<dt class="propname">language <span class="userconf">*</span></dt>
<dd>
<?php
$input_locale = new html_inputfield(['name' => '_language', 'size' => 6, 'id' => 'cfglocale']);
echo $input_locale->show($RCI->getprop('language'));
?>
<div>The default locale setting. This also defines the language of the login screen.<br/>Leave it empty to auto-detect the user agent language.</div>
<p class="hint">Enter a <a href="http://www.faqs.org/rfcs/rfc1766">RFC1766</a> formatted language name. Examples: en_US, de_DE, de_CH, fr_FR, pt_BR</p>
</dd>
<dt class="propname">skin <span class="userconf">*</span></dt>
<dd>
<?php
$input_skin = new html_select(['name' => '_skin', 'id' => 'cfgskin']);
$skins = $RCI->list_skins();
$input_skin->add($skins, $skins);
echo $input_skin->show($RCI->getprop('skin'));
?>
<div>Name of interface skin (folder in /skins)</div>
</dd>
<dt class="propname">mail_pagesize <span class="userconf">*</span></dt>
<dd>
<?php
$pagesize = $RCI->getprop('mail_pagesize');
if (!$pagesize) {
$pagesize = $RCI->getprop('pagesize');
}
$input_pagesize = new html_inputfield(['name' => '_mail_pagesize', 'size' => 6, 'id' => 'cfgmailpagesize']);
echo $input_pagesize->show($pagesize);
?>
<div>Show up to X items in the mail messages list view.</div>
</dd>
<dt class="propname">addressbook_pagesize <span class="userconf">*</span></dt>
<dd>
<?php
$pagesize = $RCI->getprop('addressbook_pagesize');
if (!$pagesize) {
$pagesize = $RCI->getprop('pagesize');
}
$input_pagesize = new html_inputfield(['name' => '_addressbook_pagesize', 'size' => 6, 'id' => 'cfgabookpagesize']);
echo $input_pagesize->show($pagesize);
?>
<div>Show up to X items in the contacts list view.</div>
</dd>
<dt class="propname">prefer_html <span class="userconf">*</span></dt>
<dd>
<?php
$check_htmlview = new html_checkbox(['name' => '_prefer_html', 'id' => 'cfghtmlview', 'value' => 1]);
echo $check_htmlview->show(intval($RCI->getprop('prefer_html')));
?>
<label for="cfghtmlview">Prefer displaying HTML messages</label><br />
</dd>
<dt class="propname">htmleditor <span class="userconf">*</span></dt>
<dd>
<label for="cfghtmlcompose">Compose HTML formatted messages</label>
<?php
$select_htmlcomp = new html_select(['name' => '_htmleditor', 'id' => 'cfghtmlcompose']);
$select_htmlcomp->add('never', 0);
$select_htmlcomp->add('always', 1);
$select_htmlcomp->add('on reply to HTML message only', 2);
echo $select_htmlcomp->show(intval($RCI->getprop('htmleditor')));
?>
</dd>
<dt class="propname">draft_autosave <span class="userconf">*</span></dt>
<dd>
<label for="cfgautosave">Save compose message every</label>
<?php
$select_autosave = new html_select(['name' => '_draft_autosave', 'id' => 'cfgautosave']);
$select_autosave->add('never', 0);
foreach ([1, 3, 5, 10] as $i => $min) {
$select_autosave->add("$min min", $min * 60);
}
echo $select_autosave->show(intval($RCI->getprop('draft_autosave')));
?>
</dd>
<dt class="propname">mdn_requests <span class="userconf">*</span></dt>
<dd>
<?php
$mdn_opts = [
0 => 'ask the user',
1 => 'send automatically',
3 => 'send receipt to user contacts, otherwise ask the user',
4 => 'send receipt to user contacts, otherwise ignore',
2 => 'ignore',
];
$select_mdnreq = new html_select(['name' => '_mdn_requests', 'id' => 'cfgmdnreq']);
$select_mdnreq->add(array_values($mdn_opts), array_keys($mdn_opts));
echo $select_mdnreq->show(intval($RCI->getprop('mdn_requests')));
?>
<div>Behavior if a received message requests a message delivery notification (read receipt)</div>
</dd>
<dt class="propname">mime_param_folding <span class="userconf">*</span></dt>
<dd>
<?php
$select_param_folding = new html_select(['name' => '_mime_param_folding', 'id' => 'cfgmimeparamfolding']);
$select_param_folding->add('Full RFC 2231 (Roundcube, Thunderbird)', '0');
$select_param_folding->add('RFC 2047/2231 (MS Outlook, OE)', '1');
$select_param_folding->add('Full RFC 2047 (deprecated)', '2');
echo $select_param_folding->show(strval($RCI->getprop('mime_param_folding')));
?>
<div>How to encode attachment long/non-ascii names</div>
</dd>
</dl>
<p class="hint"><span class="userconf">*</span>&nbsp; These settings are defaults for the user preferences</p>
</fieldset>
<fieldset>
<legend>Plugins</legend>
<dl class="configblock" id="cgfblockdisplay">
<?php
$plugins = $RCI->list_plugins();
foreach ($plugins as $p) {
$p_check = new html_checkbox(['name' => '_plugins_'.$p['name'], 'id' => 'cfgplugin_'.$p['name'], 'value' => $p['name']]);
echo '<dt class="propname"><label>';
echo $p_check->show($p['enabled'] ? $p['name'] : 0);
echo '&nbsp;' . $p['name'] . '</label></dt><dd>';
echo '<label for="cfgplugin_'.$p['name'].'" class="hint">' . $p['desc'] . '</label><br/></dd>';
}
?>
</dl>
<p class="hint">Please consider checking dependencies of enabled plugins</p>
</fieldset>
<?php
echo '<p><input type="submit" name="submit" value="' . ($RCI->configured ? 'UPDATE' : 'CREATE') . ' CONFIG" ' . ($RCI->failures ? 'disabled' : '') . ' /></p>';
?>
</form>
Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

+173
View File
@@ -0,0 +1,173 @@
<?php
/**
+-------------------------------------------------------------------------+
| Roundcube Webmail setup tool |
| Version 1.6-git |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License (with exceptions |
| for skins & plugins) as published by the Free Software Foundation, |
| either version 3 of the License, or (at your option) any later version. |
| |
| This file forms part of the Roundcube Webmail Software for which the |
| following exception is added: Plugins and Skins which merely make |
| function calls to the Roundcube Webmail Software, and for that purpose |
| include it by reference shall not be considered modifications of |
| the software. |
| |
| If you wish to use this file in another project or create a modified |
| version that will not be part of the Roundcube Webmail Software, you |
| may remove the exception above and use this source code under the |
| original version of the license. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see http://www.gnu.org/licenses/. |
| |
+-------------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-------------------------------------------------------------------------+
*/
ini_set('display_errors', 1);
define('INSTALL_PATH', realpath(__DIR__ . '/../').'/');
require INSTALL_PATH . 'program/include/iniset.php';
if (function_exists('session_start')) {
session_start();
}
$RCI = rcmail_install::get_instance();
$RCI->load_config();
if (isset($_GET['_getconfig'])) {
$filename = 'config.inc.php';
if (!empty($_SESSION['config']) && $_GET['_getconfig'] == 2) {
$path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $filename;
@unlink($path);
file_put_contents($path, $_SESSION['config']);
exit;
}
if (!empty($_SESSION['config'])) {
header('Content-type: text/plain');
header('Content-Disposition: attachment; filename="'.$filename.'"');
echo $_SESSION['config'];
exit;
}
header('HTTP/1.0 404 Not found');
die("The requested configuration was not found. Please run the installer from the beginning.");
}
if (
$RCI->configured
&& !empty($_GET['_mergeconfig'])
&& ($RCI->getprop('enable_installer') || !empty($_SESSION['allowinstaller']))
) {
$filename = 'config.inc.php';
header('Content-type: text/plain');
header('Content-Disposition: attachment; filename="'.$filename.'"');
$RCI->merge_config();
echo $RCI->create_config();
exit;
}
// go to 'check env' step if we have a local configuration
if ($RCI->configured && empty($_REQUEST['_step'])) {
header("Location: ./?_step=1");
exit;
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Roundcube Webmail Installer</title>
<meta name="Robots" content="noindex,nofollow" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript" src="client.js"></script>
</head>
<body>
<div id="banner">
<div class="banner-bg"></div>
<div class="banner-logo"><a href="http://roundcube.net"><img src="images/roundcube_logo.png" width="210" height="55" border="0" alt="Roundcube - open source webmail software" /></a></div>
</div>
<div id="topnav">
<a href="https://github.com/roundcube/roundcubemail/wiki/Installation">How-to Wiki</a>
</div>
<div id="content">
<?php
// exit if installation is complete
if ($RCI->configured && !$RCI->getprop('enable_installer') && empty($_SESSION['allowinstaller'])) {
// header("HTTP/1.0 404 Not Found");
if ($RCI->configured && $RCI->legacy_config) {
echo '<h2 class="error">Your configuration needs to be migrated!</h2>';
echo '<p>We changed the configuration files structure and your installation needs to be updated accordingly.</p>';
echo '<p>Please run the <tt>bin/update.sh</tt> script from the command line or set <p>&nbsp; <tt>$rcube_config[\'enable_installer\'] = true;</tt></p>';
echo ' in your RCUBE_CONFIG_DIR/main.inc.php to let the installer help you migrating it.</p>';
}
else {
echo '<h2 class="error">The installer is disabled!</h2>';
echo '<p>To enable it again, set <tt>$config[\'enable_installer\'] = true;</tt> in RCUBE_CONFIG_DIR/config.inc.php</p>';
}
echo '</div></body></html>';
exit;
}
?>
<h1>Roundcube Webmail Installer</h1>
<ol id="progress">
<?php
$include_steps = [
1 => './check.php',
2 => './config.php',
3 => './test.php',
];
if (!in_array($RCI->step, array_keys($include_steps))) {
$RCI->step = 1;
}
foreach (['Check environment', 'Create config', 'Test config'] as $i => $item) {
$j = $i + 1;
$link = ($RCI->step >= $j || $RCI->configured) ? '<a href="./index.php?_step='.$j.'">' . rcube::Q($item) . '</a>' : rcube::Q($item);
printf('<li class="step%d%s">%s</li>', $j+1, $RCI->step > $j ? ' passed' : ($RCI->step == $j ? ' current' : ''), $link);
}
?>
</ol>
<?php
include $include_steps[$RCI->step];
?>
</div>
<div id="footer">
Installer by the Roundcube Dev Team. Copyright &copy; 2008-2021 Published under the GNU Public License;&nbsp;
Icons by <a href="http://famfamfam.com">famfamfam</a>
</div>
</body>
</html>
+240
View File
@@ -0,0 +1,240 @@
body {
background: white;
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: small;
color: black;
margin: 0;
}
#banner {
position: relative;
height: 58px;
margin: 0 0 1em 0;
padding: 10px 20px;
background: url('images/banner_gradient.gif') top left repeat-x #d8edfd;
overflow: hidden;
}
#banner .banner-bg {
position: absolute;
top: 0;
right: 0;
width: 630px;
height: 78px;
background: url('images/banner_schraffur.gif') top right no-repeat;
z-index: 0;
}
#banner .banner-logo {
position: absolute;
top: 10px;
left: 20px;
z-index: 4;
}
#banner .banner-logo a {
border: 0;
}
#topnav {
position: absolute;
top: 3.6em;
right: 20px;
}
#topnav a {
color: #666;
}
#content {
margin: 2em 20px;
}
#footer {
margin: 2em 20px 1em 20px;
padding-top: 0.6em;
font-size: smaller;
text-align: center;
border-top: 1px dotted #999;
}
#progress {
margin-bottom: 2em;
border: 1px solid #aaa;
background-color: #f9f9f9;
}
#progress:after {
content: ".";
display: block;
height: 0;
font-size: 0;
clear: both;
visibility: hidden;
}
#progress li {
float: left;
color: #999;
padding: 1em 5em 1em 0.2em;
}
#progress li a {
color: #999;
text-decoration: none;
}
#progress li a:hover {
text-decoration: underline;
}
#progress li.current {
color: #000;
font-weight: bold;
}
#progress li.passed,
#progress li.passed a,
#progress li.current a {
color: #333;
}
fieldset {
margin-bottom: 1.5em;
border: 1px solid #aaa;
background-color: #f9f9f9;
}
fieldset p.hint {
margin-top: 0.5em;
}
legend {
font-size: 1.1em;
font-weight: bold;
}
textarea.configfile {
background-color: #f9f9f9;
font-family: monospace;
font-size: 9pt;
width: 100%;
height: 30em;
}
.propname {
font-family: monospace;
font-size: 9pt;
margin-top: 1em;
margin-bottom: 0.6em;
}
dd div {
margin-top: 0.3em;
}
dd label {
padding-left: 0.5em;
}
th {
text-align: left;
}
td > label {
min-width: 6em;
display: inline-block;
}
ul li {
margin: 0.3em 0 0.4em -1em;
}
ul li ul li {
margin-bottom: 0.2em;
}
h3 {
font-size: 1.1em;
margin-top: 1.5em;
margin-bottom: 0.6em;
}
h4 {
margin-bottom: 0.2em;
}
a.blocktoggle {
color: #666;
text-decoration: none;
}
a.addlink {
color: #999;
font-size: 0.9em;
padding: 1px 0 1px 20px;
background: url('images/add.png') top left no-repeat;
text-decoration: none;
}
a.removelink {
color: #999;
font-size: 0.9em;
padding: 1px 0 1px 24px;
background: url('images/delete.png') 4px 0 no-repeat;
text-decoration: none;
}
.hint {
color: #666;
font-size: 0.95em;
}
.success {
color: #006400;
font-weight: bold !important;
}
.fail {
color: #ff0000 !important;
font-weight: bold !important;
}
.na {
color: #f60;
font-weight: bold;
}
.indent {
padding-left: 0.8em;
}
.notice {
padding: 1em;
background-color: #f7fdcb;
border: 2px solid #c2d071;
}
.suggestion {
padding: 0.6em;
background-color: #ebebeb;
border: 1px solid #999;
}
p.warning,
div.warning {
padding: 1em;
background-color: #ef9398;
border: 2px solid #dc5757;
}
h3.warning {
color: #c00;
background: url('images/error.png') top left no-repeat;
padding-left: 24px;
}
.userconf {
color: #00c;
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
}
+499
View File
@@ -0,0 +1,499 @@
<?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. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
if (!class_exists('rcmail_install', false) || !isset($RCI)) {
die("Not allowed! Please open installer/index.php instead.");
}
?>
<h3>Check config file</h3>
<?php
if ($read_config = is_readable(RCUBE_CONFIG_DIR . 'defaults.inc.php')) {
$config = $RCI->load_config_file(RCUBE_CONFIG_DIR . 'defaults.inc.php');
if (!empty($config)) {
$RCI->pass('defaults.inc.php');
}
else {
$RCI->fail('defaults.inc.php', 'Syntax error');
}
}
else {
$RCI->fail('defaults.inc.php', 'Unable to read default config file?');
}
echo '<br />';
if ($read_config = is_readable(RCUBE_CONFIG_DIR . 'config.inc.php')) {
$config = $RCI->load_config_file(RCUBE_CONFIG_DIR . 'config.inc.php');
if (!empty($config)) {
$RCI->pass('config.inc.php');
}
else {
$RCI->fail('config.inc.php', 'Syntax error');
}
}
else {
$RCI->fail('config.inc.php', 'Unable to read file. Did you create the config file?');
}
echo '<br />';
if ($RCI->configured && ($messages = $RCI->check_config())) {
if (is_array($messages['replaced'])) {
echo '<h3 class="warning">Replaced config options</h3>';
echo '<p class="hint">The following config options have been replaced or renamed. ';
echo 'Please update them accordingly in your config files.</p>';
echo '<ul class="configwarnings">';
foreach ($messages['replaced'] as $msg) {
echo html::tag('li', null, html::span('propname', $msg['prop']) .
' was replaced by ' . html::span('propname', $msg['replacement']));
}
echo '</ul>';
}
if (is_array($messages['obsolete'])) {
echo '<h3>Obsolete config options</h3>';
echo '<p class="hint">You still have some obsolete or inexistent properties set. This isn\'t a problem but should be noticed.</p>';
echo '<ul class="configwarnings">';
foreach ($messages['obsolete'] as $msg) {
echo html::tag('li', null, html::span('propname', $msg['prop'])
. (!empty($msg['explain']) ? ':&nbsp;' . $msg['explain'] : ''));
}
echo '</ul>';
}
echo '<p class="suggestion">OK, lazy people can download the updated config file here: ';
echo html::a(['href' => './?_mergeconfig=1'], 'config.inc.php') . ' &nbsp;';
echo "</p>";
if (is_array($messages['dependencies'])) {
echo '<h3 class="warning">Dependency check failed</h3>';
echo '<p class="hint">Some of your configuration settings require other options to be configured or additional PHP modules to be installed</p>';
echo '<ul class="configwarnings">';
foreach ($messages['dependencies'] as $msg) {
echo html::tag('li', null, html::span('propname', $msg['prop']) . ': ' . $msg['explain']);
}
echo '</ul>';
}
}
?>
<h3>Check if directories are writable</h3>
<p>Roundcube may need to write/save files into these directories</p>
<?php
$dirs[] = !empty($RCI->config['temp_dir']) ? $RCI->config['temp_dir'] : 'temp';
if ($RCI->config['log_driver'] != 'syslog') {
$dirs[] = $RCI->config['log_dir'] ? $RCI->config['log_dir'] : 'logs';
}
foreach ($dirs as $dir) {
$dirpath = rcube_utils::is_absolute_path($dir) ? $dir : INSTALL_PATH . $dir;
if (is_writable(realpath($dirpath))) {
$RCI->pass($dir);
$pass = true;
}
else {
$RCI->fail($dir, 'not writeable for the webserver');
}
echo '<br />';
}
if (empty($pass)) {
echo '<p class="hint">Use <tt>chmod</tt> or <tt>chown</tt> to grant write privileges to the webserver</p>';
}
?>
<h3>Check DB config</h3>
<?php
$db_working = false;
if ($RCI->configured) {
if (!empty($RCI->config['db_dsnw'])) {
$DB = rcube_db::factory($RCI->config['db_dsnw'], '', false);
$DB->set_debug((bool)$RCI->config['sql_debug']);
$DB->db_connect('w');
if (!($db_error_msg = $DB->is_error())) {
$RCI->pass('DSN (write)');
echo '<br />';
$db_working = true;
}
else {
$RCI->fail('DSN (write)', $db_error_msg);
echo '<p class="hint">Make sure that the configured database exists and that the user has write privileges<br />';
echo 'DSN: ' . rcube::Q($RCI->config['db_dsnw']) . '</p>';
}
}
else {
$RCI->fail('DSN (write)', 'not set');
}
}
else {
$RCI->fail('DSN (write)', 'Could not read config file');
}
// initialize db with schema found in /SQL/*
if ($db_working && !empty($_POST['initdb'])) {
if (!$RCI->init_db($DB)) {
$db_working = false;
echo '<p class="warning">Please try to initialize the database manually as described in the INSTALL guide.
Make sure that the configured database exists and that the user as write privileges</p>';
}
}
else if ($db_working && !empty($_POST['updatedb'])) {
if (!$RCI->update_db($_POST['version'])) {
echo '<p class="warning">Database schema update failed.</p>';
}
}
// test database
if ($db_working) {
$db_read = $DB->query("SELECT count(*) FROM " . $DB->quote_identifier($RCI->config['db_prefix'] . 'users'));
if ($DB->is_error()) {
$RCI->fail('DB Schema', "Database not initialized");
echo '<form action="index.php?_step=3" method="post">'
. '<p><input type="submit" name="initdb" value="Initialize database" /></p>'
. '</form>';
$db_working = false;
}
else if ($err = $RCI->db_schema_check($DB, $update = !empty($_POST['updatedb']))) {
$RCI->fail('DB Schema', "Database schema differs");
echo '<ul style="margin:0"><li>' . join("</li>\n<li>", $err) . "</li></ul>";
$select = $RCI->versions_select(['name' => 'version']);
$select->add('0.9 or newer', '');
echo '<form action="index.php?_step=3" method="post">'
. '<p class="suggestion">You should run the update queries to get the schema fixed.'
. '<br/><br/>Version to update from: ' . $select->show('')
. '&nbsp;<input type="submit" name="updatedb" value="Update" /></p>'
. '</form>';
$db_working = false;
}
else {
$RCI->pass('DB Schema');
echo '<br />';
}
}
// more database tests
if ($db_working) {
// Using transactions to workaround SQLite bug (#7064)
if ($DB->db_provider == 'sqlite') {
$DB->startTransaction();
}
// write test
$insert_id = md5(uniqid());
$db_write = $DB->query("INSERT INTO " . $DB->quote_identifier($RCI->config['db_prefix'] . 'session')
. " (`sess_id`, `changed`, `ip`, `vars`) VALUES (?, ".$DB->now().", '127.0.0.1', 'foo')", $insert_id);
if ($db_write) {
$RCI->pass('DB Write');
$DB->query("DELETE FROM " . $DB->quote_identifier($RCI->config['db_prefix'] . 'session')
. " WHERE `sess_id` = ?", $insert_id);
}
else {
$RCI->fail('DB Write', $RCI->get_error());
}
echo '<br />';
// Transaction end
if ($DB->db_provider == 'sqlite') {
$DB->rollbackTransaction();
}
// check timezone settings
$tz_db = 'SELECT ' . $DB->unixtimestamp($DB->now()) . ' AS tz_db';
$tz_db = $DB->query($tz_db);
$tz_db = $DB->fetch_assoc($tz_db);
$tz_db = (int) $tz_db['tz_db'];
$tz_local = (int) time();
$tz_diff = $tz_local - $tz_db;
// sometimes db and web servers are on separate hosts, so allow a 30 minutes delta
if (abs($tz_diff) > 1800) {
$RCI->fail('DB Time', "Database time differs {$tz_diff}s from PHP time");
}
else {
$RCI->pass('DB Time');
}
}
?>
<h3>Test filetype detection</h3>
<?php
if ($errors = $RCI->check_mime_detection()) {
$RCI->fail('Fileinfo/mime_content_type configuration');
if (!empty($RCI->config['mime_magic'])) {
echo '<p class="hint">Try setting the <tt>mime_magic</tt> config option to <tt>null</tt>.</p>';
}
else {
echo '<p class="hint">Check the <a href="http://www.php.net/manual/en/function.finfo-open.php">Fileinfo functions</a> of your PHP installation.<br/>';
echo 'The path to the magic.mime file can be set using the <tt>mime_magic</tt> config option in Roundcube.</p>';
}
}
else {
$RCI->pass('Fileinfo/mime_content_type configuration');
echo "<br/>";
}
if ($errors = $RCI->check_mime_extensions()) {
$RCI->fail('Mimetype to file extension mapping');
echo '<p class="hint">Please set a valid path to your webserver\'s mime.types file to the <tt>mime_types</tt> config option.<br/>';
echo 'If you can\'t find such a file, download it from <a href="http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types">svn.apache.org</a>.</p>';
}
else {
$RCI->pass('Mimetype to file extension mapping');
echo "<br/>";
}
$smtp_hosts = $RCI->get_hostlist('smtp_host');
if (!empty($smtp_hosts)) {
$smtp_host_field = new html_select(['name' => '_smtp_host', 'id' => 'smtp_host']);
$smtp_host_field->add($smtp_hosts, $smtp_hosts);
}
else {
$smtp_host_field = new html_inputfield(['name' => '_smtp_host', 'id' => 'smtp_host']);
}
$user = $RCI->getprop('smtp_user', '(none)');
$pass = $RCI->getprop('smtp_pass', '(none)');
if ($user == '%u') {
$user_field = new html_inputfield(['name' => '_smtp_user', 'id' => 'smtp_user']);
$user = $user_field->show(isset($_POST['_smtp_user']) ? $_POST['_smtp_user'] : '');
}
else {
$user = html::quote($user);
}
if ($pass == '%p') {
$pass_field = new html_passwordfield(['name' => '_smtp_pass', 'id' => 'smtp_pass']);
$pass = $pass_field->show();
}
else {
$pass = html::quote($pass);
}
?>
<form action="index.php?_step=3" method="post">
<h3>Test SMTP config</h3>
<p>
<table>
<tbody>
<tr>
<td><label for="smtp_host">Host</label></td>
<td><?php echo $smtp_host_field->show(isset($_POST['_smtp_host']) ? $_POST['_smtp_host'] : ''); ?></td>
</tr>
<tr>
<td><label for="smtp_user">Username</label></td>
<td><?php echo $user; ?></td>
</tr>
<tr>
<td><label for="smtp_pass">Password</label></td>
<td><?php echo $pass; ?></td>
</tr>
</tbody>
</table>
</p>
<?php
$from_field = new html_inputfield(['name' => '_from', 'id' => 'sendmailfrom']);
$to_field = new html_inputfield(['name' => '_to', 'id' => 'sendmailto']);
if (isset($_POST['sendmail'])) {
echo '<p>Trying to send email...<br />';
$smtp_host = trim($_POST['_smtp_host']);
$from = rcube_utils::idn_to_ascii(trim($_POST['_from']));
$to = rcube_utils::idn_to_ascii(trim($_POST['_to']));
if (
preg_match('/^' . $RCI->email_pattern . '$/i', $from)
&& preg_match('/^' . $RCI->email_pattern . '$/i', $to)
) {
$headers = [
'From' => $from,
'To' => $to,
'Subject' => 'Test message from Roundcube',
];
$body = 'This is a test to confirm that Roundcube can send email.';
// send mail using configured SMTP server
$CONFIG = $RCI->config;
if (!empty($_POST['_smtp_user'])) {
$CONFIG['smtp_user'] = $_POST['_smtp_user'];
}
if (!empty($_POST['_smtp_pass'])) {
$CONFIG['smtp_pass'] = $_POST['_smtp_pass'];
}
$mail_object = new Mail_mime();
$send_headers = $mail_object->headers($headers);
$head = $mail_object->txtHeaders($send_headers);
$SMTP = new rcube_smtp();
$SMTP->connect($smtp_host, null, $CONFIG['smtp_user'], $CONFIG['smtp_pass']);
$status = $SMTP->send_mail($headers['From'], $headers['To'], $head, $body);
$smtp_response = $SMTP->get_response();
if ($status) {
$RCI->pass('SMTP send');
}
else {
$RCI->fail('SMTP send', join('; ', $smtp_response));
}
}
else {
$RCI->fail('SMTP send', 'Invalid sender or recipient');
}
echo '</p>';
}
?>
<table>
<tbody>
<tr>
<td><label for="sendmailfrom">Sender</label></td>
<td><?php echo $from_field->show(isset($_POST['_from']) ? $_POST['_from'] : ''); ?></td>
</tr>
<tr>
<td><label for="sendmailto">Recipient</label></td>
<td><?php echo $to_field->show(isset($_POST['_to']) ? $_POST['_to'] : ''); ?></td>
</tr>
</tbody>
</table>
<p><input type="submit" name="sendmail" value="Send test mail" /></p>
</form>
<form action="index.php?_step=3" method="post">
<h3>Test IMAP config</h3>
<?php
$default_hosts = $RCI->get_hostlist();
if (!empty($default_hosts)) {
$host_field = new html_select(['name' => '_host', 'id' => 'imaphost']);
$host_field->add($default_hosts, $default_hosts);
}
else {
$host_field = new html_inputfield(['name' => '_host', 'id' => 'imaphost']);
}
$user_field = new html_inputfield(['name' => '_user', 'id' => 'imapuser']);
$pass_field = new html_passwordfield(['name' => '_pass', 'id' => 'imappass']);
?>
<table>
<tbody>
<tr>
<td><label for="imaphost">Host</label></td>
<td><?php echo $host_field->show(isset($_POST['_host']) ? $_POST['_host'] : ''); ?></td>
</tr>
<tr>
<td><label for="imapuser">Username</label></td>
<td><?php echo $user_field->show(isset($_POST['_user']) ? $_POST['_user'] : ''); ?></td>
</tr>
<tr>
<td><label for="imappass">Password</label></td>
<td><?php echo $pass_field->show(); ?></td>
</tr>
</tbody>
</table>
<?php
if (isset($_POST['imaptest']) && !empty($_POST['_host']) && !empty($_POST['_user'])) {
echo '<p>Connecting to ' . rcube::Q($_POST['_host']) . '...<br />';
$imap_host = trim($_POST['_host']);
$imap_port = 143;
$imap_ssl = false;
$a_host = parse_url($imap_host);
if ($a_host['host']) {
$imap_host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? $a_host['scheme'] : null;
$imap_port = $a_host['port'] ?? ($imap_ssl && $imap_ssl != 'tls' ? 993 : 143);
}
$imap_host = rcube_utils::idn_to_ascii($imap_host);
$imap_user = rcube_utils::idn_to_ascii($_POST['_user']);
$imap = new rcube_imap;
$imap->set_options([
'auth_type' => $RCI->getprop('imap_auth_type'),
'debug' => $RCI->getprop('imap_debug'),
'socket_options' => $RCI->getprop('imap_conn_options'),
]);
if ($imap->connect($imap_host, $imap_user, $_POST['_pass'], $imap_port, $imap_ssl)) {
$RCI->pass('IMAP connect', 'SORT capability: ' . ($imap->get_capability('SORT') ? 'yes' : 'no'));
$imap->close();
}
else {
$RCI->fail('IMAP connect', $RCI->get_error());
}
}
?>
<p><input type="submit" name="imaptest" value="Check login" /></p>
</form>
<hr />
<p class="warning">
After completing the installation and the final tests please <b>remove</b> the whole
installer folder from the document root of the webserver or make sure that
<tt>enable_installer</tt> option in <tt>config.inc.php</tt> is disabled.<br />
<br />
These files may expose sensitive configuration data like server passwords and encryption keys
to the public. Make sure you cannot access this installer from your browser.
</p>
+7
View File
@@ -0,0 +1,7 @@
# deny webserver access to this directory
<ifModule mod_authz_core.c>
Require all denied
</ifModule>
<ifModule !mod_authz_core.c>
Deny from all
</ifModule>
+11
View File
@@ -0,0 +1,11 @@
[02-Oct-2023 18:04:08 +0000]: <13n3u2rh> DB Error: SQLSTATE[HY000] [1045] Accès refusé pour l'utilisateur: 'roundcube'@'@localhost' (mot de passe: NON) in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_db.php on line 201 (GET /ruty/mails/)
[02-Oct-2023 18:24:21 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:24:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:33:55 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:44:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:45:00 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:45:14 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:45:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 18:48:29 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 19:02:57 +0000]: <13n3u2rh> IMAP Error: Login failed for root against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
[02-Oct-2023 19:11:53 +0000]: <13n3u2rh> IMAP Error: Login failed for whykorp@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion na pu être établie car lordinateur cible la expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login)
@@ -0,0 +1,150 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Copy a contact record from one directory to another |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_copy extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$cids = self::get_cids();
$target = rcube_utils::get_input_string('_to', rcube_utils::INPUT_POST);
$target_group = rcube_utils::get_input_string('_togid', rcube_utils::INPUT_POST);
$success = 0;
$errormsg = 'copyerror';
$maxnum = $rcmail->config->get('max_group_members', 0);
foreach ($cids as $source => $cid) {
// Something wrong, target not specified
if (!strlen($target)) {
break;
}
// It might happen when copying records from search result
// Do nothing, go to next source
if ((string) $target == (string) $source) {
continue;
}
$CONTACTS = $rcmail->get_address_book($source);
$TARGET = $rcmail->get_address_book($target);
if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
break;
}
$ids = [];
foreach ($cid as $cid) {
$a_record = $CONTACTS->get_record($cid, true);
// avoid copying groups
if (isset($a_record['_type']) && $a_record['_type'] == 'group') {
continue;
}
// Check if contact exists, if so, we'll need it's ID
// Note: Some addressbooks allows empty email address field
// @TODO: should we check all email addresses?
$email = $CONTACTS->get_col_values('email', $a_record, true);
if (!empty($email)) {
$result = $TARGET->search('email', $email[0], 1, true, true);
}
else if (!empty($a_record['name'])) {
$result = $TARGET->search('name', $a_record['name'], 1, true, true);
}
else {
$result = new rcube_result_set();
}
// insert contact record
if (!$result->count) {
$plugin = $rcmail->plugins->exec_hook('contact_create', [
'record' => $a_record,
'source' => $target,
'group' => $target_group
]);
if (!$plugin['abort']) {
if ($insert_id = $TARGET->insert($plugin['record'], false)) {
$ids[] = $insert_id;
$success++;
}
}
else if ($plugin['result']) {
$ids = array_merge($ids, $plugin['result']);
$success++;
}
}
else {
$record = $result->first();
$ids[] = $record['ID'];
$errormsg = empty($email) ? 'contactnameexists' : 'contactexists';
}
}
// assign to group
if ($target_group && $TARGET->groups && !empty($ids)) {
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
'group_id' => $target_group,
'ids' => $ids,
'source' => $target
]);
if (!$plugin['abort']) {
$TARGET->reset();
$TARGET->set_group($target_group);
if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
$rcmail->output->send();
}
if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success) {
$success = $cnt;
}
}
else if (!empty($plugin['result'])) {
$success = $plugin['result'];
}
$errormsg = !empty($plugin['message']) ? $plugin['message'] : 'copyerror';
}
}
if (!$success) {
$rcmail->output->show_message($errormsg, 'error');
}
else {
$rcmail->output->show_message('copysuccess', 'confirmation', ['nr' => $success]);
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,172 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Delete the submitted contacts (CIDs) from the users address book |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_delete extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$cids = self::get_cids(null, rcube_utils::INPUT_POST);
$delcnt = 0;
// remove previous deletes
$undo_time = $rcmail->config->get('undo_timeout', 0);
$rcmail->session->remove('contact_undo');
foreach ($cids as $source => $cid) {
$CONTACTS = self::contact_source($source);
if ($CONTACTS->readonly && empty($CONTACTS->deletable)) {
// more sources? do nothing, probably we have search results from
// more than one source, some of these sources can be readonly
if (count($cids) == 1) {
$rcmail->output->show_message('contactdelerror', 'error');
$rcmail->output->command('list_contacts');
$rcmail->output->send();
}
continue;
}
$plugin = $rcmail->plugins->exec_hook('contact_delete', [
'id' => $cid,
'source' => $source
]);
$deleted = !$plugin['abort'] ? $CONTACTS->delete($cid, $undo_time < 1) : $plugin['result'];
if (!$deleted) {
if (!empty($plugin['message'])) {
$error = $plugin['message'];
}
else if (($error = $CONTACTS->get_error()) && !empty($error['message'])) {
$error = $error['message'];
}
else {
$error = 'contactdelerror';
}
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GP);
$group = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP);
$rcmail->output->show_message($error, 'error');
$rcmail->output->command('list_contacts', $source, $group);
$rcmail->output->send();
}
else {
$delcnt += $deleted;
// store deleted contacts IDs in session for undo action
if ($undo_time > 0 && $CONTACTS->undelete) {
$_SESSION['contact_undo']['data'][$source] = $cid;
}
}
}
if (!empty($_SESSION['contact_undo'])) {
$_SESSION['contact_undo']['ts'] = time();
$msg = html::span(null, $rcmail->gettext('contactdeleted'))
. ' ' . html::a(
['onclick' => rcmail_output::JS_OBJECT_NAME.".command('undo', '', this)"],
$rcmail->gettext('undo')
);
$rcmail->output->show_message($msg, 'confirmation', null, true, $undo_time);
}
else {
$rcmail->output->show_message('contactdeleted', 'confirmation');
}
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$page = $_SESSION['page'] ?? 1;
// update saved search after data changed
if (($records = self::search_update(true)) !== false) {
// create resultset object
$count = count($records);
$first = ($page-1) * $page_size;
$result = new rcube_result_set($count, $first);
$pages = ceil((count($records) + $delcnt) / $page_size);
// last page and it's empty, display previous one
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
$rcmail->output->command('list_page', 'prev');
$rowcount = $rcmail->gettext('loading');
}
// get records from the next page to add to the list
else if ($pages > 1 && $page < $pages) {
// sort the records
ksort($records, SORT_LOCALE_STRING);
$first += $page_size;
// create resultset object
$res = new rcube_result_set($count, $first - $delcnt);
if ($page_size < $count) {
$records = array_slice($records, $first - $delcnt, $delcnt);
}
$res->records = array_values($records);
$records = $res;
}
else {
unset($records);
}
}
else if (isset($CONTACTS)) {
// count contacts for this user
$result = $CONTACTS->count();
$pages = ceil(($result->count + $delcnt) / $page_size);
// last page and it's empty, display previous one
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
$rcmail->output->command('list_page', 'prev');
$rowcount = $rcmail->gettext('loading');
}
// get records from the next page to add to the list
else if ($pages > 1 && $page < $pages) {
$CONTACTS->set_page($page);
$records = $CONTACTS->list_records(null, -$delcnt);
}
}
if (!isset($rowcount)) {
$rowcount = isset($result) ? self::get_rowcount_text($result) : '';
}
// update message count display
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 0);
$rcmail->output->command('set_rowcount', $rowcount);
// add new rows from next page (if any)
if (!empty($records)) {
self::js_contacts_list($records);
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,269 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Show edit form for a contact entry |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_edit extends rcmail_action_contacts_index
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
if ($rcmail->action == 'edit') {
// Get contact ID and source ID from request
$cids = self::get_cids();
$source = key($cids);
$cid = array_first($cids[$source]);
// Initialize addressbook
$CONTACTS = self::contact_source($source, true);
// Contact edit
if ($cid && (self::$contact = $CONTACTS->get_record($cid, true))) {
$rcmail->output->set_env('cid', self::$contact['ID']);
}
// editing not allowed here
if ($CONTACTS->readonly || !empty(self::$contact['readonly'])) {
$rcmail->output->show_message('sourceisreadonly');
$rcmail->overwrite_action('show');
return;
}
if (empty(self::$contact)) {
$rcmail->output->show_message('contactnotfound', 'error');
}
}
else {
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
if (strlen($source)) {
$CONTACTS = $rcmail->get_address_book($source, true);
}
if (empty($CONTACTS) || $CONTACTS->readonly) {
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
$source = $rcmail->get_address_book_id($CONTACTS);
}
// Initialize addressbook
$CONTACTS = self::contact_source($source, true);
}
self::$SOURCE_ID = $source;
self::$CONTACTS = $CONTACTS;
self::set_sourcename($CONTACTS);
// check if we have a valid result
if (!empty($args['contact'])) {
self::$contact = $args['contact'];
}
$rcmail->output->add_handlers([
'contactedithead' => [$this, 'contact_edithead'],
'contacteditform' => [$this, 'contact_editform'],
'contactphoto' => [$this, 'contact_photo'],
'photouploadform' => [$this, 'upload_photo_form'],
'sourceselector' => [$this, 'source_selector'],
'filedroparea' => [$this, 'photo_drop_area'],
]);
$rcmail->output->set_pagetitle($rcmail->gettext(($rcmail->action == 'add' ? 'addcontact' : 'editcontact')));
if ($rcmail->action == 'add' && $rcmail->output->template_exists('contactadd')) {
$rcmail->output->send('contactadd');
}
// this will be executed if no template for addcontact exists
$rcmail->output->send('contactedit');
}
public static function contact_edithead($attrib)
{
$rcmail = rcmail::get_instance();
$business_mode = $rcmail->config->get('contact_form_mode') === 'business';
// check if we have a valid result
$i_size = !empty($attrib['size']) ? $attrib['size'] : 20;
$form = [
'head' => [
'name' => $rcmail->gettext('contactnameandorg'),
'content' => [
'source' => ['id' => '_source', 'label' => $rcmail->gettext('addressbook')],
'prefix' => ['size' => $i_size],
'firstname' => ['size' => $i_size, 'visible' => true],
'middlename' => ['size' => $i_size],
'surname' => ['size' => $i_size, 'visible' => true],
'suffix' => ['size' => $i_size],
'name' => ['size' => $i_size * 2],
'nickname' => ['size' => $i_size * 2],
'organization' => ['size' => $i_size * 2, 'visible' => $business_mode],
'department' => ['size' => $i_size * 2, 'visible' => $business_mode],
'jobtitle' => ['size' => $i_size * 2, 'visible' => $business_mode],
]
]
];
list($form_start, $form_end) = self::get_form_tags($attrib);
unset($attrib['form'], $attrib['name'], $attrib['size']);
// return the address edit form
$out = self::contact_form($form, self::$contact, $attrib);
return $form_start . $out . $form_end;
}
public static function contact_editform($attrib)
{
$rcmail = rcmail::get_instance();
$addr_tpl = $rcmail->config->get('address_template', '');
// copy (parsed) address template to client
if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $addr_tpl, $templ, PREG_SET_ORDER)) {
$rcmail->output->set_env('address_template', $templ);
}
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10;
$t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
$form = [
'contact' => [
'name' => $rcmail->gettext('properties'),
'content' => [
'email' => ['size' => $i_size, 'maxlength' => 254, 'visible' => true],
'phone' => ['size' => $i_size, 'visible' => true],
'address' => ['visible' => true],
'website' => ['size' => $i_size],
'im' => ['size' => $i_size],
],
],
'personal' => [
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
'content' => [
'gender' => ['visible' => true],
'maidenname' => ['size' => $i_size],
'birthday' => ['visible' => true],
'anniversary' => [],
'manager' => ['size' => $i_size],
'assistant' => ['size' => $i_size],
'spouse' => ['size' => $i_size],
],
],
];
if (isset(self::$CONTACT_COLTYPES['notes'])) {
$form['notes'] = [
'name' => $rcmail->gettext('notes'),
'single' => true,
'content' => [
'notes' => ['size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1],
],
];
}
list($form_start, $form_end) = self::get_form_tags($attrib);
unset($attrib['form']);
// return the complete address edit form as table
$out = self::contact_form($form, self::$contact, $attrib);
return $form_start . $out . $form_end;
}
public static function upload_photo_form($attrib)
{
$rcmail = rcmail::get_instance();
$hidden = new html_hiddenfield(['name' => '_cid', 'value' => $rcmail->output->get_env('cid')]);
$attrib['prefix'] = $hidden->show();
$input_attr = ['name' => '_photo', 'accept' => 'image/*'];
$rcmail->output->add_label('addphoto','replacephoto');
return self::upload_form($attrib, 'uploadform', 'upload-photo', $input_attr);
}
/**
* similar function as in /steps/settings/edit_identity.inc
* @todo: Use rcmail_action::get_form_tags()
*/
public static function get_form_tags($attrib, $action = null, $id = null, $hidden = null)
{
static $edit_form;
$rcmail = rcmail::get_instance();
$form_start = $form_end = '';
if (empty($edit_form)) {
$hiddenfields = new html_hiddenfield();
if ($rcmail->action == 'edit') {
$hiddenfields->add(['name' => '_source', 'value' => self::$SOURCE_ID]);
}
$hiddenfields->add(['name' => '_gid', 'value' => self::$CONTACTS->group_id]);
$hiddenfields->add(['name' => '_search', 'value' => rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC)]);
if ($cid = $rcmail->output->get_env('cid')) {
$hiddenfields->add(['name' => '_cid', 'value' => $cid]);
}
$form_attrib = [
'name' => 'form',
'method' => 'post',
'task' => $rcmail->task,
'action' => 'save',
'request' => 'save.' . intval($cid),
'noclose' => true,
];
$form_start = $rcmail->output->request_form($form_attrib + $attrib, $hiddenfields->show());
$form_end = empty($attrib['form']) ? '</form>' : '';
$edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form';
$rcmail->output->add_gui_object('editform', $edit_form);
}
return [$form_start, $form_end];
}
/**
* Register container as active area to drop photos onto
*/
public static function photo_drop_area($attrib)
{
$rcmail = rcmail::get_instance();
if (!empty($attrib['id'])) {
$rcmail->output->add_gui_object('filedrop', $attrib['id']);
$rcmail->output->set_env('filedrop', [
'action' => 'upload-photo',
'fieldname' => '_photo',
'single' => 1,
'filter' => '^image/.+'
]);
}
}
}
@@ -0,0 +1,192 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Export the selected address book as vCard file |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_export extends rcmail_action_contacts_index
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$rcmail->request_security_check(rcube_utils::INPUT_GET);
$sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) {
$search = (array) $_SESSION['contact_search'][$_REQUEST['_search']];
$records = [];
// Get records from all sources
foreach ($search as $s => $set) {
$source = $rcmail->get_address_book($s);
// reset page
$source->set_page(1);
$source->set_pagesize(99999);
$source->set_search_set($set);
// get records
$result = $source->list_records();
while ($record = $result->next()) {
// because vcard_map is per-source we need to create vcard here
self::prepare_for_export($record, $source);
$record['sourceid'] = $s;
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
$records[$key] = $record;
}
unset($result);
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
$result->records = array_values($records);
}
// selected contacts
else if (!empty($_REQUEST['_cid'])) {
$records = [];
// Selected contact IDs (with multi-source support)
$cids = self::get_cids();
foreach ($cids as $s => $ids) {
$source = $rcmail->get_address_book($s);
// reset page and page size (#6103)
$source->set_page(1);
$source->set_pagesize(count($ids));
$result = $source->search('ID', $ids, 1, true, true);
while ($record = $result->next()) {
// because vcard_map is per-source we need to create vcard here
self::prepare_for_export($record, $source);
$record['sourceid'] = $s;
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
$records[$key] = $record;
}
}
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
$result->records = array_values($records);
}
// selected directory/group
else {
$CONTACTS = self::contact_source(null, true);
// get contacts for this user
$CONTACTS->set_page(1);
$CONTACTS->set_pagesize(99999);
$result = $CONTACTS->list_records(null, 0, true);
}
// Give plugins a possibility to implement other output formats or modify the result
$plugin = $rcmail->plugins->exec_hook('addressbook_export', ['result' => $result]);
$result = $plugin['result'];
if ($plugin['abort']) {
$rcmail->output->sendExit();
}
// send download headers
$rcmail->output->header('Content-Type: text/vcard; charset=' . RCUBE_CHARSET);
$rcmail->output->header('Content-Disposition: attachment; filename="contacts.vcf"');
while ($result && ($row = $result->next())) {
if (!empty($CONTACTS)) {
self::prepare_for_export($row, $CONTACTS);
}
// fix folding and end-of-line chars
$row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']);
$row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']);
echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol;
}
$rcmail->output->sendExit();
}
/**
* Copy contact record properties into a vcard object
*/
public static function prepare_for_export(&$record, $source = null)
{
$groups = $source && $source->groups && $source->export_groups ? $source->get_record_groups($record['ID']) : null;
$fieldmap = $source ? $source->vcard_map : null;
if (empty($record['vcard'])) {
$vcard = new rcube_vcard(null, RCUBE_CHARSET, false, $fieldmap);
$vcard->reset();
foreach ($record as $key => $values) {
list($field, $section) = rcube_utils::explode(':', $key);
// avoid unwanted casting of DateTime objects to an array
// (same as in rcube_contacts::convert_save_data())
if (is_object($values) && is_a($values, 'DateTime')) {
$values = [$values];
}
foreach ((array) $values as $value) {
if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) {
$vcard->set($field, $value, $section ? strtoupper($section) : '');
}
}
}
// append group names
if ($groups) {
$vcard->set('groups', join(',', $groups), null);
}
$record['vcard'] = $vcard->export();
}
// patch categories to already existing vcard block
else {
$vcard = new rcube_vcard($record['vcard'], RCUBE_CHARSET, false, $fieldmap);
// unset CATEGORIES entry, it might be not up-to-date (#1490277)
$vcard->set('groups', null);
$record['vcard'] = $vcard->export();
if (!empty($groups)) {
$vgroups = 'CATEGORIES:' . rcube_vcard::vcard_quote($groups, ',') . rcube_vcard::$eol;
$record['vcard'] = str_replace('END:VCARD', $vgroups . 'END:VCARD', $record['vcard']);
}
}
}
}
@@ -0,0 +1,86 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handle adding members to a contact group |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_group_addmembers extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$contacts = self::contact_source($source);
if ($contacts->readonly || !$contacts->groups) {
$rcmail->output->show_message('sourceisreadonly', 'warning');
$rcmail->output->send();
}
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST);
$ids = self::get_cids($source);
$result = false;
if ($gid && $ids) {
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
'group_id' => $gid,
'ids' => $ids,
'source' => $source,
]);
$contacts->set_group($gid);
$num2add = count($plugin['ids']);
if (empty($plugin['abort'])) {
if (
($maxnum = $rcmail->config->get('max_group_members'))
&& ($contacts->count()->count + $num2add > $maxnum)
) {
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
$rcmail->output->send();
}
$result = $contacts->add_to_group($gid, $plugin['ids']);
}
else {
$result = $plugin['result'];
}
}
if ($result) {
$rcmail->output->show_message('contactaddedtogroup', 'confirmation');
}
else if (!empty($plugin['abort']) || $contacts->get_error()) {
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error');
}
else {
$message = !empty($plugin['message']) ? $plugin['message'] : 'nogroupassignmentschanged';
$rcmail->output->show_message($message);
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,67 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| A handler for contact groups creation |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_group_create extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$contacts = self::contact_source($source);
if ($contacts->readonly || !$contacts->groups) {
$rcmail->output->show_message('sourceisreadonly', 'warning');
$rcmail->output->send();
}
if ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true))) {
$plugin = $rcmail->plugins->exec_hook('group_create', [
'name' => $name,
'source' => $source,
]);
if (empty($plugin['abort'])) {
$created = $contacts->create_group($plugin['name']);
}
else {
$created = $plugin['result'];
}
}
if (!empty($created)) {
$rcmail->output->show_message('groupcreated', 'confirmation');
$rcmail->output->command('insert_contact_group', ['source' => $source] + $created);
}
else if (empty($created)) {
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,67 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| A handler for contact group delete action |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_group_delete extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$contacts = self::contact_source($source);
if ($contacts->readonly || !$contacts->groups) {
$rcmail->output->show_message('sourceisreadonly', 'warning');
$rcmail->output->send();
}
if ($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST)) {
$plugin = $rcmail->plugins->exec_hook('group_delete', [
'group_id' => $gid,
'source' => $source,
]);
if (empty($plugin['abort'])) {
$deleted = $contacts->delete_group($gid);
}
else {
$deleted = $plugin['result'];
}
}
if (!empty($deleted)) {
$rcmail->output->show_message('groupdeleted', 'confirmation');
$rcmail->output->command('remove_group_item', ['source' => $source, 'id' => $gid]);
}
else {
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,71 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Removing members from a contact group |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_group_delmembers extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$contacts = self::contact_source($source);
if ($contacts->readonly || !$contacts->groups) {
$rcmail->output->show_message('sourceisreadonly', 'warning');
$rcmail->output->send();
}
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST);
$ids = self::get_cids($source);
if ($gid && $ids) {
$plugin = $rcmail->plugins->exec_hook('group_delmembers', [
'group_id' => $gid,
'ids' => $ids,
'source' => $source,
]);
if (empty($plugin['abort'])) {
$result = $contacts->remove_from_group($gid, $plugin['ids']);
}
else {
$result = $plugin['result'];
}
}
if (!empty($result)) {
$rcmail->output->show_message('contactremovedfromgroup', 'confirmation');
$rcmail->output->command('remove_group_contacts', ['source' => $source, 'gid' => $gid]);
}
else {
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,77 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| A handler for contact groups rename action |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_group_rename extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$contacts = self::contact_source($source);
if ($contacts->readonly || !$contacts->groups) {
$rcmail->output->show_message('sourceisreadonly', 'warning');
$rcmail->output->send();
}
if (
($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST))
&& ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true)))
) {
$newgid = null;
$plugin = $rcmail->plugins->exec_hook('group_rename', [
'group_id' => $gid,
'name' => $name,
'source' => $source,
]);
if (empty($plugin['abort'])) {
$newname = $contacts->rename_group($gid, $plugin['name'], $newgid);
}
else {
$newname = $plugin['result'];
}
}
if (!empty($newname)) {
$rcmail->output->show_message('grouprenamed', 'confirmation');
$rcmail->output->command('update_contact_group', [
'source' => $source,
'id' => $gid,
'name' => $newname,
'newid' => $newgid ?? null
]);
}
else {
$error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving';
$rcmail->output->show_message($error, 'error');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,486 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Import contacts from a vCard or CSV file |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_import extends rcmail_action_contacts_index
{
const UPLOAD_ERR_CSV_FIELDS = 101;
protected static $stats;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$importstep = 'import_form';
$has_map = isset($_POST['_map']) && is_array($_POST['_map']);
if ($has_map || (isset($_FILES['_file']) && is_array($_FILES['_file']))) {
$replace = (bool) rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC);
$target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC);
$with_groups = (int) rcube_utils::get_input_string('_groups', rcube_utils::INPUT_GPC);
// reload params for CSV field mapping screen
if ($has_map && !empty($_SESSION['contactcsvimport']['params'])) {
$params = $_SESSION['contactcsvimport']['params'];
$replace = $params['replace'];
$target = $params['target'];
$with_groups = $params['with_groups'];
}
$vcards = [];
$csvs = [];
$map = [];
$upload_error = null;
$CONTACTS = $rcmail->get_address_book($target, true);
if (!$CONTACTS->groups) {
$with_groups = false;
}
if ($CONTACTS->readonly) {
$rcmail->output->show_message('addresswriterror', 'error');
}
else {
$filepaths = [];
if ($has_map) {
$filepaths = $_SESSION['contactcsvimport']['files'];
}
else {
foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) {
// Process uploaded file if there is no error
$err = $_FILES['_file']['error'][$i];
if ($err) {
$upload_error = $err;
}
else {
$filepaths[] = $filepath;
}
}
}
foreach ($filepaths as $filepath) {
$file_content = file_get_contents($filepath);
// let rcube_vcard do the hard work :-)
$vcard_o = new rcube_vcard();
$vcard_o->extend_fieldmap($CONTACTS->vcard_map);
$v_list = $vcard_o->import($file_content);
if (!empty($v_list)) {
$vcards = array_merge($vcards, $v_list);
continue;
}
// no vCards found, try CSV
$csv = new rcube_csv2vcard($_SESSION['language']);
if ($has_map) {
$skip_head = isset($_POST['_skip_header']);
$map = rcube_utils::get_input_value('_map', rcube_utils::INPUT_GPC);
$map = array_filter($map);
$csv->set_map($map);
$csv->import($file_content, false, $skip_head);
unlink($filepath);
}
else {
// save uploaded file for the real import in the next step
$temp_csv = rcube_utils::temp_filename('csvimpt');
if (move_uploaded_file($filepath, $temp_csv) && file_exists($temp_csv)) {
$fields = $csv->get_fields();
$last_map = $map;
$map = $csv->import($file_content, true);
// when multiple CSV files are uploaded check they all have the same structure
if ($last_map && $last_map !== $map) {
$csvs = [];
$upload_error = self::UPLOAD_ERR_CSV_FIELDS;
break;
}
$csvs[] = $temp_csv;
}
else {
$upload_error = UPLOAD_ERR_CANT_WRITE;
}
continue;
}
$v_list = $csv->export();
if (!empty($v_list)) {
$vcards = array_merge($vcards, $v_list);
}
}
}
if (count($csvs) > 0) {
// csv import, show field mapping options
$importstep = 'import_map';
$_SESSION['contactcsvimport']['files'] = $csvs;
$_SESSION['contactcsvimport']['params'] = [
'replace' => $replace,
'target' => $target,
'with_groups' => $with_groups,
'fields' => !empty($fields) ? $fields : [],
];
// Stored separately due to nested array limitations in session
$_SESSION['contactcsvimport']['map'] = $map;
// Re-enable the import button
$rcmail->output->command('parent.import_state_set', 'error');
}
elseif (count($vcards) > 0) {
// import vcards
self::$stats = new stdClass;
self::$stats->names = [];
self::$stats->skipped_names = [];
self::$stats->count = count($vcards);
self::$stats->inserted = 0;
self::$stats->skipped = 0;
self::$stats->invalid = 0;
self::$stats->errors = 0;
if ($replace) {
$CONTACTS->delete_all($CONTACTS->groups && $with_groups < 2);
}
if ($with_groups) {
$import_groups = $CONTACTS->list_groups();
}
foreach ($vcards as $vcard) {
$a_record = $vcard->get_assoc();
// Generate contact's display name (must be before validation), the same we do in save.inc
if (empty($a_record['name'])) {
$a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
// Reset it if equals to email address (from compose_display_name())
if ($a_record['name'] == ($a_record['email'][0] ?? null)) {
$a_record['name'] = '';
}
}
// skip invalid (incomplete) entries
if (!$CONTACTS->validate($a_record, true)) {
self::$stats->invalid++;
continue;
}
// We're using UTF8 internally
$email = null;
if (isset($vcard->email[0])) {
$email = $vcard->email[0];
$email = rcube_utils::idn_to_utf8($email);
}
if (!$replace) {
$existing = null;
// compare e-mail address
if ($email) {
$existing = $CONTACTS->search('email', $email, 1, false);
}
// compare display name if email not found
if ((!$existing || !$existing->count) && $vcard->displayname) {
$existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
}
if ($existing && $existing->count) {
self::$stats->skipped++;
self::$stats->skipped_names[] = $vcard->displayname ?: $email;
continue;
}
}
$a_record['vcard'] = $vcard->export();
$plugin = $rcmail->plugins->exec_hook('contact_create', ['record' => $a_record, 'source' => null]);
$a_record = $plugin['record'];
// insert record and send response
if (empty($plugin['abort'])) {
$success = $CONTACTS->insert($a_record);
}
else {
$success = $plugin['result'];
}
if ($success) {
// assign groups for this contact (if enabled)
if ($with_groups && !empty($a_record['groups'])) {
foreach (explode(',', $a_record['groups'][0]) as $group_name) {
if ($group_id = self::import_group_id($group_name, $CONTACTS, $with_groups == 1, $import_groups)) {
$CONTACTS->add_to_group($group_id, $success);
}
}
}
self::$stats->inserted++;
self::$stats->names[] = $a_record['name'] ?: $email;
}
else {
self::$stats->errors++;
}
}
$importstep = 'import_confirm';
$_SESSION['contactcsvimport'] = null;
$rcmail->output->command('parent.import_state_set', self::$stats->inserted ? 'reload' : 'ok');
}
else {
if ($upload_error == self::UPLOAD_ERR_CSV_FIELDS) {
$rcmail->output->show_message('csvfilemismatch', 'error');
}
else {
self::upload_error($upload_error);
}
$rcmail->output->command('parent.import_state_set', 'error');
}
}
$rcmail->output->set_pagetitle($rcmail->gettext('importcontacts'));
$rcmail->output->add_handlers([
'importstep' => [$this, $importstep],
]);
// render page
if ($rcmail->output->template_exists('contactimport')) {
$rcmail->output->send('contactimport');
}
else {
$rcmail->output->send('importcontacts'); // deprecated
}
}
/**
* Handler function to display the import/upload form
*/
public static function import_form($attrib)
{
$rcmail = rcmail::get_instance();
$target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC);
$attrib += ['id' => 'rcmImportForm'];
$writable_books = $rcmail->get_address_sources(true, true);
$max_filesize = self::upload_init();
$form = '';
$hint = $rcmail->gettext(['id' => 'importfile', 'name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]);
$table = new html_table(['cols' => 2]);
$upload = new html_inputfield([
'type' => 'file',
'name' => '_file[]',
'id' => 'rcmimportfile',
'size' => 40,
'multiple' => 'multiple',
'class' => 'form-control-file',
]);
$table->add('title', html::label('rcmimportfile', $rcmail->gettext('importfromfile')));
$table->add(null, $upload->show() . html::div('hint', $hint));
// addressbook selector
if (count($writable_books) > 1) {
$select = new html_select([
'name' => '_target',
'id' => 'rcmimporttarget',
'is_escaped' => true,
'class' => 'custom-select'
]);
foreach ($writable_books as $book) {
$select->add($book['name'], $book['id']);
}
$table->add('title', html::label('rcmimporttarget', $rcmail->gettext('importtarget')));
$table->add(null, $select->show($target));
}
else {
$abook = new html_hiddenfield(['name' => '_target', 'value' => key($writable_books)]);
$form .= $abook->show();
}
$form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']);
// selector for group import options
if (count($writable_books) >= 1 || $writable_books[0]->groups) {
$select = new html_select([
'name' => '_groups',
'id' => 'rcmimportgroups',
'is_escaped' => true,
'class' => 'custom-select'
]);
$select->add($rcmail->gettext('none'), '0');
$select->add($rcmail->gettext('importgroupsall'), '1');
$select->add($rcmail->gettext('importgroupsexisting'), '2');
$table->add('title', html::label('rcmimportgroups', $rcmail->gettext('importgroups')));
$table->add(null, $select->show(rcube_utils::get_input_value('_groups', rcube_utils::INPUT_GPC)));
}
// checkbox to replace the entire address book
$check_replace = new html_checkbox(['name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace']);
$table->add('title', html::label('rcmimportreplace', $rcmail->gettext('importreplace')));
$table->add(null, $check_replace->show(rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC)));
$form .= $table->show(['id' => null] + $attrib);
// remove any info left over info from previous import attempts
$_SESSION['contactcsvimport'] = null;
$rcmail->output->set_env('writable_source', !empty($writable_books));
$rcmail->output->add_label('selectimportfile','importwait');
$rcmail->output->add_gui_object('importform', $attrib['id']);
$attrib = [
'action' => $rcmail->url('import'),
'method' => 'post',
'enctype' => 'multipart/form-data'
] + $attrib;
return html::p(null, rcube::Q($rcmail->gettext('importdesc'), 'show'))
. $rcmail->output->form_tag($attrib, $form);
}
/**
* Render the field mapping page for the CSV import process
*/
public static function import_map($attrib)
{
$rcmail = rcmail::get_instance();
$params = $_SESSION['contactcsvimport']['params'];
// hide groups field from list when group import disabled
if (empty($params['with_groups'])) {
unset($params['fields']['groups']);
}
$fieldlist = new html_select(['name' => '_map[]']);
$fieldlist->add($rcmail->gettext('fieldnotmapped'), '');
foreach ($params['fields'] as $id => $name) {
$fieldlist->add($name, $id);
}
$field_table = new html_table(['cols' => 2] + $attrib);
if ($classes = $attrib['table-header-class']) {
$field_table->set_header_attribs($classes);
}
$field_table->add_header($attrib['table-col-source-class'] ?: null, $rcmail->gettext('source'));
$field_table->add_header($attrib['table-col-destination-class'] ?: null, $rcmail->gettext('destination'));
$map = $_SESSION['contactcsvimport']['map'];
foreach ($map['source'] as $i => $name) {
$field_table->add('title', html::label('rcmimportmap' . $i, rcube::Q($name)));
$field_table->add(null, $fieldlist->show(array_key_exists($i, $map['destination']) ? $map['destination'][$i] : '', ['id' => 'rcmimportmap' . $i]));
}
$form = '';
$form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']);
// show option to import data from first line of the file
$check_header = new html_checkbox(['name' => '_skip_header', 'value' => 1, 'id' => 'rcmskipheader']);
$form .= html::p(null, html::label('rcmskipheader', $check_header->show(1) . $rcmail->gettext('skipheader')));
$form .= $field_table->show();
$attrib = ['action' => $rcmail->url('import'), 'method' => 'post'] + $attrib + ['id' => 'rcmImportFormMap'];
$rcmail->output->add_gui_object('importformmap', $attrib['id']);
return html::p(null, rcube::Q($rcmail->gettext('importmapdesc'), 'show'))
. $rcmail->output->form_tag($attrib, $form);
}
/**
* Render the confirmation page for the import process
*/
public static function import_confirm($attrib)
{
$rcmail = rcmail::get_instance();
$vars = get_object_vars(self::$stats);
$vars['names'] = $vars['skipped_names'] = '';
$content = html::p(null, $rcmail->gettext([
'name' => 'importconfirm',
'nr' => self::$stats->inserted,
'vars' => $vars,
]) . (self::$stats->names ? ':' : '.')
);
if (self::$stats->names) {
$content .= html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->names)));
}
if (self::$stats->skipped) {
$content .= html::p(null, $rcmail->gettext([
'name' => 'importconfirmskipped',
'nr' => self::$stats->skipped,
'vars' => $vars,
]) . ':')
. html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->skipped_names)));
}
return html::div($attrib, $content);
}
/**
* Returns the matching group id. If group doesn't exist, it'll be created if allowed.
*/
public static function import_group_id($group_name, $contacts, $create, &$import_groups)
{
$group_id = 0;
foreach ($import_groups as $group) {
if (strtolower($group['name']) === strtolower($group_name)) {
$group_id = $group['ID'];
break;
}
}
// create a new group
if (!$group_id && $create) {
$new_group = $contacts->create_group($group_name);
if (empty($new_group['ID'])) {
$new_group['ID'] = $new_group['id'];
}
$import_groups[] = $new_group;
$group_id = $new_group['ID'];
}
return $group_id;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Send contacts list to client (as remote response) |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_list extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
if (!empty($_GET['_page'])) {
$page = intval($_GET['_page']);
}
else {
$page = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
}
$_SESSION['page'] = $page;
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$group_data = null;
// Use search result
if (($records = self::search_update(true)) !== false) {
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$first = ($page-1) * $page_size;
$result = new rcube_result_set($count, $first);
// we need only records for current page
if ($page_size < $count) {
$records = array_slice($records, $first, $page_size);
}
$result->records = array_values($records);
}
// List selected directory
else {
$afields = $rcmail->config->get('contactlist_fields');
$contacts = self::contact_source(null, true);
// get contacts for this user
$result = $contacts->list_records($afields);
if (!$result->count && $result->searchonly) {
$rcmail->output->show_message('contactsearchonly', 'notice');
// Don't invoke advanced search dialog automatically from here (#6679)
}
if (!empty($contacts->group_id)) {
$group_data = ['ID' => $contacts->group_id]
+ array_intersect_key((array) $contacts->get_group($contacts->group_id), ['name' => 1,'email' => 1]);
}
}
$rcmail->output->command('set_group_prop', $group_data);
// update message count display
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
$rcmail->output->command('set_rowcount', self::get_rowcount_text($result));
// create javascript list
self::js_contacts_list($result);
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,91 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Compose a recipient list with all selected contacts |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_mailto extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$cids = self::get_cids();
$mailto = [];
$sources = [];
foreach ($cids as $source => $cid) {
$contacts = $rcmail->get_address_book($source);
if ($contacts->ready) {
$contacts->set_page(1);
$contacts->set_pagesize(count($cid) + 2); // +2 to skip counting query
$sources[] = $contacts->search($contacts->primary_key, $cid, 0, true, true, 'email');
}
}
if (!empty($_REQUEST['_gid']) && isset($_REQUEST['_source'])) {
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GP);
$group_id = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP);
$contacts = $rcmail->get_address_book($source);
$group_data = $contacts->get_group($group_id);
// group has an email address assigned: use that
if (!empty($group_data['email'])) {
$mailto[] = format_email_recipient($group_data['email'][0], $group_data['name']);
}
else if ($contacts->ready) {
$maxnum = (int) $rcmail->config->get('max_group_members');
$contacts->set_group($group_id);
$contacts->set_page(1);
$contacts->set_pagesize($maxnum ?: 999);
$sources[] = $contacts->list_records();
}
}
foreach ($sources as $source) {
while (is_object($source) && ($rec = $source->iterate())) {
$emails = rcube_addressbook::get_col_values('email', $rec, true);
if (!empty($emails)) {
$mailto[] = format_email_recipient($emails[0], $rec['name']);
}
}
}
if (!empty($mailto)) {
$mailto_str = join(', ', $mailto);
$mailto_id = substr(md5($mailto_str), 0, 16);
$_SESSION['mailto'][$mailto_id] = urlencode($mailto_str);
$rcmail->output->command('open_compose_step', ['_mailto' => $mailto_id]);
}
else {
$rcmail->output->show_message('nocontactsfound', 'warning');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,243 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Move a contact record from one directory to another |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_move extends rcmail_action_contacts_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$cids = self::get_cids();
$target = rcube_utils::get_input_string('_to', rcube_utils::INPUT_POST);
$target_group = rcube_utils::get_input_string('_togid', rcube_utils::INPUT_POST);
$rcmail = rcmail::get_instance();
$all = 0;
$deleted = 0;
$success = 0;
$errormsg = 'moveerror';
$maxnum = $rcmail->config->get('max_group_members', 0);
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$page = !empty($_SESSION['page']) ? $_SESSION['page'] : 1;
foreach ($cids as $source => $source_cids) {
// Something wrong, target not specified
if (!strlen($target)) {
break;
}
// It might happen when moving records from search result
// Do nothing, go to the next source
if ((string) $target === (string) $source) {
continue;
}
$CONTACTS = $rcmail->get_address_book($source);
$TARGET = $rcmail->get_address_book($target);
if (!$TARGET || !$TARGET->ready || $TARGET->readonly) {
break;
}
if (!$CONTACTS || !$CONTACTS->ready || ($CONTACTS->readonly && empty($CONTACTS->deletable))) {
continue;
}
$ids = [];
foreach ($source_cids as $idx => $cid) {
$record = $CONTACTS->get_record($cid, true);
// avoid moving groups
if (isset($record['_type']) && $record['_type'] == 'group') {
unset($source_cids[$idx]);
continue;
}
// Check if contact exists, if so, we'll need it's ID
// Note: Some addressbooks allows empty email address field
// @TODO: should we check all email addresses?
$email = $CONTACTS->get_col_values('email', $record, true);
if (!empty($email)) {
$result = $TARGET->search('email', $email[0], 1, true, true);
}
else if (!empty($record['name'])) {
$result = $TARGET->search('name', $record['name'], 1, true, true);
}
else {
$result = new rcube_result_set();
}
// insert contact record
if (!$result->count) {
$plugin = $rcmail->plugins->exec_hook('contact_create', [
'record' => $record,
'source' => $target,
'group' => $target_group
]);
if (empty($plugin['abort'])) {
if ($insert_id = $TARGET->insert($plugin['record'], false)) {
$ids[] = $insert_id;
$success++;
}
}
else if (!empty($plugin['result'])) {
$ids = array_merge($ids, $plugin['result']);
$success++;
}
}
else {
$record = $result->first();
$ids[] = $record['ID'];
$errormsg = empty($email) ? 'contactnameexists' : 'contactexists';
}
}
// remove source contacts
if ($success && !empty($source_cids)) {
$all += count($source_cids);
$plugin = $rcmail->plugins->exec_hook('contact_delete', [
'id' => $source_cids,
'source' => $source
]);
$del_status = !$plugin['abort'] ? $CONTACTS->delete($source_cids) : $plugin['result'];
if ($del_status) {
$deleted += $del_status;
}
}
// assign to group
if ($target_group && $TARGET->groups && !empty($ids)) {
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
'group_id' => $target_group,
'ids' => $ids,
'source' => $target
]);
if (empty($plugin['abort'])) {
$TARGET->reset();
$TARGET->set_group($target_group);
if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) {
$rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]);
$rcmail->output->send();
}
if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success) {
$success = $cnt;
}
}
else if ($plugin['result']) {
$success = $plugin['result'];
}
$errormsg = !empty($plugin['message']) ? $plugin['message'] : 'moveerror';
}
}
if (!$deleted || $deleted != $all) {
$rcmail->output->command('list_contacts');
}
else {
// update saved search after data changed
if (($records = self::search_update(true)) !== false) {
// create resultset object
$count = count($records);
$first = ($page-1) * $page_size;
$result = new rcube_result_set($count, $first);
$pages = ceil((count($records) + $deleted) / $page_size);
// last page and it's empty, display previous one
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
$rcmail->output->command('list_page', 'prev');
$rowcount = $rcmail->gettext('loading');
}
// get records from the next page to add to the list
else if ($pages > 1 && $page < $pages) {
// sort the records
ksort($records, SORT_LOCALE_STRING);
$first += $page_size;
// create resultset object
$res = new rcube_result_set($count, $first - $deleted);
if ($page_size < $count) {
$records = array_slice($records, $first - $deleted, $deleted);
}
$res->records = array_values($records);
$records = $res;
}
else {
unset($records);
}
}
else if (isset($CONTACTS)) {
// count contacts for this user
$result = $CONTACTS->count();
$pages = ceil(($result->count + $deleted) / $page_size);
// last page and it's empty, display previous one
if ($result->count && $result->count <= ($page_size * ($page - 1))) {
$rcmail->output->command('list_page', 'prev');
$rowcount = $rcmail->gettext('loading');
}
// get records from the next page to add to the list
else if ($pages > 1 && $page < $pages) {
$CONTACTS->set_page($page);
$records = $CONTACTS->list_records(null, -$deleted);
}
}
if (!isset($rowcount)) {
$rowcount = isset($result) ? self::get_rowcount_text($result) : 0;
}
// update message count display
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 0);
$rcmail->output->command('set_rowcount', $rowcount);
// add new rows from next page (if any)
if (!empty($records)) {
self::js_contacts_list($records);
}
}
if (!$success) {
$rcmail->output->show_message($errormsg, 'error');
}
else {
$rcmail->output->show_message('movesuccess', 'confirmation', ['nr' => $success]);
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,118 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Show contact photo |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_photo extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// Get contact ID and source ID from request
$cids = self::get_cids();
$source = key($cids);
$cid = $cids ? array_first($cids[$source]) : null;
$file_id = rcube_utils::get_input_string('_photo', rcube_utils::INPUT_GPC);
// read the referenced file
if ($file_id && !empty($_SESSION['contacts']['files'][$file_id])) {
$tempfile = $_SESSION['contacts']['files'][$file_id];
$tempfile = $rcmail->plugins->exec_hook('attachment_display', $tempfile);
if (!empty($tempfile['status'])) {
if (!empty($tempfile['data'])) {
$data = $tempfile['data'];
}
else if ($tempfile['path']) {
$data = file_get_contents($tempfile['path']);
}
}
}
else {
// by email, search for contact first
if ($email = rcube_utils::get_input_string('_email', rcube_utils::INPUT_GPC)) {
foreach ($rcmail->get_address_sources() as $s) {
$abook = $rcmail->get_address_book($s['id']);
$result = $abook->search(['email'], $email, 1, true, true, 'photo');
while ($result && ($record = $result->iterate())) {
if (!empty($record['photo'])) {
break 2;
}
}
}
}
// by contact id
if (empty($record) && $cid) {
// Initialize addressbook source
$CONTACTS = self::contact_source($source, true);
// read contact record
$record = $CONTACTS->get_record($cid, true);
}
if (!empty($record['photo'])) {
$data = is_array($record['photo']) ? $record['photo'][0] : $record['photo'];
if (!preg_match('![^a-z0-9/=+-]!i', $data)) {
$data = base64_decode($data, true);
}
}
}
// let plugins do fancy things with contact photos
$plugin = $rcmail->plugins->exec_hook('contact_photo', [
'record' => $record ?? null,
'email' => $email ?? null,
'data' => $data ?? null,
]);
// redirect to url provided by a plugin
if (!empty($plugin['url'])) {
$rcmail->output->redirect($plugin['url']);
}
$data = $plugin['data'];
// detect if photo data is a URL
if ($data && strlen($data) < 1024 && filter_var($data, FILTER_VALIDATE_URL)) {
$rcmail->output->redirect($data);
}
// cache for one day if requested by email
if (!$cid && !empty($email)) {
$rcmail->output->future_expire_header(86400);
}
if ($data) {
$rcmail->output->sendExit($data, ['Content-Type: ' . rcube_mime::image_content_type($data)]);
}
if (!empty($_GET['_error'])) {
$rcmail->output->sendExit('', ['HTTP/1.0 204 Photo not found']);
}
$rcmail->output->sendExit(base64_decode(rcmail_output::BLANK_GIF), ['Content-Type: image/gif']);
}
}
@@ -0,0 +1,147 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Print contact details |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_print extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// Get contact ID and source ID from request
$cids = self::get_cids();
$source = key($cids);
$cid = $cids ? array_first($cids[$source]) : null;
// Initialize addressbook source
self::$CONTACTS = self::contact_source($source, true);
self::$SOURCE_ID = $source;
// read contact record
if ($cid && self::$CONTACTS) {
self::$contact = self::$CONTACTS->get_record($cid, true);
}
$rcmail->output->add_handlers([
'contacthead' => [$this, 'contact_head'],
'contactdetails' => [$this, 'contact_details'],
'contactphoto' => [$this, 'contact_photo'],
]);
$rcmail->output->send('contactprint');
}
public static function contact_head($attrib)
{
$rcmail = rcmail::get_instance();
// check if we have a valid result
if (!self::$contact) {
$rcmail->output->show_message('contactnotfound', 'error');
return false;
}
$form = [
'head' => [ // section 'head' is magic!
'name' => $rcmail->gettext('contactnameandorg'),
'content' => [
'prefix' => [],
'name' => [],
'firstname' => [],
'middlename' => [],
'surname' => [],
'suffix' => [],
],
],
];
unset($attrib['name']);
return self::contact_form($form, self::$contact, $attrib);
}
public static function contact_details($attrib)
{
// check if we have a valid result
if (!self::$contact) {
return false;
}
$rcmail = rcmail::get_instance();
$form = [
'contact' => [
'name' => $rcmail->gettext('properties'),
'content' => [
'organization' => [],
'department' => [],
'jobtitle' => [],
'email' => [],
'phone' => [],
'address' => [],
'website' => [],
'im' => [],
'groups' => [],
],
],
'personal' => [
'name' => $rcmail->gettext('personalinfo'),
'content' => [
'nickname' => [],
'gender' => [],
'maidenname' => [],
'birthday' => [],
'anniversary' => [],
'manager' => [],
'assistant' => [],
'spouse' => [],
],
],
];
if (isset(rcmail_action_contacts_index::$CONTACT_COLTYPES['notes'])) {
$form['notes'] = [
'name' => $rcmail->gettext('notes'),
'content' => [
'notes' => ['type' => 'textarea', 'label' => false],
],
];
}
if (self::$CONTACTS->groups) {
$groups = self::$CONTACTS->get_record_groups(self::$contact['ID']);
if (!empty($groups)) {
$form['contact']['content']['groups'] = [
'value' => rcube::Q(implode(', ', $groups)),
'label' => $rcmail->gettext('groups')
];
}
}
return self::contact_form($form, self::$contact, $attrib);
}
}
@@ -0,0 +1,128 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Show contact data as QR code |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_qrcode extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
// Get contact ID and source ID from request
$cids = self::get_cids();
$source = key($cids);
$cid = $cids ? array_first($cids[$source]) : null;
$rcmail = rcmail::get_instance();
// read contact record
$abook = self::contact_source($source, true);
$contact = $abook->get_record($cid, true);
// generate QR code image
if ($data = self::contact_qrcode($contact)) {
$headers = [
'Content-Type: ' . self::check_support(),
'Content-Length: ' . strlen($data)
];
$rcmail->output->sendExit($data, $headers);
}
$rcmail->output->sendExit('', ['HTTP/1.0 404 Contact not found']);
}
/**
* Generate a QR-code image for a contact
*
* @param array $contact Contact record
*
* @return string|null Image content, Null on error or missing PHP extensions
*/
public static function contact_qrcode($contact)
{
if (empty($contact)) {
return null;
}
$type = self::check_support();
if (empty($type)) {
return null;
}
$vcard = new rcube_vcard();
// QR code input is limited, use only common fields
$fields = ['name', 'firstname', 'surname', 'middlename', 'nickname',
'organization', 'phone', 'email', 'jobtitle', 'prefix', 'suffix'];
foreach ($contact as $field => $value) {
if (strpos($field, ':') !== false) {
list($field, $section) = explode(':', $field, 2);
}
else {
$section = null;
}
if (in_array($field, $fields)) {
foreach ((array) $value as $v) {
$vcard->set($field, $v, $section);
}
}
}
$data = $vcard->export();
if (empty($data)) {
return null;
}
$renderer_style = new BaconQrCode\Renderer\RendererStyle\RendererStyle(300, 1);
$renderer_image = $type == 'image/png'
? new BaconQrCode\Renderer\Image\ImagickImageBackEnd()
: new BaconQrCode\Renderer\Image\SvgImageBackEnd();
$renderer = new BaconQrCode\Renderer\ImageRenderer($renderer_style, $renderer_image);
$writer = new BaconQrCode\Writer($renderer);
return $writer->writeString($data, RCUBE_CHARSET);
}
/**
* Check required extensions and classes for QR code generation
*
* @return string|null Content-type of the image result
*/
public static function check_support()
{
if (extension_loaded('iconv') && class_exists('BaconQrCode\Renderer\ImageRenderer')) {
if (extension_loaded('xmlwriter')) {
return 'image/svg+xml';
}
if (extension_loaded('imagick')) {
return 'image/png';
}
}
}
}
@@ -0,0 +1,323 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Save a contact entry or to add a new one |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_save extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$contacts = self::contact_source(null, true);
$cid = rcube_utils::get_input_string('_cid', rcube_utils::INPUT_POST);
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$return_action = empty($cid) ? 'add' : 'edit';
// Source changed, display the form again
if (!empty($_GET['_reload'])) {
$rcmail->overwrite_action($return_action);
return;
}
// cannot edit record
if (!$contacts || $contacts->readonly) {
$rcmail->output->show_message('contactreadonly', 'error');
$rcmail->overwrite_action($return_action);
return;
}
// read POST values into hash array
$a_record = self::process_input();
// do input checks (delegated to $contacts instance)
if (!$contacts->validate($a_record)) {
$err = (array) $contacts->get_error();
$rcmail->output->show_message(!empty($err['message']) ? rcube::Q($err['message']) : 'formincomplete', 'warning');
$rcmail->overwrite_action($return_action, ['contact' => $a_record]);
return;
}
// get raw photo data if changed
if (isset($a_record['photo'])) {
if ($a_record['photo'] == '-del-') {
$a_record['photo'] = '';
}
else if (!empty($_SESSION['contacts']['files'][$a_record['photo']])) {
$tempfile = $_SESSION['contacts']['files'][$a_record['photo']];
$tempfile = $rcmail->plugins->exec_hook('attachment_get', $tempfile);
if ($tempfile['status']) {
$a_record['photo'] = $tempfile['data'] ?: @file_get_contents($tempfile['path']);
}
}
else {
unset($a_record['photo']);
}
// cleanup session data
$rcmail->plugins->exec_hook('attachments_cleanup', ['group' => 'contact']);
$rcmail->session->remove('contacts');
}
// update an existing contact
if (!empty($cid)) {
$plugin = $rcmail->plugins->exec_hook('contact_update', [
'id' => $cid,
'record' => $a_record,
'source' => $source
]);
$a_record = $plugin['record'];
if (!$plugin['abort']) {
$result = $contacts->update($cid, $a_record);
}
else {
$result = $plugin['result'];
}
if ($result) {
// show confirmation
$rcmail->output->show_message('successfullysaved', 'confirmation', null, false);
// in search mode, just reload the list (#1490015)
if (!empty($_REQUEST['_search'])) {
$rcmail->output->command('parent.command', 'list');
$rcmail->output->send('iframe');
}
$newcid = null;
// LDAP DN change
if (is_string($result) && strlen($result) > 1) {
$newcid = $result;
// change cid in POST for 'show' action
$_POST['_cid'] = $newcid;
}
// refresh contact data for list update and 'show' action
$contact = $contacts->get_record($newcid ?: $cid, true);
// Plugins can decide to remove the contact on edit, e.g. automatic_addressbook
// Best we can do is to refresh the list (#5522)
if (empty($contact)) {
$rcmail->output->command('parent.command', 'list');
$rcmail->output->send('iframe');
}
// Update contacts list
$a_js_cols = [];
$record = $contact;
$record['email'] = array_first($contacts->get_col_values('email', $record, true));
$record['name'] = rcube_addressbook::compose_list_name($record);
foreach (['name'] as $col) {
$a_js_cols[] = rcube::Q((string) $record[$col]);
}
// performance: unset some big data items we don't need here
$record = array_intersect_key($record, ['ID' => 1,'email' => 1,'name' => 1]);
$record['_type'] = 'person';
// update the changed col in list
$rcmail->output->command('parent.update_contact_row', $cid, $a_js_cols, $newcid, $source, $record);
$rcmail->overwrite_action('show', ['contact' => $contact]);
}
else {
// show error message
$error = self::error_str($contacts, $plugin);
$rcmail->output->show_message($error, 'error', null, false);
$rcmail->overwrite_action('show');
}
}
// insert a new contact
else {
// Name of the addressbook already selected on the list
$orig_source = rcube_utils::get_input_string('_orig_source', rcube_utils::INPUT_GPC);
if (!strlen($source)) {
$source = $orig_source;
}
// show notice if existing contacts with same e-mail are found
foreach ($contacts->get_col_values('email', $a_record, true) as $email) {
if ($email && ($res = $contacts->search('email', $email, 1, false, true)) && $res->count) {
$rcmail->output->show_message('contactexists', 'notice', null, false);
break;
}
}
$plugin = $rcmail->plugins->exec_hook('contact_create', [
'record' => $a_record,
'source' => $source
]);
$a_record = $plugin['record'];
// insert record and send response
if (!$plugin['abort']) {
$insert_id = $contacts->insert($a_record);
}
else {
$insert_id = $plugin['result'];
}
if ($insert_id) {
$contacts->reset();
// add new contact to the specified group
if ($contacts->groups && $contacts->group_id) {
$plugin = $rcmail->plugins->exec_hook('group_addmembers', [
'group_id' => $contacts->group_id,
'ids' => $insert_id,
'source' => $source
]);
if (!$plugin['abort']) {
if (($maxnum = $rcmail->config->get('max_group_members', 0)) && ($contacts->count()->count + 1 > $maxnum)) {
// @FIXME: should we remove the contact?
$msgtext = $rcmail->gettext(['name' => 'maxgroupmembersreached', 'vars' => ['max' => $maxnum]]);
$rcmail->output->command('parent.display_message', $msgtext, 'warning');
}
else {
$contacts->add_to_group($plugin['group_id'], $plugin['ids']);
}
}
}
// show confirmation
$rcmail->output->show_message('successfullysaved', 'confirmation', null, false);
$rcmail->output->command('parent.set_rowcount', $rcmail->gettext('loading'));
$rcmail->output->command('parent.list_contacts');
$rcmail->output->send('iframe');
}
else {
// show error message
$error = self::error_str($contacts, $plugin);
$rcmail->output->show_message($error, 'error', null, false);
$rcmail->overwrite_action('add');
}
}
}
public static function process_input()
{
$record = [];
foreach (rcmail_action_contacts_index::$CONTACT_COLTYPES as $col => $colprop) {
if (!empty($colprop['composite'])) {
continue;
}
$fname = '_' . $col;
// gather form data of composite fields
if (!empty($colprop['childs'])) {
$values = [];
foreach ($colprop['childs'] as $childcol => $cp) {
$vals = rcube_utils::get_input_value('_' . $childcol, rcube_utils::INPUT_POST, true);
foreach ((array) $vals as $i => $val) {
$values[$i][$childcol] = $val;
}
}
if (isset($_REQUEST['_subtype_' . $col])) {
$subtypes = (array) rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST);
}
else {
$subtypes = [''];
}
foreach ($subtypes as $i => $subtype) {
$suffix = $subtype ? ":$subtype" : '';
if ($values[$i]) {
$record[$col . $suffix][] = $values[$i];
}
}
}
// assign values and subtypes
else if (isset($_POST[$fname]) && is_array($_POST[$fname])) {
$values = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
$subtypes = rcube_utils::get_input_value('_subtype_' . $col, rcube_utils::INPUT_POST);
foreach ($values as $i => $val) {
if ($col == 'email') {
// extract email from full address specification, e.g. "Name" <addr@domain.tld>
$addr = rcube_mime::decode_address_list($val, 1, false);
if (!empty($addr) && ($addr = array_pop($addr)) && $addr['mailto']) {
$val = $addr['mailto'];
}
}
$subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
$record[$col.$subtype][] = $val;
}
}
else if (isset($_POST[$fname])) {
$record[$col] = rcube_utils::get_input_value($fname, rcube_utils::INPUT_POST, true);
// normalize the submitted date strings
if ($colprop['type'] == 'date') {
if ($record[$col] && ($dt = rcube_utils::anytodatetime($record[$col]))) {
$record[$col] = $dt->format('Y-m-d');
}
else {
unset($record[$col]);
}
}
}
}
// Generate contact's display name (must be before validation)
if (empty($record['name'])) {
$record['name'] = rcube_addressbook::compose_display_name($record, true);
// Reset it if equals to email address (from compose_display_name())
$email = rcube_addressbook::get_col_values('email', $record, true);
if (isset($email[0]) && $record['name'] == $email[0]) {
$record['name'] = '';
}
}
return $record;
}
public static function error_str($contacts, $plugin)
{
if (!empty($plugin['message'])) {
return $plugin['message'];
}
$err = $contacts->get_error();
if (!empty($err['message'])) {
return $err['message'];
}
return 'errorsaving';
}
}
@@ -0,0 +1,313 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Search action (and form) for address book contacts |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_search extends rcmail_action_contacts_index
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
if (empty($_GET['_form'])) {
self::contact_search();
}
$rcmail->output->add_handler('searchform', [$this, 'contact_search_form']);
$rcmail->output->send('contactsearch');
}
public static function contact_search()
{
$rcmail = rcmail::get_instance();
$adv = isset($_POST['_adv']);
$sid = rcube_utils::get_input_string('_sid', rcube_utils::INPUT_GET);
$search = null;
// get search criteria from saved search
if ($sid && ($search = $rcmail->user->get_search($sid))) {
$fields = $search['data']['fields'];
$search = $search['data']['search'];
}
// get fields/values from advanced search form
else if ($adv) {
foreach (array_keys($_POST) as $key) {
$s = trim(rcube_utils::get_input_string($key, rcube_utils::INPUT_POST, true));
if (strlen($s) && preg_match('/^_search_([a-zA-Z0-9_-]+)$/', $key, $m)) {
$search[] = $s;
$fields[] = $m[1];
}
}
if (empty($fields)) {
// do nothing, show the form again
return;
}
}
// quick-search
else {
$search = trim(rcube_utils::get_input_string('_q', rcube_utils::INPUT_GET, true));
$fields = rcube_utils::get_input_string('_headers', rcube_utils::INPUT_GET);
if (empty($fields)) {
$fields = array_keys(self::$SEARCH_MODS_DEFAULT);
}
else {
$fields = array_filter(explode(',', $fields));
}
// update search_mods setting
$old_mods = $rcmail->config->get('addressbook_search_mods');
$search_mods = array_fill_keys($fields, 1);
if ($old_mods != $search_mods) {
$rcmail->user->save_prefs(['addressbook_search_mods' => $search_mods]);
}
if (in_array('*', $fields)) {
$fields = '*';
}
}
// Values matching mode
$mode = (int) $rcmail->config->get('addressbook_search_mode');
$mode |= rcube_addressbook::SEARCH_GROUPS;
// get sources list
$sources = $rcmail->get_address_sources();
$sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
$afields = $rcmail->config->get('contactlist_fields');
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$search_set = [];
$records = [];
foreach ($sources as $s) {
$source = $rcmail->get_address_book($s['id']);
// check if search fields are supported....
if (is_array($fields)) {
$cols = !empty($source->coltypes[0]) ? array_flip($source->coltypes) : $source->coltypes;
$supported = 0;
foreach ($fields as $f) {
if (array_key_exists($f, $cols)) {
$supported ++;
}
}
// in advanced search we require all fields (AND operator)
// in quick search we require at least one field (OR operator)
if (($adv && $supported < count($fields)) || (!$adv && !$supported)) {
continue;
}
}
// reset page
$source->set_page(1);
$source->set_pagesize(9999);
// get contacts count
$result = $source->search($fields, $search, $mode, false);
if (empty($result) || !$result->count) {
continue;
}
// get records
$result = $source->list_records($afields);
while ($row = $result->next()) {
$row['sourceid'] = $s['id'];
$key = rcube_addressbook::compose_contact_key($row, $sort_col);
$records[$key] = $row;
}
unset($result);
$search_set[$s['id']] = $source->get_search_set();
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
// cut first-page records
if ($page_size < $count) {
$records = array_slice($records, 0, $page_size);
}
$result->records = array_values($records);
// search request ID
$search_request = md5('addr'
. (is_array($fields) ? implode(',', $fields) : $fields)
. (is_array($search) ? implode(',', $search) : $search)
);
// save search settings in session
$_SESSION['contact_search'][$search_request] = $search_set;
$_SESSION['contact_search_params'] = ['id' => $search_request, 'data' => [$fields, $search]];
$_SESSION['page'] = 1;
if ($adv) {
$rcmail->output->command('list_contacts_clear');
}
if ($result->count > 0) {
// create javascript list
self::js_contacts_list($result);
$rcmail->output->show_message('contactsearchsuccessful', 'confirmation', ['nr' => $result->count]);
}
else {
$rcmail->output->show_message('nocontactsfound', 'notice');
}
// update message count display
$rcmail->output->set_env('search_request', $search_request);
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
$rcmail->output->command('set_rowcount', self::get_rowcount_text($result));
// Re-set current source
$rcmail->output->set_env('search_id', $sid);
$rcmail->output->set_env('source', '');
$rcmail->output->set_env('group', '');
// Re-set list header
$rcmail->output->command('set_group_prop', null);
if (!$sid) {
// unselect currently selected directory/group
$rcmail->output->command('unselect_directory');
// enable "Save search" command
$rcmail->output->command('enable_command', 'search-create', true);
}
$rcmail->output->command('update_group_commands');
// send response
$rcmail->output->send();
}
public static function contact_search_form($attrib)
{
$rcmail = rcmail::get_instance();
$i_size = !empty($attrib['size']) ? $attrib['size'] : 30;
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
$form = [
'main' => [
'name' => $rcmail->gettext('properties'),
'content' => [],
],
'personal' => [
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
'content' => [],
],
'other' => [
'name' => $rcmail->gettext('other'),
'content' => [],
],
];
// get supported coltypes from all address sources
$sources = $rcmail->get_address_sources();
$coltypes = [];
foreach ($sources as $s) {
$CONTACTS = $rcmail->get_address_book($s['id']);
if (!empty($CONTACTS->coltypes)) {
$contact_cols = isset($CONTACTS->coltypes[0]) ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
$coltypes = array_merge($coltypes, $contact_cols);
}
}
// merge supported coltypes with global coltypes
foreach ($coltypes as $col => $colprop) {
if (!empty(rcmail_action_contacts_index::$CONTACT_COLTYPES[$col])) {
$coltypes[$col] = array_merge(rcmail_action_contacts_index::$CONTACT_COLTYPES[$col], (array) $colprop);
}
else {
$coltypes[$col] = (array) $colprop;
}
}
// build form fields list
foreach ($coltypes as $col => $colprop) {
if (!isset($colprop['type'])) {
$colprop['type'] = 'text';
}
if ($colprop['type'] != 'image' && empty($colprop['nosearch'])) {
$ftype = $colprop['type'] == 'select' ? 'select' : 'text';
$label = $colprop['label'] ?? $rcmail->gettext($col);
$category = !empty($colprop['category']) ? $colprop['category'] : 'other';
// load jquery UI datepicker for date fields
if ($colprop['type'] == 'date') {
$colprop['class'] = (!empty($colprop['class']) ? $colprop['class'] . ' ' : '') . 'datepicker';
}
else if ($ftype == 'text') {
$colprop['size'] = $i_size;
}
$colprop['id'] = '_search_' . $col;
$content = html::div('row',
html::label(['class' => 'contactfieldlabel label', 'for' => $colprop['id']], rcube::Q($label))
. html::div('contactfieldcontent', rcube_output::get_edit_field('search_' . $col, '', $colprop, $ftype))
);
$form[$category]['content'][] = $content;
}
}
$hiddenfields = new html_hiddenfield();
$hiddenfields->add(['name' => '_adv', 'value' => 1]);
$out = $rcmail->output->request_form([
'name' => 'form',
'method' => 'post',
'task' => $rcmail->task,
'action' => 'search',
'noclose' => true,
] + $attrib, $hiddenfields->show()
);
$rcmail->output->add_gui_object('editform', $attrib['id']);
unset($attrib['name']);
unset($attrib['id']);
foreach ($form as $f) {
if (!empty($f['content'])) {
$content = html::div('contactfieldgroup', join("\n", $f['content']));
$legend = html::tag('legend', null, rcube::Q($f['name']));
$out .= html::tag('fieldset', $attrib, $legend . $content) . "\n";
}
}
return $out . '</form>';
}
}
@@ -0,0 +1,73 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Create saved search |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_search_create extends rcmail_action
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$id = rcube_utils::get_input_value('_search', rcube_utils::INPUT_POST);
$name = rcube_utils::get_input_value('_name', rcube_utils::INPUT_POST, true);
if (
!empty($_SESSION['contact_search_params'])
&& ($params = $_SESSION['contact_search_params'])
&& $params['id'] == $id
) {
$data = [
'type' => rcube_user::SEARCH_ADDRESSBOOK,
'name' => $name,
'data' => [
'fields' => $params['data'][0],
'search' => $params['data'][1],
],
];
$plugin = $rcmail->plugins->exec_hook('saved_search_create', ['data' => $data]);
if (empty($plugin['abort'])) {
$result = $rcmail->user->insert_search($plugin['data']);
}
else {
$result = $plugin['result'];
}
}
if (!empty($result)) {
$rcmail->output->show_message('savedsearchcreated', 'confirmation');
$rcmail->output->command('insert_saved_search', rcube::Q($name), rcube::Q($result));
}
else {
$error = !empty($plugin['message']) ? $plugin['message'] : 'savedsearchcreateerror';
$rcmail->output->show_message($error, 'error');
}
$rcmail->output->send();
}
}
@@ -0,0 +1,63 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Delete saved search |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_search_delete extends rcmail_action
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$id = rcube_utils::get_input_string('_sid', rcube_utils::INPUT_POST);
$result = false;
if (!empty($id)) {
$plugin = $rcmail->plugins->exec_hook('saved_search_delete', ['id' => $id]);
if (empty($plugin['abort'])) {
$result = $rcmail->user->delete_search($id);
}
else {
$result = $plugin['result'];
}
}
if ($result) {
$rcmail->output->show_message('savedsearchdeleted', 'confirmation');
$rcmail->output->command('remove_search_item', rcube::Q($id));
// contact list will be cleared, clear also page counter
$rcmail->output->command('set_rowcount', $rcmail->gettext('nocontactsfound'));
$rcmail->output->set_env('pagecount', 0);
}
else {
$error = !empty($plugin['message']) ? $plugin['message'] : 'savedsearchdeleteerror';
$rcmail->output->show_message($error, 'error');
}
$rcmail->output->send();
}
}
@@ -0,0 +1,243 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Show contact details |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_show extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// Get contact ID and source ID from request
$cids = self::get_cids();
$source = key($cids);
$cid = $cids ? array_first($cids[$source]) : null;
// Initialize addressbook source
self::$CONTACTS = self::contact_source($source, true);
self::$SOURCE_ID = $source;
// read contact record (or get the one defined in 'save' action)
if (!empty($args['contact'])) {
self::$contact = $args['contact'];
}
else if ($cid) {
self::$contact = self::$CONTACTS->get_record($cid, true);
}
if ($cid && self::$contact) {
$rcmail->output->set_env('readonly', self::$CONTACTS->readonly || !empty(self::$contact['readonly']));
$rcmail->output->set_env('cid', self::$contact['ID']);
// remember current search request ID (if in search mode)
if ($search = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GET)) {
$rcmail->output->set_env('search_request', $search);
}
}
// get address book name (for display)
self::set_sourcename(self::$CONTACTS);
$rcmail->output->add_handlers([
'contacthead' => [$this, 'contact_head'],
'contactdetails' => [$this, 'contact_details'],
'contactphoto' => [$this, 'contact_photo'],
]);
$rcmail->output->send('contact');
}
public static function contact_head($attrib)
{
$rcmail = rcmail::get_instance();
// check if we have a valid result
if (!self::$contact) {
$rcmail->output->show_message('contactnotfound', 'error');
return false;
}
$form = [
'head' => [ // section 'head' is magic!
'name' => $rcmail->gettext('contactnameandorg'),
'content' => [
'source' => ['type' => 'text'],
'prefix' => ['type' => 'text'],
'firstname' => ['type' => 'text'],
'middlename' => ['type' => 'text'],
'surname' => ['type' => 'text'],
'suffix' => ['type' => 'text'],
'name' => ['type' => 'text'],
'nickname' => ['type' => 'text'],
'organization' => ['type' => 'text'],
'department' => ['type' => 'text'],
'jobtitle' => ['type' => 'text'],
],
],
];
unset($attrib['name']);
return self::contact_form($form, self::$contact, $attrib);
}
public static function contact_details($attrib)
{
$rcmail = rcmail::get_instance();
// check if we have a valid result
if (!self::$contact) {
return false;
}
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$short_labels = self::get_bool_attr($attrib, 'short-legend-labels');
$form = [
'contact' => [
'name' => $rcmail->gettext('properties'),
'content' => [
'email' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_email_value'],
'phone' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_phone_value'],
'address' => [],
'website' => ['size' => $i_size, 'render_func' => 'rcmail_action_contacts_show::render_url_value'],
'im' => ['size' => $i_size],
],
],
'personal' => [
'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'),
'content' => [
'gender' => ['size' => $i_size],
'maidenname' => ['size' => $i_size],
'birthday' => ['size' => $i_size],
'anniversary' => ['size' => $i_size],
'manager' => ['size' => $i_size],
'assistant' => ['size' => $i_size],
'spouse' => ['size' => $i_size],
],
],
];
if (isset(rcmail_action_contacts_index::$CONTACT_COLTYPES['notes'])) {
$form['notes'] = [
'name' => $rcmail->gettext('notes'),
'content' => [
'notes' => ['type' => 'textarea', 'label' => false],
],
];
}
if (self::$CONTACTS->groups) {
$form['groups'] = [
'name' => $rcmail->gettext('groups'),
'content' => self::contact_record_groups(self::$contact['ID']),
];
}
return self::contact_form($form, self::$contact, $attrib);
}
public static function render_email_value($email)
{
$rcmail = rcmail::get_instance();
return html::a([
'href' => 'mailto:' . $email,
'onclick' => sprintf(
"return %s.command('compose','%s',this)",
rcmail_output::JS_OBJECT_NAME,
rcube::JQ($email)
),
'title' => $rcmail->gettext('composeto'),
'class' => 'email',
],
rcube::Q($email)
);
}
public static function render_phone_value($phone)
{
$attrs = [
'href' => 'tel:' . preg_replace('/[^0-9+,;-]/', '', $phone),
'class' => 'phone',
];
return html::a($attrs, rcube::Q($phone));
}
public static function render_url_value($url)
{
$prefix = preg_match('!^(http|ftp)s?://!', $url) ? '' : 'http://';
return html::a([
'href' => $prefix . $url,
'target' => '_blank',
'class' => 'url',
],
rcube::Q($url)
);
}
public static function contact_record_groups($contact_id)
{
$groups = self::$CONTACTS->list_groups();
if (empty($groups)) {
return '';
}
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$members = self::$CONTACTS->get_record_groups($contact_id);
$table = new html_table(['tagname' => 'ul', 'cols' => 1, 'class' => 'proplist simplelist']);
$checkbox = new html_checkbox(['name' => '_gid[]', 'class' => 'groupmember', 'disabled' => self::$CONTACTS->readonly]);
foreach ($groups as $group) {
$gid = $group['ID'];
$input = $checkbox->show(!empty($members[$gid]) ? $gid : null, ['value' => $gid]);
$table->add(null, html::label(null, $input . rcube::Q($group['name'])));
}
$hiddenfields = new html_hiddenfield(['name' => '_source', 'value' => $source]);
$hiddenfields->add(['name' => '_cid', 'value' => $contact_id]);
$form_attrs = [
'name' => 'form',
'method' => 'post',
'task' => $rcmail->task,
'action' => 'save',
'request' => 'save.' . intval($contact_id),
'noclose' => true,
];
$form_start = $rcmail->output->request_form($form_attrs, $hiddenfields->show());
$form_end = '</form>';
$rcmail->output->add_gui_object('editform', 'form');
$rcmail->output->add_label('addingmember', 'removingmember');
return $form_start . html::tag('fieldset', 'contactfieldgroup contactgroups', $table->show()) . $form_end;
}
}
@@ -0,0 +1,68 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Undelete contacts (CIDs) from last delete action |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_undo extends rcmail_action_contacts_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$delcnt = 0;
if (!empty($_SESSION['contact_undo']) && !empty($_SESSION['contact_undo']['data'])) {
foreach ((array) $_SESSION['contact_undo']['data'] as $source => $cid) {
$contacts = self::contact_source($source);
$plugin = $rcmail->plugins->exec_hook('contact_undelete', [
'id' => $cid,
'source' => $source
]);
$restored = empty($plugin['abort']) ? $contacts->undelete($cid) : $plugin['result'];
if (!$restored) {
$error = !empty($plugin['message']) ? $plugin['message'] : 'contactrestoreerror';
$rcmail->output->show_message($error, 'error');
$rcmail->output->command('list_contacts');
$rcmail->output->send();
}
else {
$delcnt += $restored;
}
}
}
$rcmail->session->remove('contact_undo');
if ($delcnt) {
$rcmail->output->show_message('contactrestored', 'confirmation');
$rcmail->output->command('list_contacts');
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,94 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handles contact photo uploads |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_contacts_upload_photo extends rcmail_action_contacts_index
{
/**
* Supported image format types
* ImageMagick works with other non-image types (e.g. pdf), we don't want that here
*
* @var array
*/
public static $IMAGE_TYPES = ['jpeg','jpg','jp2','tiff','tif','bmp','eps','gif','png','png8','png24','png32','svg','ico'];
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// clear all stored output properties (like scripts and env vars)
$rcmail->output->reset();
if (!empty($_FILES['_photo']['tmp_name'])) {
$filepath = $_FILES['_photo']['tmp_name'];
// check file type and resize image
$image = new rcube_image($_FILES['_photo']['tmp_name']);
$imageprop = $image->props();
if (
in_array(strtolower($imageprop['type']), self::$IMAGE_TYPES)
&& $imageprop['width']
&& $imageprop['height']
) {
$maxsize = intval($rcmail->config->get('contact_photo_size', 160));
$tmpfname = rcube_utils::temp_filename('imgconvert');
$save_hook = 'attachment_upload';
// scale image to a maximum size
if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) && $image->resize($maxsize, $tmpfname)) {
$filepath = $tmpfname;
$save_hook = 'attachment_save';
}
// save uploaded file in storage backend
$attachment = $rcmail->plugins->exec_hook($save_hook, [
'path' => $filepath,
'size' => $_FILES['_photo']['size'],
'name' => $_FILES['_photo']['name'],
'mimetype' => 'image/' . $imageprop['type'],
'group' => 'contact',
]);
}
else {
$attachment = ['error' => $rcmail->gettext('invalidimageformat')];
}
if (!empty($attachment['status']) && empty($attachment['abort'])) {
$file_id = $attachment['id'];
$_SESSION['contacts']['files'][$file_id] = $attachment;
$rcmail->output->command('replace_contact_photo', $file_id);
}
else {
// upload failed
self::upload_error($_FILES['_photo']['error'], $attachment);
}
}
else {
self::upload_failure();
}
$rcmail->output->command('photo_upload_end');
$rcmail->output->send('iframe');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Perform OAuth2 user login |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_login_oauth extends rcmail_action
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$auth_code = rcube_utils::get_input_string('code', rcube_utils::INPUT_GET);
$auth_error = rcube_utils::get_input_string('error', rcube_utils::INPUT_GET);
$auth_state = rcube_utils::get_input_string('state', rcube_utils::INPUT_GET);
// auth code return from oauth login
if (!empty($auth_code)) {
$auth = $rcmail->oauth->request_access_token($auth_code, $auth_state);
// oauth success
if ($auth && isset($auth['username'], $auth['authorization'], $auth['token'])) {
// enforce XOAUTH2 auth type
$rcmail->config->set('imap_auth_type', 'XOAUTH2');
$rcmail->config->set('login_password_maxlen', strlen($auth['authorization']));
// use access_token and user info for IMAP login
$storage_host = $rcmail->autoselect_host();
if ($rcmail->login($auth['username'], $auth['authorization'], $storage_host, true)) {
// replicate post-login tasks from index.php
$rcmail->session->remove('temp');
$rcmail->session->regenerate_id(false);
// send auth cookie if necessary
$rcmail->session->set_auth_cookie();
// save OAuth token in session
$_SESSION['oauth_token'] = $auth['token'];
// log successful login
$rcmail->log_login();
// allow plugins to control the redirect url after login success
$redir = $rcmail->plugins->exec_hook('login_after', ['_task' => 'mail']);
unset($redir['abort'], $redir['_err']);
// send redirect
header('Location: ' . $rcmail->url($redir, true, false));
exit;
}
else {
$rcmail->output->show_message('loginfailed', 'warning');
// log failed login
$error_code = $rcmail->login_error();
$rcmail->log_login($auth['username'], true, $error_code);
$rcmail->plugins->exec_hook('login_failed', [
'code' => $error_code,
'host' => $storage_host,
'user' => $auth['username'],
]);
$rcmail->kill_session();
// fall through -> login page
}
}
else {
$rcmail->output->show_message('oauthloginfailed', 'warning');
}
}
// error return from oauth login
else if (!empty($auth_error)) {
$error_message = rcube_utils::get_input_string('error_description', rcube_utils::INPUT_GET) ?: $auth_error;
$rcmail->output->show_message($error_message, 'warning');
}
// login action: redirect to `oauth_auth_uri`
else if ($rcmail->task === 'login') {
// this will always exit() the process
$rcmail->oauth->login_redirect();
}
}
}
@@ -0,0 +1,98 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Add the submitted contact to the user's address book |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_addcontact extends rcmail_action
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$address = rcube_utils::get_input_string('_address', rcube_utils::INPUT_POST, true);
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_POST);
// Get the default addressbook
$CONTACTS = null;
$SENDERS = null;
$type = 0;
if ($source != rcube_addressbook::TYPE_TRUSTED_SENDER) {
$CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true);
$type = rcube_addressbook::TYPE_DEFAULT;
}
// Get the trusted senders addressbook
if (!empty($_POST['_reload']) || $source == rcube_addressbook::TYPE_TRUSTED_SENDER) {
$collected_senders = $rcmail->config->get('collected_senders');
if (strlen($collected_senders)) {
$type |= rcube_addressbook::TYPE_TRUSTED_SENDER;
$SENDERS = $rcmail->get_address_book($collected_senders);
if ($CONTACTS == $SENDERS) {
$SENDERS = null;
}
}
}
$contact = rcube_mime::decode_address_list($address, 1, false);
if (empty($contact[1]['mailto'])) {
$rcmail->output->show_message('errorsavingcontact', 'error', null, false);
$rcmail->output->send();
}
$contact = [
'email' => $contact[1]['mailto'],
'name' => $contact[1]['name'],
];
$email = rcube_utils::idn_to_ascii($contact['email']);
if (!rcube_utils::check_email($email, false)) {
$rcmail->output->show_message('emailformaterror', 'error', ['email' => $contact['email']], false);
$rcmail->output->send();
}
if ($rcmail->contact_exists($contact['email'], $type)) {
$rcmail->output->show_message('contactexists', 'warning');
$rcmail->output->send();
}
$done = $rcmail->contact_create($contact, $SENDERS ?: $CONTACTS, $error);
if ($done) {
$rcmail->output->show_message('addedsuccessfully', 'confirmation');
if (!empty($_POST['_reload'])) {
$rcmail->output->command('command', 'load-remote');
}
}
else {
$rcmail->output->show_message($error ?: 'errorsavingcontact', 'error', null, false);
}
$rcmail->output->send();
}
}
@@ -0,0 +1,48 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Delete attachments from compose form |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_attachment_delete extends rcmail_action_mail_attachment_upload
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
self::init();
$rcmail = rcmail::get_instance();
$attachment = self::get_attachment();
if (is_array($attachment)) {
$attachment = $rcmail->plugins->exec_hook('attachment_delete', $attachment);
if (!empty($attachment['status'])) {
$rcmail->session->remove(self::$SESSION_KEY . '.attachments.' . self::$file_id);
$rcmail->output->command('remove_from_attachment_list', 'rcmfile' . self::$file_id);
}
}
$rcmail->output->send();
}
}
@@ -0,0 +1,35 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Display attachments in compose form |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_attachment_display extends rcmail_action_mail_attachment_upload
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
self::init();
self::display_uploaded_file(self::get_attachment());
exit;
}
}
@@ -0,0 +1,54 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Rename attachments in compose form |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_attachment_rename extends rcmail_action_mail_attachment_upload
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
self::init();
$filename = rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST);
$filename = trim($filename);
if (
strlen($filename)
&& ($attachment = self::get_attachment())
&& is_array($attachment)
) {
$attachment['name'] = $filename;
$rcmail->session->remove(self::$SESSION_KEY . '.attachments. ' . self::$file_id);
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
$rcmail->output->command('rename_attachment_handler', 'rcmfile' . self::$file_id, $filename);
}
$rcmail->output->send();
}
}
@@ -0,0 +1,290 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Attachment uploads handler for the compose form |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_attachment_upload extends rcmail_action_mail_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
protected static $SESSION_KEY;
protected static $COMPOSE;
protected static $COMPOSE_ID;
protected static $file_id;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
self::init();
// clear all stored output properties (like scripts and env vars)
$rcmail->output->reset();
$uploadid = rcube_utils::get_input_string('_uploadid', rcube_utils::INPUT_GPC);
$uri = rcube_utils::get_input_string('_uri', rcube_utils::INPUT_POST);
// handle dropping a reference to an attachment part of some message
if ($uri) {
$attachment = null;
$url = parse_url($uri);
if (!empty($url['query'])) {
parse_str($url['query'], $params);
}
if (
!empty($params) && isset($params['_mbox']) && strlen($params['_mbox'])
&& !empty($params['_uid']) && !empty($params['_part'])
) {
// @TODO: at some point we might support drag-n-drop between
// two different accounts on the same server, for now make sure
// this is the same server and the same user
list($host, $port) = rcube_utils::explode(':', $_SERVER['HTTP_HOST']);
if (
$host == $url['host']
&& $port == $url['port']
&& $rcmail->get_user_name() == rawurldecode($url['user'])
) {
$message = new rcube_message($params['_uid'], $params['_mbox']);
if ($message && !empty($message->headers)) {
$attachment = rcmail_action_mail_compose::save_attachment($message, $params['_part'], self::$COMPOSE_ID);
}
}
}
$plugin = $rcmail->plugins->exec_hook('attachment_from_uri', [
'attachment' => $attachment,
'uri' => $uri,
'compose_id' => self::$COMPOSE_ID
]);
if ($plugin['attachment']) {
self::attachment_success($plugin['attachment'], $uploadid);
}
else {
$rcmail->output->command('display_message', $rcmail->gettext('filelinkerror'), 'error');
$rcmail->output->command('remove_from_attachment_list', $uploadid);
}
$rcmail->output->send();
}
// handle file(s) upload
if (is_array($_FILES['_attachments']['tmp_name'])) {
$multiple = count($_FILES['_attachments']['tmp_name']) > 1;
$errors = [];
foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
// Process uploaded attachment if there is no error
$err = $_FILES['_attachments']['error'][$i];
if (!$err) {
$filename = $_FILES['_attachments']['name'][$i];
$filesize = $_FILES['_attachments']['size'][$i];
$filetype = rcube_mime::file_content_type($filepath, $filename, $_FILES['_attachments']['type'][$i]);
if ($err = self::check_message_size($filesize, $filetype)) {
if (!in_array($err, $errors)) {
$rcmail->output->command('display_message', $err, 'error');
$rcmail->output->command('remove_from_attachment_list', $uploadid);
$errors[] = $err;
}
continue;
}
$attachment = $rcmail->plugins->exec_hook('attachment_upload', [
'path' => $filepath,
'name' => $filename,
'size' => $filesize,
'mimetype' => $filetype,
'group' => self::$COMPOSE_ID,
]);
}
if (!$err && !empty($attachment['status']) && empty($attachment['abort'])) {
// store new attachment in session
unset($attachment['status'], $attachment['abort']);
$rcmail->session->append(self::$SESSION_KEY . '.attachments', $attachment['id'], $attachment);
self::attachment_success($attachment, $uploadid);
}
else { // upload failed
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$size = self::show_bytes(rcube_utils::max_upload_size());
$msg = $rcmail->gettext(['name' => 'filesizeerror', 'vars' => ['size' => $size]]);
}
else if (!empty($attachment['error'])) {
$msg = $attachment['error'];
}
else {
$msg = $rcmail->gettext('fileuploaderror');
}
if (!empty($attachment['error']) || $err != UPLOAD_ERR_NO_FILE) {
if (!in_array($msg, $errors)) {
$rcmail->output->command('display_message', $msg, 'error');
$rcmail->output->command('remove_from_attachment_list', $uploadid);
$errors[] = $msg;
}
}
}
}
}
else if (self::upload_failure()) {
$rcmail->output->command('remove_from_attachment_list', $uploadid);
}
// send html page with JS calls as response
$rcmail->output->command('auto_save_start', false);
$rcmail->output->send('iframe');
}
public static function init()
{
self::$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
self::$COMPOSE = null;
self::$SESSION_KEY = 'compose_data_' . self::$COMPOSE_ID;
if (self::$COMPOSE_ID && !empty($_SESSION[self::$SESSION_KEY])) {
self::$COMPOSE =& $_SESSION[self::$SESSION_KEY];
}
if (!self::$COMPOSE) {
die("Invalid session var!");
}
self::$file_id = rcube_utils::get_input_string('_file', rcube_utils::INPUT_GPC);
self::$file_id = preg_replace('/^rcmfile/', '', self::$file_id) ?: 'unknown';
}
public static function get_attachment()
{
return self::$COMPOSE['attachments'][self::$file_id];
}
public static function attachment_success($attachment, $uploadid)
{
$rcmail = rcmail::get_instance();
$id = $attachment['id'];
if (!empty(self::$COMPOSE['deleteicon']) && is_file(self::$COMPOSE['deleteicon'])) {
$button = html::img([
'src' => self::$COMPOSE['deleteicon'],
'alt' => $rcmail->gettext('delete')
]);
}
else if (!empty(self::$COMPOSE['textbuttons'])) {
$button = rcube::Q($rcmail->gettext('delete'));
}
else {
$button = '';
}
$link_content = sprintf(
'<span class="attachment-name">%s</span><span class="attachment-size">(%s)</span>',
rcube::Q($attachment['name']), self::show_bytes($attachment['size'])
);
$content_link = html::a([
'href' => "#load",
'class' => 'filename',
'onclick' => sprintf(
"return %s.command('load-attachment','rcmfile%s', this, event)",
rcmail_output::JS_OBJECT_NAME,
$id
),
], $link_content);
$delete_link = html::a([
'href' => "#delete",
'onclick' => sprintf(
"return %s.command('remove-attachment','rcmfile%s', this, event)",
rcmail_output::JS_OBJECT_NAME,
$id
),
'title' => $rcmail->gettext('delete'),
'class' => 'delete',
'aria-label' => $rcmail->gettext('delete') . ' ' . $attachment['name'],
], $button);
if (!empty(self::$COMPOSE['icon_pos']) && self::$COMPOSE['icon_pos'] == 'left') {
$content = $delete_link . $content_link;
}
else {
$content = $content_link . $delete_link;
}
$rcmail->output->command('add2attachment_list', "rcmfile$id", [
'html' => $content,
'name' => $attachment['name'],
'mimetype' => $attachment['mimetype'],
'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']),
'complete' => true
],
$uploadid
);
}
/**
* Checks if the attached file will fit in message size limit.
* Calculates size of all attachments and compares with the limit.
*
* @param int $filesize File size
* @param string $filetype File mimetype
*
* @return string Error message if the limit is exceeded
*/
public static function check_message_size($filesize, $filetype)
{
$rcmail = rcmail::get_instance();
$limit = parse_bytes($rcmail->config->get('max_message_size'));
$size = 10 * 1024; // size of message body
if (!$limit) {
return;
}
// add size of already attached files
if (!empty(self::$COMPOSE['attachments'])) {
foreach ((array) self::$COMPOSE['attachments'] as $att) {
// All attachments are base64-encoded except message/rfc822 (see sendmail.inc)
$multip = $att['mimetype'] == 'message/rfc822' ? 1 : 1.33;
$size += $att['size'] * $multip;
}
}
// add size of the new attachment
$multip = $filetype == 'message/rfc822' ? 1 : 1.33;
$size += $filesize * $multip;
if ($size > $limit) {
$limit = self::show_bytes($limit);
return $rcmail->gettext(['name' => 'msgsizeerror', 'vars' => ['size' => $limit]]);
}
}
}
@@ -0,0 +1,214 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Perform a search on configured address books for the email |
| address autocompletion |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_autocomplete extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$MAXNUM = (int) $rcmail->config->get('autocomplete_max', 15);
$mode = (int) $rcmail->config->get('addressbook_search_mode');
$single = (bool) $rcmail->config->get('autocomplete_single');
$search = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC, true);
$reqid = rcube_utils::get_input_string('_reqid', rcube_utils::INPUT_GPC);
$contacts = [];
if (strlen($search) && ($book_types = self::autocomplete_addressbooks())) {
$sort_keys = [];
$books_num = count($book_types);
$search_lc = mb_strtolower($search);
$mode |= rcube_addressbook::SEARCH_GROUPS;
$fields = $rcmail->config->get('contactlist_fields');
foreach ($book_types as $abook_id) {
$abook = $rcmail->get_address_book($abook_id);
$abook->set_pagesize($MAXNUM);
if ($result = $abook->search($fields, $search, $mode, true, true, 'email')) {
while ($record = $result->iterate()) {
// Contact can have more than one e-mail address
$email_arr = (array) $abook->get_col_values('email', $record, true);
$email_cnt = count($email_arr);
$idx = 0;
foreach ($email_arr as $email) {
if (empty($email)) {
continue;
}
$name = rcube_addressbook::compose_list_name($record);
$contact = format_email_recipient($email, $name);
// skip entries that don't match
if ($email_cnt > 1 && strpos(mb_strtolower($contact), $search_lc) === false) {
continue;
}
$index = $contact;
// skip duplicates
if (empty($contacts[$index])) {
$contact = [
'name' => $contact,
'type' => $record['_type'] ?? null,
'id' => $record['ID'],
'source' => $abook_id,
];
$display = rcube_addressbook::compose_search_name($record, $email, $name);
if ($display && $display != $contact['name']) {
$contact['display'] = $display;
}
// groups with defined email address will not be expanded to its members' addresses
if ($contact['type'] == 'group') {
$contact['email'] = $email;
}
$name = !empty($contact['display']) ? $contact['display'] : $name;
$contacts[$index] = $contact;
$sort_keys[$index] = sprintf('%s %03d', $name, $idx++);
if (count($contacts) >= $MAXNUM) {
break 2;
}
}
// skip redundant entries (show only first email address)
if ($single) {
break;
}
}
}
}
// also list matching contact groups
if ($abook->groups && count($contacts) < $MAXNUM) {
foreach ($abook->list_groups($search, $mode) as $group) {
$abook->reset();
$abook->set_group($group['ID']);
$group_prop = $abook->get_group($group['ID']);
// group (distribution list) with email address(es)
if (!empty($group_prop['email'])) {
$idx = 0;
foreach ((array) $group_prop['email'] as $email) {
$index = format_email_recipient($email, $group['name']);
if (empty($contacts[$index])) {
$sort_keys[$index] = sprintf('%s %03d', $group['name'] , $idx++);
$contacts[$index] = [
'name' => $index,
'email' => $email,
'type' => 'group',
'id' => $group['ID'],
'source' => $abook_id,
];
if (count($contacts) >= $MAXNUM) {
break 3;
}
}
}
}
// show group with count
else if (($result = $abook->count()) && $result->count) {
if (empty($contacts[$group['name']])) {
$sort_keys[$group['name']] = $group['name'];
$contacts[$group['name']] = [
'name' => $group['name'] . ' (' . intval($result->count) . ')',
'type' => 'group',
'id' => $group['ID'],
'source' => $abook_id,
];
if (count($contacts) >= $MAXNUM) {
break 2;
}
}
}
}
}
}
if (count($contacts)) {
// sort contacts index
asort($sort_keys, SORT_LOCALE_STRING);
// re-sort contacts according to index
foreach ($sort_keys as $idx => $val) {
$sort_keys[$idx] = $contacts[$idx];
}
$contacts = array_values($sort_keys);
}
}
// Allow autocomplete result optimization via plugin
$plugin = $rcmail->plugins->exec_hook('contacts_autocomplete_after', [
'search' => $search,
// Provide already-found contacts to plugin if they are required
'contacts' => $contacts,
]);
$contacts = $plugin['contacts'];
$rcmail->output->command('ksearch_query_results', $contacts, $search, $reqid);
$rcmail->output->send();
}
/**
* Collect addressbook sources used for autocompletion
*/
public static function autocomplete_addressbooks()
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
if (strlen($source)) {
$book_types = [$source];
}
else {
$book_types = (array) $rcmail->config->get('autocomplete_addressbooks', 'sql');
}
$collected_recipients = $rcmail->config->get('collected_recipients');
$collected_senders = $rcmail->config->get('collected_senders');
if (strlen($collected_recipients) && !in_array($collected_recipients, $book_types)) {
$book_types[] = $collected_recipients;
}
if (strlen($collected_senders) && !in_array($collected_senders, $book_types)) {
$book_types[] = $collected_senders;
}
return !empty($book_types) ? $book_types : null;
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Bounce/resend an email message |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_bounce extends rcmail_action
{
protected static $MESSAGE;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$msg_uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
$msg_folder = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GP, true);
$MESSAGE = new rcube_message($msg_uid, $msg_folder);
self::$MESSAGE = $MESSAGE;
if (!$MESSAGE->headers) {
$rcmail->output->show_message('messageopenerror', 'error');
$rcmail->output->send('iframe');
}
// Display Bounce form
if (empty($_POST)) {
if (!empty($MESSAGE->headers->charset)) {
$rcmail->storage->set_charset($MESSAGE->headers->charset);
}
// Initialize helper class to build the UI
$SENDMAIL = new rcmail_sendmail(
['mode' => rcmail_sendmail::MODE_FORWARD],
['message' => $MESSAGE]
);
$rcmail->output->set_env('mailbox', $msg_folder);
$rcmail->output->set_env('uid', $msg_uid);
$rcmail->output->add_handler('bounceobjects', [$this, 'bounce_objects']);
$rcmail->output->send('bounce');
}
// Initialize helper class to send the message
$SENDMAIL = new rcmail_sendmail(
['mode' => rcmail_sendmail::MODE_FORWARD],
[
'sendmail' => true,
'error_handler' => function(...$args) use ($rcmail) {
call_user_func_array([$rcmail->output, 'show_message'], $args);
$rcmail->output->send('iframe');
}
]
);
// Handle the form input
$input_headers = $SENDMAIL->headers_input();
// Set Resent-* headers, these will be added on top of the bounced message
$headers = [];
foreach (['From', 'To', 'Cc', 'Bcc', 'Date', 'Message-ID'] as $name) {
if (!empty($input_headers[$name])) {
$headers['Resent-' . $name] = $input_headers[$name];
}
}
// Create the bounce message
$BOUNCE = new rcmail_resend_mail([
'bounce_message' => $MESSAGE,
'bounce_headers' => $headers,
]);
// Send the bounce message
$SENDMAIL->deliver_message($BOUNCE);
// Save in Sent (if requested)
$saved = $SENDMAIL->save_message($BOUNCE);
if (!$saved && strlen($SENDMAIL->options['store_target'])) {
self::display_server_error('errorsaving');
}
$rcmail->output->show_message('messagesent', 'confirmation', null, false);
$rcmail->output->send('iframe');
}
/**
* Handler for template object 'bounceObjects'
*
* @param array $attrib HTML attributes
*
* @return string HTML content
*/
public static function bounce_objects($attrib)
{
if (empty($attrib['id'])) {
$attrib['id'] = 'bounce-objects';
}
$rcmail = rcmail::get_instance();
$content = [];
// Always display a hint about the bounce feature behavior
$msg = html::span(null, rcube::Q($rcmail->gettext('bouncehint')));
$msg_attrib = ['id' => 'bounce-hint', 'class' => 'boxinformation'];
$content[] = html::div($msg_attrib, $msg);
// Add a warning about Bcc recipients
if (self::$MESSAGE->headers->get('bcc', false) || self::$MESSAGE->headers->get('resent-bcc', false)) {
$msg = html::span(null, rcube::Q($rcmail->gettext('bccemail')));
$msg_attrib = ['id' => 'bcc-warning', 'class' => 'boxwarning'];
$content[] = html::div($msg_attrib, $msg);
}
$plugin = $rcmail->plugins->exec_hook('bounce_objects',
['content' => $content, 'message' => self::$MESSAGE]);
$content = implode("\n", $plugin['content']);
return $content ? html::div($attrib, $content) : '';
}
}
@@ -0,0 +1,211 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Check for recent messages, in all mailboxes |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_check_recent extends rcmail_action_mail_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// If there's no folder or messages list, there's nothing to update
// This can happen on 'refresh' request
if (empty($_POST['_folderlist']) && empty($_POST['_list'])) {
return;
}
$trash = $rcmail->config->get('trash_mbox');
$current = $rcmail->storage->get_folder();
$check_all = $rcmail->action != 'refresh' || (bool) $rcmail->config->get('check_all_folders');
$page = $rcmail->storage->get_page();
$page_size = $rcmail->storage->get_pagesize();
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
if ($search_request && $_SESSION['search_request'] != $search_request) {
$search_request = null;
}
// list of folders to check
if ($check_all) {
$a_mailboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
}
else if ($search_request && isset($_SESSION['search'][1]) && is_object($_SESSION['search'][1])) {
$a_mailboxes = (array) $_SESSION['search'][1]->get_parameters('MAILBOX');
}
else {
$a_mailboxes = (array) $current;
if ($current != 'INBOX') {
$a_mailboxes[] = 'INBOX';
}
}
// Control folders list from a plugin
$plugin = $rcmail->plugins->exec_hook('check_recent', ['folders' => $a_mailboxes, 'all' => $check_all]);
$a_mailboxes = $plugin['folders'];
$list_cleared = false;
self::storage_fatal_error();
// check recent/unseen counts
foreach ($a_mailboxes as $mbox_name) {
$is_current = $mbox_name == $current
|| (
!empty($search_request)
&& isset($_SESSION['search'][1])
&& is_object($_SESSION['search'][1])
&& in_array($mbox_name, (array)$_SESSION['search'][1]->get_parameters('MAILBOX'))
);
if ($is_current) {
// Synchronize mailbox cache, handle flag changes
$rcmail->storage->folder_sync($mbox_name);
}
// Get mailbox status
$status = $rcmail->storage->folder_status($mbox_name, $diff);
if ($is_current) {
self::storage_fatal_error();
}
if ($status & 1) {
// trigger plugin hook
$rcmail->plugins->exec_hook('new_messages', [
'mailbox' => $mbox_name,
'is_current' => $is_current,
'diff' => $diff
]);
}
self::send_unread_count($mbox_name, true, null, (!$is_current && ($status & 1)) ? 'recent' : '');
if ($status && $is_current) {
// refresh saved search set
if (!empty($search_request) && isset($_SESSION['search'])) {
unset($search_request); // only do this once
$_SESSION['search'] = $rcmail->storage->refresh_search();
if (!empty($_SESSION['search'][1]->multi)) {
$mbox_name = '';
}
}
if (!empty($_POST['_quota'])) {
$rcmail->output->command('set_quota', self::quota_content(null, $mbox_name));
}
$rcmail->output->set_env('exists', $rcmail->storage->count($mbox_name, 'EXISTS', true));
// "No-list" mode, don't get messages
if (empty($_POST['_list'])) {
continue;
}
// get overall message count; allow caching because rcube_storage::folder_status()
// did a refresh but only in list mode
$list_mode = $rcmail->storage->get_threading() ? 'THREADS' : 'ALL';
$all_count = $rcmail->storage->count($mbox_name, $list_mode, $list_mode == 'THREADS', false);
// check current page if we're not on the first page
if ($all_count && $page > 1) {
$remaining = $all_count - $page_size * ($page - 1);
if ($remaining <= 0) {
$page -= 1;
$rcmail->storage->set_page($page);
$_SESSION['page'] = $page;
}
}
$rcmail->output->set_env('messagecount', $all_count);
$rcmail->output->set_env('pagecount', ceil($all_count/$page_size));
$rcmail->output->command('set_rowcount', self::get_messagecount_text($all_count), $mbox_name);
$rcmail->output->set_env('current_page', $all_count ? $page : 1);
// remove old rows (and clear selection if new list is empty)
$rcmail->output->command('message_list.clear', $all_count ? false : true);
if ($all_count) {
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
// add message rows
self::js_message_list($a_headers, false);
// remove messages that don't exists from list selection array
$rcmail->output->command('update_selection');
}
$list_cleared = true;
}
// set trash folder state
if ($mbox_name === $trash) {
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox_name, 'EXISTS', true));
}
}
// handle flag updates
if (!$list_cleared) {
$uids = rcube_utils::get_input_value('_uids', rcube_utils::INPUT_POST);
$uids = self::get_uids($uids, null, $multifolder);
$recent_flags = [];
foreach ($uids as $mbox_name => $set) {
$get_flags = true;
$modseq = null;
if ($mbox_name == $current) {
$data = $rcmail->storage->folder_data($mbox_name);
$modseq = !empty($_SESSION['list_mod_seq']) ? $_SESSION['list_mod_seq'] : null;
$get_flags = empty($modseq) || empty($data['HIGHESTMODSEQ']) || $modseq != $data['HIGHESTMODSEQ'];
// remember last HIGHESTMODSEQ value (if supported)
if (!empty($data['HIGHESTMODSEQ'])) {
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
}
}
// TODO: Consider HIGHESTMODSEQ for all folders in multifolder search, otherwise
// flags for all messages in a set are requested on every refresh
if ($get_flags) {
$flags = $rcmail->storage->list_flags($mbox_name, $set, $modseq);
foreach ($flags as $idx => $row) {
if ($multifolder) {
$idx .= '-' . $mbox_name;
}
$recent_flags[$idx] = array_change_key_case(array_map('intval', $row));
}
}
$rcmail->output->set_env('recent_flags', $recent_flags);
}
}
// trigger refresh hook
$rcmail->plugins->exec_hook('refresh', []);
$rcmail->output->send();
}
}
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Copy the submitted messages to a specific mailbox |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_copy extends rcmail_action_mail_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// copy messages
if (empty($_POST['_uid']) || !isset($_POST['_target_mbox']) || !strlen($_POST['_target_mbox'])) {
$rcmail->output->show_message('internalerror', 'error');
}
$uids = self::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST);
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
$sources = [];
$copied = false;
foreach ($uids as $mbox => $uids) {
if ($mbox === $target) {
$copied++;
}
else {
$copied += (int) $rcmail->storage->copy_message($uids, $target, $mbox);
$sources[] = $mbox;
}
}
if (!$copied) {
self::display_server_error('errorcopying');
}
else {
$rcmail->output->show_message('messagecopied', 'confirmation');
self::send_unread_count($target, true);
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
}
$rcmail->output->send();
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handler for mail delete operation |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_delete extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// count messages before changing anything
$threading = (bool) $rcmail->storage->get_threading();
$trash = $rcmail->config->get('trash_mbox');
$sources = [];
$old_count = 0;
$deleted = 0;
$count = 0;
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
}
if (empty($_POST['_uid'])) {
$rcmail->output->show_message('internalerror', 'error');
$rcmail->output->send();
}
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
$deleted += (int) $rcmail->storage->delete_message($uids, $mbox);
$count += is_array($uids) ? count($uids) : 1;
$sources[] = $mbox;
}
if (empty($deleted)) {
// send error message
if ($_POST['_from'] != 'show') {
$rcmail->output->command('list_mailbox');
}
self::display_server_error('errordeleting');
$rcmail->output->send();
}
else {
$rcmail->output->show_message('messagedeleted', 'confirmation');
}
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
// refresh saved search set after moving some messages
if ($search_request && $rcmail->storage->get_search_set()) {
$_SESSION['search'] = $rcmail->storage->refresh_search();
}
if (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
if ($next = rcube_utils::get_input_string('_next_uid', rcube_utils::INPUT_GPC)) {
$rcmail->output->command('show_message', $next);
}
else {
$rcmail->output->command('command', 'list');
}
$rcmail->output->send();
}
$mbox = $rcmail->storage->get_folder();
$msg_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
$exists = $rcmail->storage->count($mbox, 'EXISTS', true);
$page_size = $rcmail->storage->get_pagesize();
$page = $rcmail->storage->get_page();
$pages = ceil($msg_count / $page_size);
$nextpage_count = $old_count - $page_size * $page;
$remaining = $msg_count - $page_size * ($page - 1);
$jump_back = false;
// jump back one page (user removed the whole last page)
if ($page > 1 && $remaining == 0) {
$page -= 1;
$rcmail->storage->set_page($page);
$_SESSION['page'] = $page;
$jump_back = true;
}
// update unseen messages counts for all involved folders
foreach ($sources as $source) {
self::send_unread_count($source, true);
}
// update message count display
$rcmail->output->set_env('messagecount', $msg_count);
$rcmail->output->set_env('current_page', $page);
$rcmail->output->set_env('pagecount', $pages);
$rcmail->output->set_env('exists', $exists);
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
if ($threading) {
$count = rcube_utils::get_input_string('_count', rcube_utils::INPUT_POST);
}
// add new rows from next page (if any)
if (!empty($count) && $_POST['_uid'] != '*' && ($jump_back || $nextpage_count > 0)) {
// #5862: Don't add more rows than it was on the next page
$count = $jump_back ? null : min($nextpage_count, $count);
$a_headers = $rcmail->storage->list_messages($mbox, null, self::sort_column(), self::sort_order(), $count);
self::js_message_list($a_headers, false);
}
// set trash folder state
if ($mbox === $trash) {
$rcmail->output->command('set_trash_count', $exists);
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,54 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Implement folder EXPUNGE request |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_folder_expunge extends rcmail_action
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
$success = $rcmail->storage->expunge_folder($mbox);
// reload message list if current mailbox
if ($success) {
$rcmail->output->show_message('folderexpunged', 'confirmation');
if (!empty($_REQUEST['_reload'])) {
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
$rcmail->output->command('message_list.clear');
$rcmail->action = 'list';
return;
}
}
else {
self::display_server_error();
}
$rcmail->output->send();
}
}
@@ -0,0 +1,82 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Implement folder PURGE request |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_folder_purge extends rcmail_action_mail_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$delimiter = $storage->get_hierarchy_delimiter();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
$trash_mbox = $rcmail->config->get('trash_mbox');
$trash_regexp = '/^' . preg_quote($trash_mbox . $delimiter, '/') . '/';
// we should only be purging trash (or their subfolders)
if (!strlen($trash_mbox) || $mbox === $trash_mbox || preg_match($trash_regexp, $mbox)) {
$success = $storage->delete_message('*', $mbox);
$delete = true;
}
// move to Trash
else {
$success = $storage->move_message('1:*', $trash_mbox, $mbox);
$delete = false;
}
if ($success) {
$rcmail->output->show_message('folderpurged', 'confirmation');
$rcmail->output->command('set_unread_count', $mbox, 0);
self::set_unseen_count($mbox, 0);
// set trash folder state
if ($mbox === $trash_mbox) {
$rcmail->output->command('set_trash_count', 0);
}
else if (strlen($trash_mbox)) {
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash_mbox, 'EXISTS'));
}
if (!$delete && strlen($trash_mbox)) {
self::send_unread_count($trash_mbox, true);
}
if (!empty($_REQUEST['_reload'])) {
$rcmail->output->set_env('messagecount', 0);
$rcmail->output->set_env('pagecount', 0);
$rcmail->output->set_env('exists', 0);
$rcmail->output->command('message_list.clear');
$rcmail->output->command('set_rowcount', self::get_messagecount_text(), $mbox);
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
}
}
else {
self::display_server_error();
}
$rcmail->output->send();
}
}
+372
View File
@@ -0,0 +1,372 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Delivering a specific uploaded file or mail message attachment |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_get extends rcmail_action_mail_index
{
protected static $attachment;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// This resets X-Frame-Options for framed output (#6688)
$rcmail->output->page_headers();
// show loading page
if (!empty($_GET['_preload'])) {
unset($_GET['_preload']);
unset($_GET['_safe']);
$url = $rcmail->url($_GET + ['_mimewarning' => 1, '_embed' => 1]);
$message = $rcmail->gettext('loadingdata');
header('Content-Type: text/html; charset=' . RCUBE_CHARSET);
print "<html>\n<head>\n"
. '<meta http-equiv="refresh" content="0; url='.rcube::Q($url).'">' . "\n"
. '<meta http-equiv="content-type" content="text/html; charset=' . RCUBE_CHARSET . '">' . "\n"
. "</head>\n<body>\n$message\n</body>\n</html>";
exit;
}
$attachment = new rcmail_attachment_handler;
$mimetype = $attachment->mimetype;
$filename = $attachment->filename;
self::$attachment = $attachment;
// show part page
if (!empty($_GET['_frame'])) {
$rcmail->output->set_pagetitle($filename);
// register UI objects
$rcmail->output->add_handlers([
'messagepartframe' => [$this, 'message_part_frame'],
'messagepartcontrols' => [$this, 'message_part_controls'],
]);
$part_id = rcube_utils::get_input_string('_part', rcube_utils::INPUT_GET);
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
// message/rfc822 preview (Note: handle also multipart/ parts, they can
// come from Enigma, which replaces message/rfc822 with real mimetype)
if ($part_id && ($mimetype == 'message/rfc822' || strpos($mimetype, 'multipart/') === 0)) {
$uid = preg_replace('/\.[0-9.]+/', '', $uid);
$uid .= '.' . $part_id;
$rcmail->output->set_env('is_message', true);
}
$rcmail->output->set_env('mailbox', $rcmail->storage->get_folder());
$rcmail->output->set_env('uid', $uid);
$rcmail->output->set_env('part', $part_id);
$rcmail->output->set_env('filename', $filename);
$rcmail->output->set_env('mimetype', $mimetype);
$rcmail->output->send('messagepart');
}
// render thumbnail of an image attachment
if (!empty($_GET['_thumb']) && $attachment->is_valid()) {
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
$file_ident = $attachment->ident;
$thumb_name = 'thumb' . md5($file_ident . ':' . $rcmail->user->ID . ':' . $thumbnail_size);
$cache_file = rcube_utils::temp_filename($thumb_name, false, false);
// render thumbnail image if not done yet
if (!is_file($cache_file) && $attachment->body_to_file($orig_name = rcube_utils::temp_filename('attmnt'))) {
$image = new rcube_image($orig_name);
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
$mimetype = 'image/' . $imgtype;
}
else {
// Resize failed, we need to check the file mimetype
// So, we do not exit here, but goto generic file body handler below
$_GET['_thumb'] = 0;
$_REQUEST['_embed'] = 1;
}
}
if (!empty($_GET['_thumb'])) {
if (is_file($cache_file)) {
$rcmail->output->future_expire_header(3600);
header('Content-Type: ' . $mimetype);
header('Content-Length: ' . filesize($cache_file));
readfile($cache_file);
}
exit;
}
}
// Handle attachment body (display or download)
if (empty($_GET['_thumb']) && $attachment->is_valid()) {
// require CSRF protected url for downloads
if (!empty($_GET['_download'])) {
$rcmail->request_security_check(rcube_utils::INPUT_GET);
}
$extensions = rcube_mime::get_mime_extensions($mimetype);
// compare file mimetype with the stated content-type headers and file extension to avoid malicious operations
if (!empty($_REQUEST['_embed']) && empty($_REQUEST['_nocheck'])) {
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
// 1. compare filename suffix with expected suffix derived from mimetype
$valid = $file_extension && in_array($file_extension, (array)$extensions)
|| empty($extensions)
|| !empty($_REQUEST['_mimeclass']);
// 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension
if ($valid || !$file_extension || $mimetype == 'application/octet-stream' || stripos($mimetype, 'text/') === 0) {
$tmp_body = $attachment->body(2048);
// detect message part mimetype
$real_mimetype = rcube_mime::file_content_type($tmp_body, $filename, $mimetype, true, true);
list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype);
// accept text/plain with any extension
if ($real_mimetype == 'text/plain' && self::mimetype_compare($real_mimetype, $mimetype)) {
$valid_extension = true;
}
// ignore differences in text/* mimetypes. Filetype detection isn't very reliable here
else if ($real_ctype_primary == 'text' && strpos($mimetype, $real_ctype_primary) === 0) {
$real_mimetype = $mimetype;
$valid_extension = true;
}
// ignore filename extension if mimeclass matches (#1489029)
else if (!empty($_REQUEST['_mimeclass']) && $real_ctype_primary == $_REQUEST['_mimeclass']) {
$valid_extension = true;
}
else {
// get valid file extensions
$extensions = rcube_mime::get_mime_extensions($real_mimetype);
$valid_extension = !$file_extension || empty($extensions) || in_array($file_extension, (array)$extensions);
}
if (
// fix mimetype for files wrongly declared as octet-stream
($mimetype == 'application/octet-stream' && $valid_extension)
// force detected mimetype for images (#8158)
|| (strpos($real_mimetype, 'image/') === 0)
) {
$mimetype = $real_mimetype;
}
// "fix" real mimetype the same way the original is before comparison
$real_mimetype = rcube_mime::fix_mimetype($real_mimetype);
$valid = $valid_extension && self::mimetype_compare($real_mimetype, $mimetype);
}
else {
$real_mimetype = $mimetype;
}
// show warning if validity checks failed
if (!$valid) {
// send blocked.gif for expected images
if (empty($_REQUEST['_mimewarning']) && strpos($mimetype, 'image/') === 0) {
// Do not cache. Failure might be the result of a misconfiguration,
// thus real content should be returned once fixed.
$content = self::get_resource_content('blocked.gif');
$rcmail->output->nocacheing_headers();
header("Content-Type: image/gif");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . strlen($content));
echo $content;
}
// html warning with a button to load the file anyway
else {
$rcmail->output = new rcmail_html_page();
$rcmail->output->register_inline_warning(
$rcmail->gettext([
'name' => 'attachmentvalidationerror',
'vars' => [
'expected' => $mimetype . (!empty($file_extension) ? rcube::Q(" (.{$file_extension})") : ''),
'detected' => $real_mimetype . (!empty($extensions[0]) ? " (.{$extensions[0]})" : ''),
]
]),
$rcmail->gettext('showanyway'),
$rcmail->url(array_merge($_GET, ['_nocheck' => 1]))
);
$rcmail->output->write();
}
exit;
}
}
// TIFF/WEBP to JPEG conversion, if needed
foreach (['tiff', 'webp'] as $type) {
$img_support = !empty($_SESSION['browser_caps']) && !empty($_SESSION['browser_caps'][$type]);
if (
!empty($_REQUEST['_embed'])
&& !$img_support
&& $attachment->image_type() == 'image/' . $type
&& rcube_image::is_convertable('image/' . $type)
) {
$convert2jpeg = true;
$mimetype = 'image/jpeg';
break;
}
}
// deliver part content
if ($mimetype == 'text/html' && empty($_GET['_download'])) {
$rcmail->output = new rcmail_html_page();
$out = '';
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcube_utils::mem_check($attachment->size * 10)) {
$rcmail->output->register_inline_warning(
$rcmail->gettext('messagetoobig'),
$rcmail->gettext('download'),
$rcmail->url(array_merge($_GET, ['_download' => 1]))
);
}
else {
// render HTML body
$out = $attachment->html();
// insert remote objects warning into HTML body
if (self::$REMOTE_OBJECTS) {
$rcmail->output->register_inline_warning(
$rcmail->gettext('blockedresources'),
$rcmail->gettext('allow'),
$rcmail->url(array_merge($_GET, ['_safe' => 1]))
);
}
}
$rcmail->output->write($out);
exit;
}
// add filename extension if missing
if (!pathinfo($filename, PATHINFO_EXTENSION) && ($extensions = rcube_mime::get_mime_extensions($mimetype))) {
$filename .= '.' . $extensions[0];
}
$rcmail->output->download_headers($filename, [
'type' => $mimetype,
'type_charset' => $attachment->charset,
'disposition' => !empty($_GET['_download']) ? 'attachment' : 'inline',
]);
// handle tiff to jpeg conversion
if (!empty($convert2jpeg)) {
$file_path = rcube_utils::temp_filename('attmnt');
// convert image to jpeg and send it to the browser
if ($attachment->body_to_file($file_path)) {
$image = new rcube_image($file_path);
if ($image->convert(rcube_image::TYPE_JPG, $file_path)) {
header("Content-Length: " . filesize($file_path));
readfile($file_path);
}
}
}
else {
$attachment->output($mimetype);
}
exit;
}
// if we arrive here, the requested part was not found
header('HTTP/1.1 404 Not Found');
exit;
}
/**
* Compares two mimetype strings with making sure that
* e.g. image/bmp and image/x-ms-bmp are treated as equal.
*/
public static function mimetype_compare($type1, $type2)
{
$regexp = '~/(x-ms-|x-)~';
$type1 = preg_replace($regexp, '/', $type1);
$type2 = preg_replace($regexp, '/', $type2);
return $type1 === $type2;
}
/**
* Attachment properties table
*/
public static function message_part_controls($attrib)
{
if (!self::$attachment->is_valid()) {
return '';
}
$rcmail = rcmail::get_instance();
$table = new html_table(['cols' => 2]);
$table->add('title', rcube::Q($rcmail->gettext('namex')).':');
$table->add('header', rcube::Q(self::$attachment->filename));
$table->add('title', rcube::Q($rcmail->gettext('type')).':');
$table->add('header', rcube::Q(self::$attachment->mimetype));
$table->add('title', rcube::Q($rcmail->gettext('size')).':');
$table->add('header', rcube::Q(self::$attachment->size()));
return $table->show($attrib);
}
/**
* Attachment preview frame
*/
public static function message_part_frame($attrib)
{
$rcmail = rcmail::get_instance();
if ($rcmail->output->get_env('is_message')) {
$url = [
'task' => 'mail',
'action' => 'preview',
'uid' => $rcmail->output->get_env('uid'),
'mbox' => $rcmail->output->get_env('mailbox'),
];
}
else {
$mimetype = $rcmail->output->get_env('mimetype');
$url = $_GET;
$url[strpos($mimetype, 'text/') === 0 ? '_embed' : '_preload'] = 1;
unset($url['_frame']);
}
$url['_framed'] = 1; // For proper X-Frame-Options:deny handling
$attrib['src'] = $rcmail->url($url);
$rcmail->output->add_gui_object('messagepartframe', $attrib['id']);
return html::iframe($attrib);
}
}
@@ -0,0 +1,69 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Check all mailboxes for unread messages and update GUI |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_getunread extends rcmail_action_mail_index
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$a_folders = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
if (!empty($a_folders)) {
$current = $rcmail->storage->get_folder();
$inbox = $current == 'INBOX';
$trash = $rcmail->config->get('trash_mbox');
$check_all = (bool) $rcmail->config->get('check_all_folders');
foreach ($a_folders as $mbox) {
$unseen_old = self::get_unseen_count($mbox);
if (!$check_all && $unseen_old !== null && $mbox != $current) {
$unseen = $unseen_old;
}
else {
$unseen = $rcmail->storage->count($mbox, 'UNSEEN', $unseen_old === null);
}
// call it always for current folder, so it can update counter
// after possible message status change when opening a message
// not in preview frame
if ($unseen || $unseen_old === null || $mbox == $current) {
$rcmail->output->command('set_unread_count', $mbox, $unseen, $inbox && $mbox == 'INBOX');
}
self::set_unseen_count($mbox, $unseen);
// set trash folder state
if ($mbox === $trash) {
$rcmail->output->command('set_trash_count', $rcmail->storage->count($mbox, 'EXISTS'));
}
}
}
$rcmail->output->send();
}
}
@@ -0,0 +1,56 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Expand addressbook group into list of email addresses |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_group_expand extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET);
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$abook = $rcmail->get_address_book($source);
if ($gid && $abook) {
$abook->set_group($gid);
$abook->set_pagesize(9999); // TODO: limit number of group members by config?
$result = $abook->list_records($rcmail->config->get('contactlist_fields'));
$members = [];
while ($result && ($record = $result->iterate())) {
$email = array_first((array) $abook->get_col_values('email', $record, true));
if (!empty($email)) {
$members[] = format_email_recipient($email, rcube_addressbook::compose_list_name($record));
}
}
$rcmail->output->command('replace_group_recipients', $gid, join(', ', array_unique($members)));
}
$rcmail->output->send();
}
}
@@ -0,0 +1,87 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Fetch message headers in raw format for display |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_headers extends rcmail_action_mail_index
{
protected static $source;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GP);
$inline = $rcmail->output instanceof rcmail_output_html;
if (!$uid) {
exit;
}
if ($pos = strpos($uid, '.')) {
$message = new rcube_message($uid);
$source = $message->get_part_body(substr($uid, $pos + 1));
$source = substr($source, 0, strpos($source, "\r\n\r\n"));
}
else {
$source = $rcmail->storage->get_raw_headers($uid);
}
if ($source !== false) {
$source = trim(rcube_charset::clean($source));
$source = htmlspecialchars($source, ENT_COMPAT | ENT_HTML401, RCUBE_CHARSET);
$source = preg_replace(
[
'/\n[\t\s]+/',
'/^([a-z0-9_:-]+)/im',
'/\r?\n/'
],
[
"\n&nbsp;&nbsp;&nbsp;&nbsp;",
'<font class="bold">\1</font>',
'<br />'
],
$source
);
self::$source = $source;
$rcmail->output->add_handlers(['dialogcontent' => [$this, 'headers_output']]);
if ($inline) {
$rcmail->output->set_env('dialog_class', 'text-nowrap');
}
else {
$rcmail->output->command('set_headers', $source);
}
}
else if (!$inline) {
$rcmail->output->show_message('messageopenerror', 'error');
}
$rcmail->output->send($inline ? 'dialog' : null);
}
public static function headers_output()
{
return self::$source;
}
}
+200
View File
@@ -0,0 +1,200 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Save the uploaded file(s) as messages to the current IMAP folder |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_import extends rcmail_action
{
// only process ajax requests
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// clear all stored output properties (like scripts and env vars)
$rcmail->output->reset();
if (!empty($_FILES['_file']) && is_array($_FILES['_file'])) {
$imported = 0;
$folder = $rcmail->storage->get_folder();
foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) {
// Process uploaded file if there is no error
$err = $_FILES['_file']['error'][$i];
if (!$err) {
// check file content type first
$ctype = rcube_mime::file_content_type($filepath, $_FILES['_file']['name'][$i], $_FILES['_file']['type'][$i]);
list($mtype_primary, $mtype_secondary) = explode('/', $ctype);
if (in_array($ctype, ['application/zip', 'application/x-zip'])) {
$filepath = self::zip_extract($filepath);
if (empty($filepath)) {
continue;
}
}
else if (!in_array($mtype_primary, ['text', 'message'])) {
continue;
}
foreach ((array) $filepath as $file) {
// read the first few lines to detect header-like structure
$fp = fopen($file, 'r');
do {
$line = fgets($fp);
}
while ($line !== false && trim($line) == '');
if (!preg_match('/^From .+/', $line) && !preg_match('/^[a-z-_]+:\s+.+/i', $line)) {
continue;
}
$message = $lastline = '';
fseek($fp, 0);
while (($line = fgets($fp)) !== false) {
// importing mbox file, split by From - lines
if ($lastline === '' && strncmp($line, 'From ', 5) === 0 && strlen($line) > 5) {
if (!empty($message)) {
$imported += (int) self::save_message($folder, $message);
}
$message = $line;
$lastline = '';
continue;
}
$message .= $line;
$lastline = rtrim($line);
}
if (!empty($message)) {
$imported += (int) self::save_message($folder, $message);
}
// remove temp files extracted from zip
if (is_array($filepath)) {
unlink($file);
}
}
}
else {
self::upload_error($err);
}
}
if ($imported) {
$rcmail->output->show_message($rcmail->gettext(['name' => 'importmessagesuccess', 'nr' => $imported, 'vars' => ['nr' => $imported]]), 'confirmation');
$rcmail->output->command('command', 'list');
}
else {
$rcmail->output->show_message('importmessageerror', 'error');
}
}
else {
self::upload_failure();
}
// send html page with JS calls as response
$rcmail->output->send('iframe');
}
public static function zip_extract($path)
{
if (!class_exists('ZipArchive', false)) {
return;
}
$zip = new ZipArchive;
$files = [];
if ($zip->open($path)) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
$tmpfname = rcube_utils::temp_filename('zipimport');
if (copy("zip://$path#$entry", $tmpfname)) {
$ctype = rcube_mime::file_content_type($tmpfname, $entry);
list($mtype_primary, ) = explode('/', $ctype);
if (in_array($mtype_primary, ['text', 'message'])) {
$files[] = $tmpfname;
}
else {
unlink($tmpfname);
}
}
}
$zip->close();
}
return $files;
}
public static function save_message($folder, &$message)
{
$date = null;
if (strncmp($message, 'From ', 5) === 0) {
// Extract the mbox from_line
$pos = strpos($message, "\n");
$from = substr($message, 0, $pos);
$message = substr($message, $pos + 1);
// Read the received date, support only known date formats
// RFC4155: "Sat Jan 3 01:05:34 1996"
$mboxdate_rx = '/^([a-z]{3} [a-z]{3} [0-9 ][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4})/i';
// Roundcube/Zipdownload: "12-Dec-2016 10:56:33 +0100"
$imapdate_rx = '/^([0-9]{1,2}-[a-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9+-]{5})/i';
if (
($pos = strpos($from, ' ', 6))
&& ($dt_str = substr($from, $pos + 1))
&& (preg_match($mboxdate_rx, $dt_str, $m) || preg_match($imapdate_rx, $dt_str, $m))
) {
try {
$date = new DateTime($m[0], new DateTimeZone('UTC'));
}
catch (Exception $e) {
// ignore
}
}
}
// unquote ">From " lines in message body
$message = preg_replace('/\n>([>]*)From /', "\n\\1From ", $message);
$message = rtrim($message);
$rcmail = rcmail::get_instance();
if ($rcmail->storage->save_message($folder, $message, '', false, [], $date)) {
return true;
}
rcube::raise_error("Failed to import message to $folder", true, false);
return false;
}
}
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Send message list to client (as remote response) |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_list extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$save_arr = [];
$dont_override = (array) $rcmail->config->get('dont_override');
$cols = null;
// is there a sort type for this request?
$sort = rcube_utils::get_input_string('_sort', rcube_utils::INPUT_GET);
if ($sort && preg_match('/^[a-zA-Z_-]+$/', $sort)) {
// yes, so set the sort vars
list($sort_col, $sort_order) = explode('_', $sort);
// set session vars for sort (so next page and task switch know how to sort)
if (!in_array('message_sort_col', $dont_override)) {
$_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
}
if (!in_array('message_sort_order', $dont_override)) {
$_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
}
}
// is there a set of columns for this request?
if ($cols = rcube_utils::get_input_string('_cols', rcube_utils::INPUT_GET)) {
$_SESSION['list_attrib']['columns'] = explode(',', $cols);
if (!in_array('list_cols', $dont_override)) {
$save_arr['list_cols'] = explode(',', $cols);
}
}
// register layout change
if ($layout = rcube_utils::get_input_string('_layout', rcube_utils::INPUT_GET)) {
$rcmail->output->set_env('layout', $layout);
$save_arr['layout'] = $layout;
// force header replace on layout change
if (!empty($_SESSION['list_attrib']['columns'])) {
$cols = $_SESSION['list_attrib']['columns'];
}
}
if (!empty($save_arr)) {
$rcmail->user->save_prefs($save_arr);
}
$mbox_name = $rcmail->storage->get_folder();
$threading = (bool) $rcmail->storage->get_threading();
// Synchronize mailbox cache, handle flag changes
$rcmail->storage->folder_sync($mbox_name);
// fetch message headers
$a_headers = [];
if ($count = $rcmail->storage->count($mbox_name, $threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh']))) {
$a_headers = $rcmail->storage->list_messages($mbox_name, null, self::sort_column(), self::sort_order());
}
// update search set (possible change of threading mode)
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
&& $_SESSION['search_request'] == $_REQUEST['_search']
) {
$search_request = $_REQUEST['_search'];
$_SESSION['search'] = $rcmail->storage->get_search_set();
$multifolder = !empty($_SESSION['search']) && !empty($_SESSION['search'][1]->multi);
}
// remove old search data
else if (empty($_REQUEST['_search']) && isset($_SESSION['search'])) {
$rcmail->session->remove('search');
}
self::list_pagetitle();
// update mailboxlist
if (empty($search_request)) {
self::send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), empty($a_headers) ? 0 : null);
}
// update message count display
$pages = ceil($count / $rcmail->storage->get_pagesize());
$page = $count ? $rcmail->storage->get_page() : 1;
$exists = $rcmail->storage->count($mbox_name, 'EXISTS', true);
$rcmail->output->set_env('messagecount', $count);
$rcmail->output->set_env('pagecount', $pages);
$rcmail->output->set_env('threading', $threading);
$rcmail->output->set_env('current_page', $page);
$rcmail->output->set_env('exists', $exists);
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count), $mbox_name);
// remove old message rows if commanded by the client
if (!empty($_REQUEST['_clear'])) {
$rcmail->output->command('clear_message_list');
}
// add message rows
self::js_message_list($a_headers, false, $cols);
if (!empty($a_headers)) {
if (!empty($search_request)) {
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $count]);
}
// remember last HIGHESTMODSEQ value (if supported)
// we need it for flag updates in check-recent
$data = $rcmail->storage->folder_data($mbox_name);
if (!empty($data['HIGHESTMODSEQ'])) {
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
}
}
else {
// handle IMAP errors (e.g. #1486905)
if ($err_code = $rcmail->storage->get_error_code()) {
self::display_server_error();
}
else if (!empty($search_request)) {
$rcmail->output->show_message('searchnomatch', 'notice');
}
else {
$rcmail->output->show_message('nomessagesfound', 'notice');
}
}
// set trash folder state
if ($mbox_name === $rcmail->config->get('trash_mbox')) {
$rcmail->output->command('set_trash_count', $exists);
}
if ($page == 1) {
$rcmail->output->command('set_quota', self::quota_content(null, !empty($multifolder) ? 'INBOX' : $mbox_name));
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,204 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Send contacts list to client (as remote response) |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_list_contacts extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC);
$afields = $rcmail->config->get('contactlist_fields');
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$list_page = max(1, $_GET['_page'] ?? 0);
$jsresult = [];
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) {
$search = (array) $_SESSION['contact_search'][$_REQUEST['_search']];
$sparam = $_SESSION['contact_search_params']['id'] == $_REQUEST['_search'] ? $_SESSION['contact_search_params']['data'] : [];
$mode = (int) $rcmail->config->get('addressbook_search_mode');
$records = [];
// get records from all sources
foreach ($search as $s => $set) {
$CONTACTS = $rcmail->get_address_book($s);
// list matching groups of this source (on page one)
if ($sparam[1] && $CONTACTS->groups && $list_page == 1) {
$jsresult += self::compose_contact_groups($CONTACTS, $s, $sparam[1], $mode);
}
// reset page
$CONTACTS->set_page(1);
$CONTACTS->set_pagesize(9999);
$CONTACTS->set_search_set($set);
// get records
$result = $CONTACTS->list_records($afields);
while ($row = $result->next()) {
$row['sourceid'] = $s;
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
$records[$key] = $row;
}
unset($result);
}
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$first = ($list_page-1) * $page_size;
$result = new rcube_result_set($count, $first);
// we need only records for current page
if ($page_size < $count) {
$records = array_slice($records, $first, $page_size);
}
$result->records = array_values($records);
}
// list contacts from selected source
else {
$CONTACTS = $rcmail->get_address_book($source);
if ($CONTACTS && $CONTACTS->ready) {
// set list properties
$CONTACTS->set_pagesize($page_size);
$CONTACTS->set_page($list_page);
if ($group_id = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GET)) {
$CONTACTS->set_group($group_id);
}
// list groups of this source (on page one)
else if ($CONTACTS->groups && $CONTACTS->list_page == 1) {
$jsresult = self::compose_contact_groups($CONTACTS, $source);
}
// get contacts for this user
$result = $CONTACTS->list_records($afields);
}
}
if (!empty($result) && !$result->count && $result->searchonly) {
$rcmail->output->show_message('contactsearchonly', 'notice');
}
else if (!empty($result) && $result->count > 0) {
// create javascript list
while ($row = $result->next()) {
$name = rcube_addressbook::compose_list_name($row);
// add record for every email address of the contact
$emails = rcube_addressbook::get_col_values('email', $row, true);
foreach ($emails as $i => $email) {
$source = !empty($row['sourceid']) ? $row['sourceid'] : $source;
$row_id = $source.'-'.$row['ID'].'-'.$i;
$is_group = isset($row['_type']) && $row['_type'] == 'group';
$classname = $is_group ? 'group' : 'person';
$keyname = $is_group ? 'contactgroup' : 'contact';
$jsresult[$row_id] = format_email_recipient($email, $name);
$rcmail->output->command('add_contact_row', $row_id, [
$keyname => html::a(
['title' => $email],
rcube::Q($name ?: $email)
. ($name && count($emails) > 1 ? '&nbsp;' . html::span('email', rcube::Q($email)) : '')
)
],
$classname
);
}
}
}
// update env
$rcmail->output->set_env('contactdata', $jsresult);
$rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 1);
$rcmail->output->command('set_page_buttons');
// send response
$rcmail->output->send();
}
/**
* Add groups from the given address source to the address book widget
*/
public static function compose_contact_groups($abook, $source_id, $search = null, $search_mode = 0)
{
$rcmail = rcmail::get_instance();
$jsresult = [];
foreach ($abook->list_groups($search, $search_mode) as $group) {
$abook->reset();
$abook->set_group($group['ID']);
// group (distribution list) with email address(es)
if (!empty($group['email'])) {
foreach ((array) $group['email'] as $email) {
$row_id = 'G'.$group['ID'];
$jsresult[$row_id] = format_email_recipient($email, $group['name']);
$rcmail->output->command('add_contact_row', $row_id, [
'contactgroup' => html::span(['title' => $email], rcube::Q($group['name']))
], 'group');
}
}
// make virtual groups clickable to list their members
else if (!empty($group['virtual'])) {
$row_id = 'G'.$group['ID'];
$rcmail->output->command('add_contact_row', $row_id, [
'contactgroup' => html::a([
'href' => '#list',
'rel' => $group['ID'],
'title' => $rcmail->gettext('listgroup'),
'onclick' => sprintf("return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)",
rcmail_output::JS_OBJECT_NAME, $source_id, $group['ID']),
],
rcube::Q($group['name']) . '&nbsp;' . html::span('action', '&raquo;')
)],
'group',
['ID' => $group['ID'], 'name' => $group['name'], 'virtual' => true]
);
}
// show group with count
else if (($result = $abook->count()) && $result->count) {
$row_id = 'E'.$group['ID'];
$jsresult[$row_id] = ['name' => $group['name'], 'source' => $source_id];
$rcmail->output->command('add_contact_row', $row_id, [
'contactgroup' => rcube::Q($group['name'] . ' (' . intval($result->count) . ')')
], 'group');
}
}
$abook->reset();
$abook->set_group(0);
return $jsresult;
}
}
+196
View File
@@ -0,0 +1,196 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Mark the submitted messages with the specified flag |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_mark extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$_uids = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
$flag = rcube_utils::get_input_string('_flag', rcube_utils::INPUT_POST);
$folders = rcube_utils::get_input_string('_folders', rcube_utils::INPUT_POST);
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST);
if (empty($_uids) || empty($flag)) {
$rcmail->output->show_message('internalerror', 'error');
$rcmail->output->send();
}
$rcmail = rcmail::get_instance();
$threading = (bool) $rcmail->storage->get_threading();
$skip_deleted = (bool) $rcmail->config->get('skip_deleted');
$read_deleted = (bool) $rcmail->config->get('read_when_deleted');
$flag = self::imap_flag($flag);
$old_count = 0;
$from = $_POST['_from'] ?? null;
if ($flag == 'DELETED' && $skip_deleted && $from != 'show') {
// count messages before changing anything
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
}
if ($folders == 'all') {
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail');
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
}
else if ($folders == 'sub') {
$delim = $rcmail->storage->get_hierarchy_delimiter();
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
array_unshift($mboxes, $mbox);
$input = array_combine($mboxes, array_fill(0, count($mboxes), '*'));
}
else if ($folders == 'cur') {
$input = [$mbox => '*'];
}
else {
$input = self::get_uids(null, null, $dummy, rcube_utils::INPUT_POST);
}
$marked = 0;
$count = 0;
$read = 0;
foreach ($input as $mbox => $uids) {
$marked += (int) $rcmail->storage->set_flag($uids, $flag, $mbox);
$count += is_array($uids) ? count($uids) : 1;
}
if (!$marked) {
// send error message
if ($from != 'show') {
$rcmail->output->command('list_mailbox');
}
self::display_server_error('errormarking');
$rcmail->output->send();
}
else if (empty($_POST['_quiet'])) {
$rcmail->output->show_message('messagemarked', 'confirmation');
}
if ($flag == 'DELETED' && $read_deleted && !empty($_POST['_ruid'])) {
if ($ruids = rcube_utils::get_input_value('_ruid', rcube_utils::INPUT_POST)) {
foreach (self::get_uids($ruids) as $mbox => $uids) {
$read += (int) $rcmail->storage->set_flag($uids, 'SEEN', $mbox);
}
}
if ($read && !$skip_deleted) {
$rcmail->output->command('flag_deleted_as_read', $ruids);
}
}
if ($flag == 'SEEN' || $flag == 'UNSEEN' || ($flag == 'DELETED' && !$skip_deleted)) {
foreach ($input as $mbox => $uids) {
self::send_unread_count($mbox);
}
$rcmail->output->set_env('last_flag', $flag);
}
else if ($flag == 'DELETED' && $skip_deleted) {
if ($from == 'show') {
if ($next = rcube_utils::get_input_value('_next_uid', rcube_utils::INPUT_GPC)) {
$rcmail->output->command('show_message', $next);
}
else {
$rcmail->output->command('command', 'list');
}
}
else {
$search_request = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC);
// refresh saved search set after moving some messages
if ($search_request && $rcmail->storage->get_search_set()) {
$_SESSION['search'] = $rcmail->storage->refresh_search();
}
$msg_count = $rcmail->storage->count(NULL, $threading ? 'THREADS' : 'ALL');
$page_size = $rcmail->storage->get_pagesize();
$page = $rcmail->storage->get_page();
$pages = ceil($msg_count / $page_size);
$nextpage_count = $old_count - $page_size * $page;
$remaining = $msg_count - $page_size * ($page - 1);
$jump_back = false;
// jump back one page (user removed the whole last page)
if ($page > 1 && $remaining == 0) {
$page -= 1;
$rcmail->storage->set_page($page);
$_SESSION['page'] = $page;
$jump_back = true;
}
foreach ($input as $mbox => $uids) {
self::send_unread_count($mbox, true);
}
// update message count display
$rcmail->output->set_env('messagecount', $msg_count);
$rcmail->output->set_env('current_page', $page);
$rcmail->output->set_env('pagecount', $pages);
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
if ($threading) {
$count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST);
}
// add new rows from next page (if any)
if ($old_count && $_uids != '*' && ($jump_back || $nextpage_count > 0)) {
// #5862: Don't add more rows than it was on the next page
$count = $jump_back ? null : min($nextpage_count, $count);
$a_headers = $rcmail->storage->list_messages($mbox, null,
self::sort_column(), self::sort_order(), $count);
self::js_message_list($a_headers, false);
}
}
}
$rcmail->output->send();
}
/**
* Map Roundcube UI's flag label into IMAP flag
*
* @param string $flag Flag label
*
* @return string Uppercase IMAP flag
*/
public static function imap_flag($flag)
{
$flags_map = [
'undelete' => 'UNDELETED',
'delete' => 'DELETED',
'read' => 'SEEN',
'unread' => 'UNSEEN',
'flagged' => 'FLAGGED',
'unflagged' => 'UNFLAGGED',
];
return !empty($flags_map[$flag]) ? $flags_map[$flag] : strtoupper($flag);
}
}
+164
View File
@@ -0,0 +1,164 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handler for mail move operation |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_move extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// count messages before changing anything
$threading = (bool) $rcmail->storage->get_threading();
$trash = $rcmail->config->get('trash_mbox');
$old_count = 0;
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
$old_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
}
$target = rcube_utils::get_input_string('_target_mbox', rcube_utils::INPUT_POST, true);
if (empty($_POST['_uid']) || !strlen($target)) {
$rcmail->output->show_message('internalerror', 'error');
$rcmail->output->send();
}
$success = true;
$addrows = false;
$count = 0;
$sources = [];
foreach (rcmail::get_uids(null, null, $multifolder, rcube_utils::INPUT_POST) as $mbox => $uids) {
if ($mbox === $target) {
$count += is_array($uids) ? count($uids) : 1;
}
else if ($rcmail->storage->move_message($uids, $target, $mbox)) {
$count += is_array($uids) ? count($uids) : 1;
$sources[] = $mbox;
}
else {
$success = false;
}
}
if (!$success) {
// send error message
if (empty($_POST['_from']) || $_POST['_from'] != 'show') {
$rcmail->output->command('list_mailbox');
}
self::display_server_error('errormoving', null, $target == $trash ? 'delete' : '');
$rcmail->output->send();
}
else {
$rcmail->output->show_message($target == $trash ? 'messagemovedtotrash' : 'messagemoved', 'confirmation');
}
if (!empty($_POST['_refresh'])) {
// FIXME: send updated message rows instead of reloading the entire list
$rcmail->output->command('refresh_list');
}
else {
$addrows = true;
}
$search_request = rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC);
// refresh saved search set after moving some messages
if ($search_request && $rcmail->storage->get_search_set()) {
$_SESSION['search'] = $rcmail->storage->refresh_search();
}
if (!empty($_POST['_from']) && $_POST['_from'] == 'show') {
if ($next = rcube_utils::get_input_string('_next_uid', rcube_utils::INPUT_GPC)) {
$rcmail->output->command('show_message', $next);
}
else {
$rcmail->output->command('command', 'list');
}
$rcmail->output->send();
}
$mbox = $rcmail->storage->get_folder();
$msg_count = $rcmail->storage->count(null, $threading ? 'THREADS' : 'ALL');
$exists = $rcmail->storage->count($mbox, 'EXISTS', true);
$page_size = $rcmail->storage->get_pagesize();
$page = $rcmail->storage->get_page();
$pages = ceil($msg_count / $page_size);
$nextpage_count = $old_count - $page_size * $page;
$remaining = $msg_count - $page_size * ($page - 1);
// jump back one page (user removed the whole last page)
if ($page > 1 && $remaining == 0) {
$page -= 1;
$rcmail->storage->set_page($page);
$_SESSION['page'] = $page;
$jump_back = true;
}
// update unseen messages counts for all involved folders
foreach ($sources as $source) {
self::send_unread_count($source, true);
}
self::send_unread_count($target, true);
// update message count display
$rcmail->output->set_env('messagecount', $msg_count);
$rcmail->output->set_env('current_page', $page);
$rcmail->output->set_env('pagecount', $pages);
$rcmail->output->set_env('exists', $exists);
$rcmail->output->command('set_quota', self::quota_content(null, $multifolder ? $sources[0] : 'INBOX'));
$rcmail->output->command('set_rowcount', self::get_messagecount_text($msg_count), $mbox);
if ($threading) {
$count = rcube_utils::get_input_string('_count', rcube_utils::INPUT_POST);
}
// add new rows from next page (if any)
if ($addrows && $count && $_POST['_uid'] != '*' && (!empty($jump_back) || $nextpage_count > 0)) {
// #5862: Don't add more rows than it was on the next page
$count = !empty($jump_back) ? null : min($nextpage_count, $count);
$a_headers = $rcmail->storage->list_messages($mbox, NULL,
self::sort_column(), self::sort_order(), $count);
self::js_message_list($a_headers, false);
}
// set trash folder state
if ($mbox === $trash) {
$rcmail->output->command('set_trash_count', $exists);
}
else if ($target === $trash) {
$rcmail->output->command('set_trash_count', $rcmail->storage->count($trash, 'EXISTS', true));
}
// send response
$rcmail->output->send();
}
}
@@ -0,0 +1,78 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Updates message page navigation controls |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_pagenav extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
$index = $rcmail->storage->index(null, self::sort_column(), self::sort_order());
$cnt = $index->count_messages();
if ($cnt && ($pos = $index->exists($uid, true)) !== false) {
$prev = $pos ? $index->get_element($pos-1) : 0;
$first = $pos ? $index->get_element('FIRST') : 0;
$next = $pos < $cnt-1 ? $index->get_element($pos+1) : 0;
$last = $pos < $cnt-1 ? $index->get_element('LAST') : 0;
}
else {
// error, this will at least disable page navigation
$rcmail->output->command('set_rowcount', '');
$rcmail->output->send();
}
// Set UIDs and activate navigation buttons
if (!empty($prev)) {
$rcmail->output->set_env('prev_uid', $prev);
$rcmail->output->command('enable_command', 'previousmessage', 'firstmessage', true);
}
if (!empty($next)) {
$rcmail->output->set_env('next_uid', $next);
$rcmail->output->command('enable_command', 'nextmessage', 'lastmessage', true);
}
if (!empty($first)) {
$rcmail->output->set_env('first_uid', $first);
}
if (!empty($last)) {
$rcmail->output->set_env('last_uid', $last);
}
// Don't need a real messages count value
$rcmail->output->set_env('messagecount', 1);
// Set rowcount text
$rcmail->output->command('set_rowcount', $rcmail->gettext([
'name' => 'messagenrof',
'vars' => ['nr' => ($pos ?? 0) + 1, 'count' => $cnt]
]));
$rcmail->output->send();
}
}
+287
View File
@@ -0,0 +1,287 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Mail messages search action |
+-----------------------------------------------------------------------+
| Author: Benjamin Smith <defitro@gmail.com> |
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_search extends rcmail_action_mail_index
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
@set_time_limit(170); // extend default max_execution_time to ~3 minutes
// reset list_page and old search results
$rcmail->storage->set_page(1);
$rcmail->storage->set_search_set(null);
$_SESSION['page'] = 1;
// get search string
$str = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GET, true);
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GET, true);
$filter = rcube_utils::get_input_string('_filter', rcube_utils::INPUT_GET);
$headers = rcube_utils::get_input_string('_headers', rcube_utils::INPUT_GET);
$scope = rcube_utils::get_input_string('_scope', rcube_utils::INPUT_GET);
$interval = rcube_utils::get_input_string('_interval', rcube_utils::INPUT_GET);
$continue = rcube_utils::get_input_string('_continue', rcube_utils::INPUT_GET);
$filter = trim((string) $filter);
$search_request = md5($mbox . $scope . $interval . $filter . $str);
// Parse input
list($subject, $search) = self::search_input($str, $headers, $scope, $mbox);
// add list filter string
$search_str = $filter && $filter != 'ALL' ? $filter : '';
if ($search_interval = self::search_interval_criteria($interval)) {
$search_str .= ' ' . $search_interval;
}
if (!empty($subject)) {
$search_str .= str_repeat(' OR', count($subject)-1);
foreach ($subject as $sub) {
$search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search);
}
}
$search_str = trim($search_str);
$sort_column = self::sort_column();
$sort_order = self::sort_order();
// set message set for already stored (but incomplete) search request
if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
$rcmail->storage->set_search_set($_SESSION['search']);
$search_str = $_SESSION['search'][0];
}
// execute IMAP search
if ($search_str) {
$mboxes = [];
// search all, current or subfolders folders
if ($scope == 'all') {
$mboxes = $rcmail->storage->list_folders_subscribed('', '*', 'mail', null, true);
// we want natural alphabetic sorting of folders in the result set
natcasesort($mboxes);
}
else if ($scope == 'sub') {
$delim = $rcmail->storage->get_hierarchy_delimiter();
$mboxes = $rcmail->storage->list_folders_subscribed($mbox . $delim, '*', 'mail');
array_unshift($mboxes, $mbox);
}
if ($scope != 'all') {
// Remember current folder, it can change in meantime (plugins)
// but we need it to e.g. recognize Sent folder to handle From/To column later
$rcmail->output->set_env('mailbox', $mbox);
}
$result = $rcmail->storage->search($mboxes, $search_str, RCUBE_CHARSET, $sort_column);
}
// save search results in session
if (!isset($_SESSION['search']) || !is_array($_SESSION['search'])) {
$_SESSION['search'] = [];
}
if ($search_str) {
$_SESSION['search'] = $rcmail->storage->get_search_set();
$_SESSION['last_text_search'] = $str;
}
$_SESSION['search_request'] = $search_request;
$_SESSION['search_scope'] = $scope;
$_SESSION['search_interval'] = $interval;
$_SESSION['search_filter'] = $filter;
// Get the headers
if (!isset($result) || empty($result->incomplete)) {
$result_h = $rcmail->storage->list_messages($mbox, 1, $sort_column, $sort_order);
}
// Make sure we got the headers
if (!empty($result_h)) {
$count = $rcmail->storage->count($mbox, $rcmail->storage->get_threading() ? 'THREADS' : 'ALL');
self::js_message_list($result_h, false);
if ($search_str) {
$all_count = $rcmail->storage->count(null, 'ALL');
$rcmail->output->show_message('searchsuccessful', 'confirmation', ['nr' => $all_count]);
}
// remember last HIGHESTMODSEQ value (if supported)
// we need it for flag updates in check-recent
if ($mbox !== null) {
$data = $rcmail->storage->folder_data($mbox);
if (!empty($data['HIGHESTMODSEQ'])) {
$_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
}
}
}
// handle IMAP errors (e.g. #1486905)
else if ($err_code = $rcmail->storage->get_error_code()) {
$count = 0;
self::display_server_error();
}
// advice the client to re-send the (cross-folder) search request
else if (!empty($result) && !empty($result->incomplete)) {
$count = 0; // keep UI locked
$rcmail->output->command('continue_search', $search_request);
}
else {
$count = 0;
$rcmail->output->show_message('searchnomatch', 'notice');
$rcmail->output->set_env('multifolder_listing', isset($result) ? !empty($result->multi) : false);
if (isset($result) && !empty($result->multi) && $scope == 'all') {
$rcmail->output->command('select_folder', '');
}
}
// update message count display
$rcmail->output->set_env('search_request', $search_str ? $search_request : '');
$rcmail->output->set_env('search_filter', $_SESSION['search_filter']);
$rcmail->output->set_env('messagecount', $count);
$rcmail->output->set_env('pagecount', ceil($count / $rcmail->storage->get_pagesize()));
$rcmail->output->set_env('exists', $mbox === null ? 0 : $rcmail->storage->count($mbox, 'EXISTS'));
$rcmail->output->command('set_rowcount', self::get_messagecount_text($count, 1), $mbox);
self::list_pagetitle();
// update unseen messages count
if ($search_str === '') {
self::send_unread_count($mbox, false, empty($result_h) ? 0 : null);
}
if (isset($result) && empty($result->incomplete)) {
$rcmail->output->command('set_quota', self::quota_content(null, !empty($result->multi) ? 'INBOX' : $mbox));
}
$rcmail->output->send();
}
/**
* Creates BEFORE/SINCE search criteria from the specified interval
* Interval can be: 1W, 1M, 1Y, -1W, -1M, -1Y
*/
public static function search_interval_criteria($interval)
{
if (empty($interval)) {
return;
}
if ($interval[0] == '-') {
$search = 'BEFORE';
$interval = substr($interval, 1);
}
else {
$search = 'SINCE';
}
$date = new DateTime('now');
$interval = new DateInterval('P' . $interval);
$date->sub($interval);
return $search . ' ' . $date->format('j-M-Y');
}
/**
* Parse search input.
*
* @param string $str Search string
* @param string $headers Comma-separated list of headers/fields to search in
* @param string $scope Search scope (all | base | sub)
* @param string $mbox Folder name
*
* @return array Search criteria (1st element) and search value (2nd element)
*/
public static function search_input($str, $headers, $scope, $mbox)
{
$rcmail = rcmail::get_instance();
$subject = [];
$srch = null;
$supported = ['subject', 'from', 'to', 'cc', 'bcc'];
// Check the search string for type of search
if (preg_match("/^(from|to|reply-to|cc|bcc|subject):.*/i", $str, $m)) {
list(, $srch) = explode(":", $str);
$subject[$m[1]] = 'HEADER ' . strtoupper($m[1]);
}
else if (preg_match("/^body:.*/i", $str)) {
list(, $srch) = explode(":", $str);
$subject['body'] = 'BODY';
}
else if (strlen(trim($str))) {
if ($headers) {
foreach (explode(',', $headers) as $header) {
switch ($header) {
case 'text':
// #1488208: get rid of other headers when searching by "TEXT"
$subject = ['text' => 'TEXT'];
break 2;
case 'body':
$subject['body'] = 'BODY';
break;
case 'replyto':
case 'reply-to':
$subject['reply-to'] = 'HEADER REPLY-TO';
$subject['mail-reply-to'] = 'HEADER MAIL-REPLY-TO';
break;
case 'followupto':
case 'followup-to':
$subject['followup-to'] = 'HEADER FOLLOWUP-TO';
$subject['mail-followup-to'] = 'HEADER MAIL-FOLLOWUP-TO';
break;
default:
if (in_array_nocase($header, $supported)) {
$subject[$header] = 'HEADER ' . strtoupper($header);
}
}
}
// save search modifiers for the current folder to user prefs
if ($scope != 'all') {
$search_mods = self::search_mods();
$search_mods_value = array_fill_keys(array_keys($subject), 1);
if (!isset($search_mods[$mbox]) || $search_mods[$mbox] != $search_mods_value) {
$search_mods[$mbox] = $search_mods_value;
$rcmail->user->save_prefs(['search_mods' => $search_mods]);
}
}
}
else {
// search in subject by default
$subject['subject'] = 'HEADER SUBJECT';
}
}
return [$subject, isset($srch) ? trim($srch) : trim($str)];
}
}
@@ -0,0 +1,139 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Search contacts from the address book widget |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_search_contacts extends rcmail_action_mail_list_contacts
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$search = rcube_utils::get_input_string('_q', rcube_utils::INPUT_GPC, true);
$sources = $rcmail->get_address_sources();
$search_mode = (int) $rcmail->config->get('addressbook_search_mode');
$addr_sort_col = $rcmail->config->get('addressbook_sort_col', 'name');
$afields = $rcmail->config->get('contactlist_fields');
$page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50));
$records = [];
$search_set = [];
$jsresult = [];
$search_mode |= rcube_addressbook::SEARCH_GROUPS;
foreach ($sources as $s) {
$source = $rcmail->get_address_book($s['id']);
$source->set_page(1);
$source->set_pagesize(9999);
// list matching groups of this source
if ($source->groups) {
$jsresult += self::compose_contact_groups($source, $s['id'], $search, $search_mode);
}
// get contacts count
$result = $source->search($afields, $search, $search_mode, true, true, 'email');
if (!$result->count) {
continue;
}
while ($row = $result->next()) {
$row['sourceid'] = $s['id'];
$key = rcube_addressbook::compose_contact_key($row, $addr_sort_col);
$records[$key] = $row;
}
$search_set[$s['id']] = $source->get_search_set();
unset($result);
}
$group_count = count($jsresult);
// sort the records
ksort($records, SORT_LOCALE_STRING);
// create resultset object
$count = count($records);
$result = new rcube_result_set($count);
// select the requested page
if ($page_size < $count) {
$records = array_slice($records, $result->first, $page_size);
}
$result->records = array_values($records);
if (!empty($result) && $result->count > 0) {
// create javascript list
while ($row = $result->next()) {
$name = rcube_addressbook::compose_list_name($row);
$is_group = isset($row['_type']) && $row['_type'] == 'group';
$classname = $is_group ? 'group' : 'person';
$keyname = $is_group ? 'contactgroup' : 'contact';
// add record for every email address of the contact
// (same as in list_contacts.inc)
$emails = rcube_addressbook::get_col_values('email', $row, true);
foreach ($emails as $i => $email) {
$row_id = $row['sourceid'].'-'.$row['ID'].'-'.$i;
$jsresult[$row_id] = format_email_recipient($email, $name);
$title = rcube_addressbook::compose_search_name($row, $email, $name);
$link_content = rcube::Q($name ?: $email);
if ($name && count($emails) > 1) {
$link_content .= '&nbsp;' . html::span('email', rcube::Q($email));
}
$link = html::a(['title' => $title], $link_content);
$rcmail->output->command('add_contact_row', $row_id, [$keyname => $link], $classname);
}
}
// search request ID
$search_request = md5('composeaddr' . $search);
// save search settings in session
$_SESSION['contact_search'][$search_request] = $search_set;
$_SESSION['contact_search_params'] = ['id' => $search_request, 'data' => [$afields, $search]];
$rcmail->output->show_message('contactsearchsuccessful', 'confirmation', ['nr' => $result->count]);
$rcmail->output->set_env('search_request', $search_request);
$rcmail->output->set_env('source', '');
$rcmail->output->command('unselect_directory');
}
else if (!$group_count) {
$rcmail->output->show_message('nocontactsfound', 'notice');
}
// update env
$rcmail->output->set_env('contactdata', $jsresult);
$rcmail->output->set_env('pagecount', ceil($result->count / $page_size));
$rcmail->output->command('set_page_buttons');
// send response
$rcmail->output->send();
}
}
+424
View File
@@ -0,0 +1,424 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Compose a new mail message and send it or store as draft |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_send extends rcmail_action
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
// remove all scripts and act as called in frame
$rcmail->output->reset();
$rcmail->output->framed = true;
$COMPOSE_ID = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GPC);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
// Sanity checks
if (!isset($COMPOSE['id'])) {
rcube::raise_error([
'code' => 500,
'file' => __FILE__,
'line' => __LINE__,
'message' => "Invalid compose ID"
], true, false
);
$rcmail->output->show_message('internalerror', 'error');
$rcmail->output->send('iframe');
}
$saveonly = !empty($_GET['_saveonly']);
$savedraft = !empty($_POST['_draft']) && !$saveonly;
$SENDMAIL = new rcmail_sendmail($COMPOSE, [
'sendmail' => true,
'saveonly' => $saveonly,
'savedraft' => $savedraft,
'error_handler' => function(...$args) use ($rcmail) {
call_user_func_array([$rcmail->output, 'show_message'], $args);
$rcmail->output->send('iframe');
},
'keepformatting' => !empty($_POST['_keepformatting']),
]);
if (!isset($COMPOSE['attachments'])) {
$COMPOSE['attachments'] = [];
}
// Collect input for message headers
$headers = $SENDMAIL->headers_input();
$COMPOSE['param']['message-id'] = $headers['Message-ID'];
$message_id = $headers['Message-ID'];
$message_charset = $SENDMAIL->options['charset'];
$message_body = rcube_utils::get_input_string('_message', rcube_utils::INPUT_POST, true, $message_charset);
$isHtml = (bool) rcube_utils::get_input_string('_is_html', rcube_utils::INPUT_POST);
// Reset message body and attachments in Mailvelope mode
if (isset($_POST['_pgpmime'])) {
$pgp_mime = rcube_utils::get_input_string('_pgpmime', rcube_utils::INPUT_POST);
$isHtml = false;
$message_body = '';
// clear unencrypted attachments
if (!empty($COMPOSE['attachments'])) {
foreach ((array) $COMPOSE['attachments'] as $attach) {
$rcmail->plugins->exec_hook('attachment_delete', $attach);
}
}
$COMPOSE['attachments'] = [];
}
if ($isHtml) {
$bstyle = [];
if ($font_size = $rcmail->config->get('default_font_size')) {
$bstyle[] = 'font-size: ' . $font_size;
}
if ($font_family = $rcmail->config->get('default_font')) {
$bstyle[] = 'font-family: ' . self::font_defs($font_family);
}
// append doctype and html/body wrappers
$bstyle = !empty($bstyle) ? (" style='" . implode('; ', $bstyle) . "'") : '';
$message_body = '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset='
. ($message_charset ?: RCUBE_CHARSET) . '" /></head>'
. "<body" . $bstyle . ">\r\n" . $message_body;
}
if (!$savedraft) {
if ($isHtml) {
$b_style = 'padding: 0 0.4em; border-left: #1010ff 2px solid; margin: 0';
$pre_style = 'margin: 0; padding: 0; font-family: monospace';
$message_body = preg_replace(
[
// remove empty signature div
'/<div id="_rc_sig">(&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>/',
],
[
'',
' id="signature"',
'<blockquote type="cite" style="'.$b_style.'">',
'<div class="pre" style="'.$pre_style.'">',
'<p><br /></p>',
],
$message_body
);
rcube_utils::preg_error([
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not format HTML!"
], true);
}
// Check spelling before send
if (
$rcmail->config->get('spellcheck_before_send')
&& $rcmail->config->get('enable_spellcheck')
&& empty($COMPOSE['spell_checked'])
&& !empty($message_body)
) {
$language = rcube_utils::get_input_string('_lang', rcube_utils::INPUT_GPC);
$message_body = str_replace("\r\n", "\n", $message_body);
$spellchecker = new rcube_spellchecker($language);
$spell_result = $spellchecker->check($message_body, $isHtml);
if ($error = $spellchecker->error()) {
rcube::raise_error([
'code' => 500, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Spellcheck error: " . $error
],
true, false
);
}
else {
$COMPOSE['spell_checked'] = true;
if (!$spell_result) {
if ($isHtml) {
$result['words'] = $spellchecker->get();
$result['dictionary'] = (bool) $rcmail->config->get('spellcheck_dictionary');
}
else {
$result = $spellchecker->get_xml();
}
$rcmail->output->show_message('mispellingsfound', 'error');
$rcmail->output->command('spellcheck_resume', $result);
$rcmail->output->send('iframe');
}
}
}
// generic footer for all messages
if ($footer = $SENDMAIL->generic_message_footer($isHtml)) {
$message_body .= "\r\n" . $footer;
}
}
if ($isHtml) {
$message_body .= "\r\n</body></html>\r\n";
}
// sort attachments to make sure the order is the same as in the UI (#1488423)
if ($files = rcube_utils::get_input_string('_attachments', rcube_utils::INPUT_POST)) {
$files = explode(',', $files);
$files = array_flip($files);
foreach ($files as $idx => $val) {
if (!empty($COMPOSE['attachments'][$idx])) {
$files[$idx] = $COMPOSE['attachments'][$idx];
unset($COMPOSE['attachments'][$idx]);
}
}
$COMPOSE['attachments'] = array_merge(array_filter($files), (array) $COMPOSE['attachments']);
}
// Since we can handle big messages with disk usage, we need more time to work
@set_time_limit(360);
// create PEAR::Mail_mime instance, set headers, body and params
$MAIL_MIME = $SENDMAIL->create_message($headers, $message_body, $isHtml, $COMPOSE['attachments']);
// add stored attachments, if any
if (is_array($COMPOSE['attachments'])) {
self::add_attachments($SENDMAIL, $MAIL_MIME, $COMPOSE['attachments'], $isHtml);
}
// compose PGP/Mime message
if (!empty($pgp_mime)) {
$MAIL_MIME->addAttachment(new Mail_mimePart('Version: 1', [
'content_type' => 'application/pgp-encrypted',
'description' => 'PGP/MIME version identification',
]));
$MAIL_MIME->addAttachment(new Mail_mimePart($pgp_mime, [
'content_type' => 'application/octet-stream',
'filename' => 'encrypted.asc',
'disposition' => 'inline',
]));
$MAIL_MIME->setContentType('multipart/encrypted', ['protocol' => 'application/pgp-encrypted']);
$MAIL_MIME->setParam('preamble', 'This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)');
}
// This hook allows to modify the message before send or save action
$plugin = $rcmail->plugins->exec_hook('message_ready', ['message' => $MAIL_MIME]);
$MAIL_MIME = $plugin['message'];
// Deliver the message over SMTP
if (!$savedraft && !$saveonly) {
$sent = $SENDMAIL->deliver_message($MAIL_MIME);
}
// Save the message in Drafts/Sent
$saved = $SENDMAIL->save_message($MAIL_MIME);
// raise error if saving failed
if (!$saved && $savedraft) {
self::display_server_error('errorsaving');
// start the auto-save timer again
$rcmail->output->command('auto_save_start');
$rcmail->output->send('iframe');
}
$store_target = $SENDMAIL->options['store_target'];
$store_folder = $SENDMAIL->options['store_folder'];
// delete previous saved draft
$drafts_mbox = $rcmail->config->get('drafts_mbox');
$old_id = rcube_utils::get_input_string('_draft_saveid', rcube_utils::INPUT_POST);
if ($old_id && (!empty($sent) || $saved)) {
$deleted = $rcmail->storage->delete_message($old_id, $drafts_mbox);
// raise error if deletion of old draft failed
if (!$deleted) {
rcube::raise_error([
'code' => 800,
'type' => 'imap',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not delete message from $drafts_mbox"
],
true, false
);
}
}
if ($savedraft) {
// remember new draft-uid ($saved could be an UID or true/false here)
if ($saved && is_bool($saved)) {
$index = $rcmail->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
$saved = $index->max();
}
if ($saved) {
$plugin = $rcmail->plugins->exec_hook('message_draftsaved', [
'msgid' => $message_id,
'uid' => $saved,
'folder' => $store_target
]);
// display success
$rcmail->output->show_message(!empty($plugin['message']) ? $plugin['message'] : 'messagesaved', 'confirmation');
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
$COMPOSE['param']['draft_uid'] = $plugin['uid'];
$rcmail->output->command('set_draft_id', $plugin['uid']);
$rcmail->output->command('compose_field_hash', true);
}
// start the auto-save timer again
$rcmail->output->command('auto_save_start');
}
else {
// Collect folders which could contain the composed message,
// we'll refresh the list if currently opened folder is one of them (#1490238)
$folders = [];
$save_error = false;
if (!$saveonly) {
if (in_array($COMPOSE['mode'], ['reply', 'forward', 'draft'])) {
$folders[] = $COMPOSE['mailbox'];
}
if (!empty($COMPOSE['param']['draft_uid']) && $drafts_mbox) {
$folders[] = $drafts_mbox;
}
}
if ($store_folder && !$saved) {
$params = $saveonly ? null : ['prefix' => true];
self::display_server_error('errorsavingsent', null, null, $params);
if ($saveonly) {
$rcmail->output->send('iframe');
}
$save_error = true;
}
else {
$rcmail->plugins->exec_hook('attachments_cleanup', ['group' => $COMPOSE_ID]);
$rcmail->session->remove('compose_data_' . $COMPOSE_ID);
$_SESSION['last_compose_session'] = $COMPOSE_ID;
$rcmail->output->command('remove_compose_data', $COMPOSE_ID);
if ($store_folder) {
$folders[] = $store_target;
}
}
$msg = $rcmail->gettext($saveonly ? 'successfullysaved' : 'messagesent');
$rcmail->output->command('sent_successfully', 'confirmation', $msg, $folders, $save_error);
}
$rcmail->output->send('iframe');
}
public static function add_attachments($SENDMAIL, $message, $attachments, $isHtml)
{
$rcmail = rcmail::get_instance();
foreach ($attachments as $id => $attachment) {
// This hook retrieves the attachment contents from the file storage backend
$attachment = $rcmail->plugins->exec_hook('attachment_get', $attachment);
$is_inline = false;
$dispurl = null;
if ($isHtml) {
$dispurl = '/[\'"]\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\'"]/';
$message_body = $message->getHTMLBody();
$is_inline = preg_match($dispurl, $message_body);
}
$ctype = isset($attachment['mimetype']) ? $attachment['mimetype'] : '';
$ctype = str_replace('image/pjpeg', 'image/jpeg', $ctype); // #1484914
// inline image
if ($is_inline) {
// Mail_Mime does not support many inline attachments with the same name (#1489406)
// we'll generate cid: urls here to workaround this
$cid = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true));
if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $SENDMAIL->options['from'], $matches)) {
$cid .= $matches[1];
}
else {
$cid .= '@localhost';
}
if ($dispurl && !empty($message_body)) {
$message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
rcube_utils::preg_error([
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not replace an image reference!"
], true
);
$message->setHTMLBody($message_body);
}
if (!empty($attachment['data'])) {
$message->addHTMLImage($attachment['data'], $ctype, $attachment['name'], false, $cid);
}
else {
$message->addHTMLImage($attachment['path'], $ctype, $attachment['name'], true, $cid);
}
}
else {
$file = !empty($attachment['data']) ? $attachment['data'] : $attachment['path'];
$folding = (int) $rcmail->config->get('mime_param_folding');
$message->addAttachment($file,
$ctype,
$attachment['name'],
empty($attachment['data']),
$ctype == 'message/rfc822' ? '8bit' : 'base64',
'attachment',
$attachment['charset'] ?? null,
'', '',
$folding ? 'quoted-printable' : null,
$folding == 2 ? 'quoted-printable' : null,
'', RCUBE_CHARSET
);
}
}
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Send a message disposition notification for a specific mail |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_sendmdn extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_POST)) {
$sent = self::send_mdn($uid, $smtp_error);
}
// show either confirm or error message
if (!empty($sent)) {
$rcmail->output->set_env('mdn_request', false);
$rcmail->output->show_message('receiptsent', 'confirmation');
}
else if (!empty($smtp_error) && is_string($smtp_error)) {
$rcmail->output->show_message($smtp_error, 'error');
}
else if (!empty($smtp_error) && !empty($smtp_error['label'])) {
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
}
else {
$rcmail->output->show_message('errorsendingreceipt', 'error');
}
// Redirect to 'addcontact' action to save the sender address
if (!empty($_POST['_save'])) {
if ($_POST['_save'] == 5) {
$_POST['_source'] = rcube_addressbook::TYPE_TRUSTED_SENDER;
}
$rcmail->action = 'addcontact';
return;
}
$rcmail->output->send();
}
/**
* Send the MDN response
*
* @param mixed $message Original message object (rcube_message) or UID
* @param array|string $smtp_error SMTP error array or (deprecated) string
*
* @return boolean Send status
*/
public static function send_mdn($message, &$smtp_error)
{
$rcmail = rcmail::get_instance();
if (!is_object($message) || !is_a($message, 'rcube_message')) {
$message = new rcube_message($message);
}
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
) {
$charset = $message->headers->charset;
$identity = rcmail_sendmail::identity_select($message);
$sender = format_email_recipient($identity['email'], $identity['name']);
$recipient = array_first(rcube_mime::decode_address_list($message->headers->mdn_to, 1, true, $charset));
$mailto = $recipient['mailto'];
$compose = new Mail_mime("\r\n");
$compose->setParam('text_encoding', 'quoted-printable');
$compose->setParam('html_encoding', 'quoted-printable');
$compose->setParam('head_encoding', 'quoted-printable');
$compose->setParam('head_charset', RCUBE_CHARSET);
$compose->setParam('html_charset', RCUBE_CHARSET);
$compose->setParam('text_charset', RCUBE_CHARSET);
// compose headers array
$headers = [
'Date' => $rcmail->user_date(),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => $rcmail->gettext('receiptread') . ': ' . $message->subject,
'Message-ID' => $rcmail->gen_message_id($identity['email']),
'X-Sender' => $identity['email'],
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
'In-Reply-To' => $message->headers->messageID,
];
$report = "Final-Recipient: rfc822; {$identity['email']}\r\n"
. "Original-Message-ID: {$message->headers->messageID}\r\n"
. "Disposition: manual-action/MDN-sent-manually; displayed\r\n";
if ($message->headers->to) {
$report .= "Original-Recipient: {$message->headers->to}\r\n";
}
if ($agent = $rcmail->config->get('useragent')) {
$headers['User-Agent'] = $agent;
$report .= "Reporting-UA: $agent\r\n";
}
$to = rcube_mime::decode_mime_string($message->headers->to, $charset);
$date = $rcmail->format_date($message->headers->date, $rcmail->config->get('date_long'));
$body = $rcmail->gettext("yourmessage") . "\r\n\r\n" .
"\t" . $rcmail->gettext("to") . ": {$to}\r\n" .
"\t" . $rcmail->gettext("subject") . ": {$message->subject}\r\n" .
"\t" . $rcmail->gettext("date") . ": {$date}\r\n" .
"\r\n" . $rcmail->gettext("receiptnote");
$compose->headers(array_filter($headers));
$compose->setContentType('multipart/report', ['report-type'=> 'disposition-notification']);
$compose->setTXTBody(rcube_mime::wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
// SMTP options
$options = ['mdn_use_from' => (bool) $rcmail->config->get('mdn_use_from')];
$sent = $rcmail->deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options, true);
if ($sent) {
$rcmail->storage->set_flag($message->uid, 'MDNSENT');
return true;
}
}
return false;
}
}
+953
View File
@@ -0,0 +1,953 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Display a mail message similar as a usual mail application does |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_show extends rcmail_action_mail_index
{
protected static $MESSAGE;
protected static $CLIENT_MIMETYPES = [];
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
self::$PRINT_MODE = $rcmail->action == 'print';
// Read browser capabilities and store them in session
if ($caps = rcube_utils::get_input_string('_caps', rcube_utils::INPUT_GET)) {
$browser_caps = [];
foreach (explode(',', $caps) as $cap) {
$cap = explode('=', $cap);
$browser_caps[$cap[0]] = $cap[1];
}
$_SESSION['browser_caps'] = $browser_caps;
}
$msg_id = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET);
$uid = preg_replace('/\.[0-9.]+$/', '', $msg_id);
$mbox_name = $rcmail->storage->get_folder();
// similar code as in program/steps/mail/get.inc
if ($uid) {
// set message format (need to be done before rcube_message construction)
if (!empty($_GET['_format'])) {
$prefer_html = $_GET['_format'] == 'html';
$rcmail->config->set('prefer_html', $prefer_html);
$_SESSION['msg_formats'][$mbox_name.':'.$uid] = $prefer_html;
}
else if (isset($_SESSION['msg_formats'][$mbox_name.':'.$uid])) {
$rcmail->config->set('prefer_html', $_SESSION['msg_formats'][$mbox_name.':'.$uid]);
}
$MESSAGE = new rcube_message($msg_id, $mbox_name, !empty($_GET['_safe']));
self::$MESSAGE = $MESSAGE;
// if message not found (wrong UID)...
if (empty($MESSAGE->headers)) {
self::message_error();
}
self::$CLIENT_MIMETYPES = self::supported_mimetypes();
// show images?
self::check_safe($MESSAGE);
// set message charset as default
if (!empty($MESSAGE->headers->charset)) {
$rcmail->storage->set_charset($MESSAGE->headers->charset);
}
if (!isset($_SESSION['writeable_abook'])) {
$_SESSION['writeable_abook'] = $rcmail->get_address_sources(true) ? true : false;
}
$rcmail->output->set_pagetitle(abbreviate_string($MESSAGE->subject, 128, '...', true));
// set environment
$rcmail->output->set_env('uid', $msg_id);
$rcmail->output->set_env('safemode', $MESSAGE->is_safe);
$rcmail->output->set_env('message_context', $MESSAGE->context);
$rcmail->output->set_env('message_flags', array_keys(array_change_key_case((array) $MESSAGE->headers->flags)));
$rcmail->output->set_env('sender', !empty($MESSAGE->sender) ? $MESSAGE->sender['string'] : '');
$rcmail->output->set_env('mailbox', $mbox_name);
$rcmail->output->set_env('username', $rcmail->get_user_name());
$rcmail->output->set_env('permaurl', $rcmail->url(['_action' => 'show', '_uid' => $msg_id, '_mbox' => $mbox_name]));
$rcmail->output->set_env('has_writeable_addressbook', $_SESSION['writeable_abook']);
$rcmail->output->set_env('delimiter', $rcmail->storage->get_hierarchy_delimiter());
$rcmail->output->set_env('mimetypes', self::$CLIENT_MIMETYPES);
if ($MESSAGE->headers->get('list-post', false)) {
$rcmail->output->set_env('list_post', true);
}
// set configuration
self::set_env_config(['delete_junk', 'flag_for_deletion', 'read_when_deleted',
'skip_deleted', 'display_next', 'forward_attachment', 'mailvelope_main_keyring']);
// set special folders
foreach (['drafts', 'trash', 'junk'] as $mbox) {
if ($folder = $rcmail->config->get($mbox . '_mbox')) {
$rcmail->output->set_env($mbox . '_mailbox', $folder);
}
}
if ($MESSAGE->has_html_part()) {
$prefer_html = $rcmail->config->get('prefer_html');
$rcmail->output->set_env('optional_format', $prefer_html ? 'text' : 'html');
}
$rcmail->output->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'deletingmessage', 'markingmessage', 'replyall', 'replylist',
'bounce', 'bouncemsg', 'sendingmessage');
// check for unset disposition notification
self::mdn_request_handler($MESSAGE);
if (empty($MESSAGE->headers->flags['SEEN']) && $MESSAGE->context === null) {
$v = intval($rcmail->config->get('mail_read_time'));
if ($v > 0) {
$rcmail->output->set_env('mail_read_time', $v);
}
else if ($v == 0) {
$rcmail->output->command('set_unread_message', $MESSAGE->uid, $mbox_name);
$rcmail->plugins->exec_hook('message_read', [
'uid' => $MESSAGE->uid,
'mailbox' => $mbox_name,
'message' => $MESSAGE,
]);
$set_seen_flag = true;
}
}
}
$rcmail->output->add_handlers([
'mailboxname' => [$this, 'mailbox_name_display'],
'messageattachments' => [$this, 'message_attachments'],
'messageobjects' => [$this, 'message_objects'],
'messagesummary' => [$this, 'message_summary'],
'messageheaders' => [$this, 'message_headers'],
'messagefullheaders' => [$this, 'message_full_headers'],
'messagebody' => [$this, 'message_body'],
'contactphoto' => [$this, 'message_contactphoto'],
]);
if ($rcmail->action == 'print' && $rcmail->output->template_exists('messageprint')) {
$rcmail->output->send('messageprint', false);
}
else if ($rcmail->action == 'preview' && $rcmail->output->template_exists('messagepreview')) {
$rcmail->output->send('messagepreview', false);
}
else {
$rcmail->output->send('message', false);
}
// mark message as read
if (!empty($set_seen_flag)) {
if ($rcmail->storage->set_flag(self::$MESSAGE->uid, 'SEEN', $mbox_name)) {
if ($count = self::get_unseen_count($mbox_name)) {
self::set_unseen_count($mbox_name, $count - 1);
}
}
}
exit;
}
/**
* Handler for the template object 'messageattachments'.
*
* @param array $attrib Named parameters
*
* @return string HTML content showing the message attachments list
*/
public static function message_attachments($attrib)
{
if (empty(self::$MESSAGE->attachments)) {
return '';
}
$rcmail = rcmail::get_instance();
$out =
$ol = '';
$attachments = [];
foreach (self::$MESSAGE->attachments as $attach_prop) {
$filename = self::attachment_name($attach_prop, true);
$filesize = self::message_part_size($attach_prop);
$mimetype = rcube_mime::fix_mimetype($attach_prop->mimetype);
$class = rcube_utils::file2class($mimetype, $filename);
$id = 'attach' . $attach_prop->mime_id;
if ($mimetype == 'application/octet-stream' && ($type = rcube_mime::file_ext_type($filename))) {
$mimetype = $type;
}
// Skip inline images
if (strpos($mimetype, 'image/') === 0 && !self::is_attachment(self::$MESSAGE, $attach_prop)) {
continue;
}
if (!empty($attrib['maxlength']) && mb_strlen($filename) > $attrib['maxlength']) {
$title = $filename;
$filename = abbreviate_string($filename, $attrib['maxlength']);
}
else {
$title = '';
}
$item = html::span('attachment-name', rcube::Q($filename))
. html::span('attachment-size', '(' . rcube::Q($filesize) . ')');
$li_class = $class;
if (!self::$PRINT_MODE) {
$link_attrs = [
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
'onclick' => sprintf('%s.command(\'load-attachment\',\'%s\',this); return false',
rcmail_output::JS_OBJECT_NAME, $attach_prop->mime_id),
'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
'title' => $title,
'class' => 'filename',
];
if ($mimetype != 'message/rfc822' && empty($attach_prop->size)) {
$li_class .= ' no-menu';
$link_attrs['onclick'] = sprintf('%s.alert_dialog(%s.get_label(\'emptyattachment\')); return false',
rcmail_output::JS_OBJECT_NAME, rcmail_output::JS_OBJECT_NAME);
$rcmail->output->add_label('emptyattachment');
}
$item = html::a($link_attrs, $item);
$attachments[$attach_prop->mime_id] = $mimetype;
}
$ol .= html::tag('li', ['class' => $li_class, 'id' => $id], $item);
}
$out = html::tag('ul', $attrib, $ol, html::$common_attrib);
$rcmail->output->set_env('attachments', $attachments);
$rcmail->output->add_gui_object('attachments', $attrib['id']);
return $out;
}
public static function remote_objects_msg()
{
$rcmail = rcmail::get_instance();
$attrib['id'] = 'remote-objects-message';
$attrib['class'] = 'notice';
$attrib['style'] = 'display: none';
$msg = html::span(null, rcube::Q($rcmail->gettext('blockedresources')));
$buttons = html::a([
'href' => "#loadremote",
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote')"
],
rcube::Q($rcmail->gettext('allow'))
);
// add link to save sender in addressbook and reload message
$show_images = $rcmail->config->get('show_images');
if (!empty(self::$MESSAGE->sender['mailto']) && ($show_images == 1 || $show_images == 3)) {
$arg = $show_images == 3 ? rcube_addressbook::TYPE_TRUSTED_SENDER : 'true';
$buttons .= ' ' . html::a([
'href' => "#loadremotealways",
'onclick' => rcmail_output::JS_OBJECT_NAME . ".command('load-remote', $arg)",
'style' => "white-space:nowrap"
],
rcube::Q($rcmail->gettext(['name' => 'alwaysallow', 'vars' => ['sender' => self::$MESSAGE->sender['mailto']]]))
);
}
$rcmail->output->add_gui_object('remoteobjectsmsg', $attrib['id']);
return html::div($attrib, $msg . '&nbsp;' . html::span('boxbuttons', $buttons));
}
/**
* Display a warning whenever a suspicious email address has been found in the message.
*
* @return string HTML content of the warning element
*/
public static function suspicious_content_warning()
{
if (empty(self::$SUSPICIOUS_EMAIL)) {
return '';
}
$rcmail = rcmail::get_instance();
$attrib = [
'id' => 'suspicious-content-message',
'class' => 'notice',
];
$msg = html::span(null, rcube::Q($rcmail->gettext('suspiciousemail')));
return html::div($attrib, $msg);
}
public static function message_buttons()
{
$rcmail = rcmail::get_instance();
$delim = $rcmail->storage->get_hierarchy_delimiter();
$dbox = $rcmail->config->get('drafts_mbox');
// the message is not a draft
if (!empty(self::$MESSAGE->context)
|| (
!empty(self::$MESSAGE->folder)
&& (self::$MESSAGE->folder != $dbox && strpos(self::$MESSAGE->folder, $dbox.$delim) !== 0)
)
) {
return '';
}
$attrib['id'] = 'message-buttons';
$attrib['class'] = 'information notice';
$msg = html::span(null, rcube::Q($rcmail->gettext('isdraft')))
. '&nbsp;'
. html::a([
'href' => "#edit",
'onclick' => rcmail_output::JS_OBJECT_NAME.".command('edit')"
],
rcube::Q($rcmail->gettext('edit'))
);
return html::div($attrib, $msg);
}
/**
* Handler for the template object 'messageobjects' that contains
* warning/info boxes, buttons, etc. related to the displayed message.
*
* @param array $attrib Named parameters
*
* @return string HTML content showing the message objects
*/
public static function message_objects($attrib)
{
if (empty($attrib['id'])) {
$attrib['id'] = 'message-objects';
}
$rcmail = rcmail::get_instance();
$content = [
self::message_buttons(),
self::remote_objects_msg(),
self::suspicious_content_warning(),
];
$plugin = $rcmail->plugins->exec_hook('message_objects',
['content' => $content, 'message' => self::$MESSAGE]);
$content = implode("\n", $plugin['content']);
return html::div($attrib, $content);
}
/**
* Handler for the template object 'contactphoto'.
*
* @param array $attrib Named parameters
*
* @return string HTML content for the IMG tag
*/
public static function message_contactphoto($attrib)
{
$rcmail = rcmail::get_instance();
$error_handler = false;
$placeholder = 'data:image/gif;base64,' . rcmail_output::BLANK_GIF;
if (!empty($attrib['placeholder'])) {
$placeholder = $rcmail->output->abs_url($attrib['placeholder'], true);
$placeholder = $rcmail->output->asset_url($placeholder);
// set error handler on <img>
$error_handler = true;
$attrib['onerror'] = "this.onerror = null; this.src = '$placeholder';";
}
if (!empty(self::$MESSAGE->sender)) {
$photo_img = $rcmail->url([
'_task' => 'addressbook',
'_action' => 'photo',
'_email' => self::$MESSAGE->sender['mailto'],
'_error' => $error_handler ? 1 : null,
'_bgcolor' => $attrib['bg-color'] ?? null
]);
}
else {
$photo_img = $placeholder;
}
return html::img(['src' => $photo_img, 'alt' => $rcmail->gettext('contactphoto')] + $attrib);
}
/**
* Returns table with message headers
*/
public static function message_headers($attrib, $headers = null)
{
static $sa_attrib;
// keep header table attrib
if (is_array($attrib) && !$sa_attrib && empty($attrib['valueof'])) {
$sa_attrib = $attrib;
}
else if (!is_array($attrib) && is_array($sa_attrib)) {
$attrib = $sa_attrib;
}
if (!isset(self::$MESSAGE)) {
return false;
}
$rcmail = rcmail::get_instance();
// get associative array of headers object
if (!$headers) {
$headers_obj = self::$MESSAGE->headers;
$headers = get_object_vars(self::$MESSAGE->headers);
}
else if (is_object($headers)) {
$headers_obj = $headers;
$headers = get_object_vars($headers_obj);
}
else {
$headers_obj = rcube_message_header::from_array($headers);
}
// show these headers
$standard_headers = ['subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
'mail-reply-to', 'mail-followup-to', 'date', 'priority'];
$exclude_headers = !empty($attrib['exclude']) ? explode(',', $attrib['exclude']) : [];
$output_headers = [];
$attr_max = $attrib['max'] ?? null;
$attr_addicon = $attrib['addicon'] ?? null;
$charset = !empty($headers['charset']) ? $headers['charset'] : null;
foreach ($standard_headers as $hkey) {
$value = null;
if (!empty($headers[$hkey])) {
$value = $headers[$hkey];
}
else if (!empty($headers['others'][$hkey])) {
$value = $headers['others'][$hkey];
}
else if (empty($attrib['valueof'])) {
continue;
}
if (in_array($hkey, $exclude_headers)) {
continue;
}
$ishtml = false;
$header_title = $rcmail->gettext(preg_replace('/(^mail-|-)/', '', $hkey));
$header_value = null;
if ($hkey == 'date') {
$header_value = $rcmail->format_date($value,
self::$PRINT_MODE ? $rcmail->config->get('date_long', 'x') : null);
}
else if ($hkey == 'priority') {
$header_value = html::span('prio' . $value, rcube::Q(self::localized_priority($value)));
$ishtml = true;
}
else if ($hkey == 'replyto') {
if ($value != $headers['from']) {
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
$ishtml = true;
}
}
else if ($hkey == 'mail-reply-to') {
if ((!isset($headers['replyto']) || $value != $headers['replyto']) && $value != $headers['from']) {
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
$ishtml = true;
}
}
else if ($hkey == 'sender') {
if ($value != $headers['from']) {
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
$ishtml = true;
}
}
else if ($hkey == 'mail-followup-to') {
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
$ishtml = true;
}
else if (in_array($hkey, ['from', 'to', 'cc', 'bcc'])) {
$header_value = self::address_string($value, $attr_max, true, $attr_addicon, $charset, $header_title);
$ishtml = true;
}
else if ($hkey == 'subject' && empty($value)) {
$header_value = $rcmail->gettext('nosubject');
}
else {
$value = is_array($value) ? implode(' ', $value) : $value;
$header_value = trim(rcube_mime::decode_header($value, $charset));
}
if (empty($header_value)) {
continue;
}
$output_headers[$hkey] = [
'title' => $header_title,
'value' => $header_value,
'raw' => $value,
'html' => $ishtml,
];
}
$plugin = $rcmail->plugins->exec_hook('message_headers_output', [
'output' => $output_headers,
'headers' => $headers_obj,
'exclude' => $exclude_headers, // readonly
'folder' => self::$MESSAGE->folder, // readonly
'uid' => self::$MESSAGE->uid, // readonly
]);
// single header value is requested
if (!empty($attrib['valueof'])) {
if (empty($plugin['output'][$attrib['valueof']])) {
return '';
}
$row = $plugin['output'][$attrib['valueof']];
return !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
}
// compose html table
$table = new html_table(['cols' => 2]);
foreach ($plugin['output'] as $hkey => $row) {
$val = !empty($row['html']) ? $row['value'] : rcube::SQ($row['value']);
$table->add(['class' => 'header-title'], rcube::SQ($row['title']));
$table->add(['class' => 'header ' . $hkey], $val);
}
return $table->show($attrib);
}
/**
* Returns element with "From|To <sender|recipient> on <date>"
*/
public static function message_summary($attrib)
{
if (!isset(self::$MESSAGE) || empty(self::$MESSAGE->headers)) {
return;
}
$rcmail = rcmail::get_instance();
$header = self::$MESSAGE->context ? 'from' : self::message_list_smart_column_name();
$label = 'shortheader' . $header;
$date = $rcmail->format_date(self::$MESSAGE->headers->date, $rcmail->config->get('date_long', 'x'));
$user = self::$MESSAGE->headers->$header;
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->cc)) {
$user = self::$MESSAGE->headers->cc;
}
if (!$user && $header == 'to' && !empty(self::$MESSAGE->headers->bcc)) {
$user = self::$MESSAGE->headers->bcc;
}
$vars[$header] = self::address_string($user, 1, true, $attrib['addicon'], self::$MESSAGE->headers->charset);
$vars['date'] = html::span('text-nowrap', $date);
if (empty($user)) {
$label = 'shortheaderdate';
}
$out = html::span(null, $rcmail->gettext(['name' => $label, 'vars' => $vars]));
return html::div($attrib, $out);
}
/**
* Convert Priority header value into a localized string
*/
public static function localized_priority($value)
{
$labels_map = [
'1' => 'highest',
'2' => 'high',
'3' => 'normal',
'4' => 'low',
'5' => 'lowest',
];
if ($value && !empty($labels_map[$value])) {
return rcmail::get_instance()->gettext($labels_map[$value]);
}
return '';
}
/**
* Returns block to show full message headers
*/
public static function message_full_headers($attrib)
{
$rcmail = rcmail::get_instance();
$html = html::div(['id' => "all-headers", 'class' => "all", 'style' => 'display:none'],
html::div(['id' => 'headers-source'], ''));
$html .= html::div([
'class' => "more-headers show-headers",
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('show-headers','',this)",
'title' => $rcmail->gettext('togglefullheaders')
], '');
$rcmail->output->add_gui_object('all_headers_row', 'all-headers');
$rcmail->output->add_gui_object('all_headers_box', 'headers-source');
return html::div($attrib, $html);
}
/**
* Handler for the 'messagebody' GUI object
*
* @param array $attrib Named parameters
*
* @return string HTML content showing the message body
*/
public static function message_body($attrib)
{
if (
empty(self::$MESSAGE)
|| (!is_array(self::$MESSAGE->parts) && empty(self::$MESSAGE->body))
) {
return '';
}
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmailMsgBody';
}
$rcmail = rcmail::get_instance();
$safe_mode = self::$MESSAGE->is_safe || !empty($_GET['_safe']);
$out = '';
$part_no = 0;
$header_attrib = [];
foreach ($attrib as $attr => $value) {
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs)) {
$header_attrib[$regs[1]] = $value;
}
}
if (!empty(self::$MESSAGE->parts)) {
foreach (self::$MESSAGE->parts as $part) {
if ($part->type == 'headers') {
$out .= html::div('message-partheaders', self::message_headers(count($header_attrib) ? $header_attrib : null, $part->headers));
}
else if ($part->type == 'content') {
// unsupported (e.g. encrypted)
if (!empty($part->realtype)) {
if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
if (
!empty($_SESSION['browser_caps']['pgpmime'])
&& ($pgp_mime_part = self::$MESSAGE->get_multipart_encrypted_part())
) {
$out .= html::span('part-notice', $rcmail->gettext('externalmessagedecryption'));
$rcmail->output->set_env('pgp_mime_part', $pgp_mime_part->mime_id);
$rcmail->output->set_env('pgp_mime_container', '#' . $attrib['id']);
$rcmail->output->add_label('loadingdata');
}
if (!self::$MESSAGE->encrypted_part) {
$out .= html::span('part-notice', $rcmail->gettext('encryptedmessage'));
}
}
continue;
}
else if (!$part->size) {
continue;
}
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
else if (!rcube_utils::mem_check($part->size * 10)) {
$out .= self::part_too_big_message(self::$MESSAGE, $part->mime_id);
continue;
}
// fetch part body
$body = self::$MESSAGE->get_part_body($part->mime_id, true);
// message is cached but not exists (#1485443), or other error
if ($body === false) {
// Don't bail out if it is only one-of-many part of the message (#6854)
if (strlen($out)) {
$out .= html::span('part-notice', $rcmail->gettext('messageopenerror'));
continue;
}
self::message_error();
}
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
['part' => $part, 'prefix' => '', 'message' => self::$MESSAGE]);
// Set attributes of the part container
$container_class = $part->ctype_secondary == 'html' ? 'message-htmlpart' : 'message-part';
$container_id = $container_class . (++$part_no);
$container_attrib = ['class' => $container_class, 'id' => $container_id];
$body_args = [
'safe' => $safe_mode,
'plain' => !$rcmail->config->get('prefer_html'),
'css_prefix' => 'v' . $part_no,
'body_class' => 'rcmBody',
'container_id' => $container_id,
'container_attrib' => $container_attrib,
];
// Parse the part content for display
$body = self::print_body($body, $part, $body_args);
// check if the message body is PGP encrypted
if (strpos($body, '-----BEGIN PGP MESSAGE-----') !== false) {
$rcmail->output->set_env('is_pgp_content', '#' . $container_id);
}
if ($part->ctype_secondary == 'html') {
$body = self::html4inline($body, $body_args);
}
$out .= html::div($body_args['container_attrib'], $plugin['prefix'] . $body);
}
}
}
else {
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (isset(self::$MESSAGE->body) && !rcube_utils::mem_check(strlen(self::$MESSAGE->body) * 10)) {
$out .= self::part_too_big_message(self::$MESSAGE, 0);
}
else {
$plugin = $rcmail->plugins->exec_hook('message_body_prefix',
['part' => self::$MESSAGE, 'prefix' => '']);
$out .= html::div('message-part',
$plugin['prefix'] . self::plain_body(self::$MESSAGE->body));
}
}
// list images after mail body
if ($rcmail->config->get('inline_images', true) && !empty(self::$MESSAGE->attachments)) {
$thumbnail_size = $rcmail->config->get('image_thumbnail_size', 240);
$show_label = rcube::Q($rcmail->gettext('showattachment'));
$download_label = rcube::Q($rcmail->gettext('download'));
foreach (self::$MESSAGE->attachments as $attach_prop) {
// Content-Type: image/*...
if ($mimetype = self::part_image_type($attach_prop)) {
// Skip inline images
if (!self::is_attachment(self::$MESSAGE, $attach_prop)) {
continue;
}
// display thumbnails
if ($thumbnail_size) {
$supported = in_array($mimetype, self::$CLIENT_MIMETYPES);
$show_link_attr = [
'href' => self::$MESSAGE->get_part_url($attach_prop->mime_id, false),
'onclick' => sprintf(
'%s.command(\'load-attachment\',\'%s\',this); return false',
rcmail_output::JS_OBJECT_NAME,
$attach_prop->mime_id
)
];
$download_link_attr = [
'href' => $show_link_attr['href'] . '&_download=1',
];
$show_link = html::a($show_link_attr + ['class' => 'open'], $show_label);
$download_link = html::a($download_link_attr + ['class' => 'download'], $download_label);
$out .= html::p(['class' => 'image-attachment', 'style' => $supported ? '' : 'display:none'],
html::a($show_link_attr + ['class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)],
html::img([
'class' => 'image-thumbnail',
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image') . '&_thumb=1',
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
'onload' => $supported ? '' : '$(this).parents(\'p.image-attachment\').show()',
])
) .
html::span('image-filename', rcube::Q($attach_prop->filename)) .
html::span('image-filesize', rcube::Q(self::message_part_size($attach_prop))) .
html::span('attachment-links', ($supported ? $show_link . '&nbsp;' : '') . $download_link) .
html::br(['style' => 'clear:both'])
);
}
else {
$out .= html::tag('fieldset', 'image-attachment',
html::tag('legend', 'image-filename', rcube::Q($attach_prop->filename)) .
html::p(['align' => 'center'],
html::img([
'src' => self::$MESSAGE->get_part_url($attach_prop->mime_id, 'image'),
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
])
)
);
}
}
}
}
// tell client that there are blocked remote objects
if (self::$REMOTE_OBJECTS && !$safe_mode) {
$rcmail->output->set_env('blockedobjects', true);
}
$rcmail->output->add_gui_object('messagebody', $attrib['id']);
return html::div($attrib, $out);
}
/**
* Returns a HTML notice element for too big message parts
*
* @param rcube_message $message Email message object
* @param string $part_id Message part identifier
*
* @return string HTML content
*/
public static function part_too_big_message($message, $part_id)
{
$rcmail = rcmail::get_instance();
$token = $rcmail->get_request_token();
$url = $rcmail->url([
'task' => 'mail',
'action' => 'get',
'download' => 1,
'uid' => $message->uid,
'part' => $part_id,
'mbox' => $message->folder,
'token' => $token,
]);
return html::span('part-notice', $rcmail->gettext('messagetoobig')
. '&nbsp;' . html::a($url, $rcmail->gettext('download')));
}
/**
* Handle disposition notification requests
*
* @param rcube_message $message Email message object
*/
public static function mdn_request_handler($message)
{
$rcmail = rcmail::get_instance();
if ($message->headers->mdn_to
&& $message->context === null
&& !empty($message->sender['mailto'])
&& empty($message->headers->flags['MDNSENT'])
&& empty($message->headers->flags['SEEN'])
&& ($rcmail->storage->check_permflag('MDNSENT') || $rcmail->storage->check_permflag('*'))
&& $message->folder != $rcmail->config->get('drafts_mbox')
&& $message->folder != $rcmail->config->get('sent_mbox')
) {
$mdn_cfg = intval($rcmail->config->get('mdn_requests'));
$exists = $mdn_cfg == 1;
// Check sender existence in contacts
// 3 and 4 = my contacts, 5 and 6 = trusted senders
if ($mdn_cfg == 3 || $mdn_cfg == 4 || $mdn_cfg == 5 || $mdn_cfg == 6) {
$type = rcube_addressbook::TYPE_TRUSTED_SENDER;
if ($mdn_cfg == 3 || $mdn_cfg == 4) {
$type |= rcube_addressbook::TYPE_WRITEABLE | rcube_addressbook::TYPE_RECIPIENT;
}
if ($rcmail->contact_exists($message->sender['mailto'], $type)) {
$exists = 1;
}
}
if ($exists) {
// Send MDN
if (rcmail_action_mail_sendmdn::send_mdn($message, $smtp_error)) {
$rcmail->output->show_message('receiptsent', 'confirmation');
}
else if ($smtp_error && is_string($smtp_error)) {
$rcmail->output->show_message($smtp_error, 'error');
}
else if ($smtp_error && !empty($smtp_error['label'])) {
$rcmail->output->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
}
else {
$rcmail->output->show_message('errorsendingreceipt', 'error');
}
}
else if ($mdn_cfg != 2 && $mdn_cfg != 4 && $mdn_cfg != 6) {
// Ask the user
$rcmail->output->add_label('sendreceipt', 'mdnrequest', 'send', 'sendalwaysto', 'ignore');
$rcmail->output->set_env('mdn_request_save', $mdn_cfg == 3 || $mdn_cfg == 5 ? $mdn_cfg : 0);
$rcmail->output->set_env('mdn_request_sender', $message->sender);
$rcmail->output->set_env('mdn_request', true);
}
}
}
/**
* Check whether the message part is a normal attachment
*
* @param rcube_message $message Message object
* @param rcube_message_part $part Message part
*
* @return bool
*/
protected static function is_attachment($message, $part)
{
// Inline attachment with Content-Id specified
if (!empty($part->content_id) && $part->disposition == 'inline') {
return false;
}
// Any image attached to multipart/related message (#7184)
$parent_id = preg_replace('/\.[0-9]+$/', '', $part->mime_id);
$parent = $message->mime_parts[$parent_id] ?? null;
if ($parent && $parent->mimetype == 'multipart/related') {
return false;
}
return true;
}
}
@@ -0,0 +1,98 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Display a mail message similar as a usual mail application does |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_mail_viewsource extends rcmail_action
{
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
if (!empty($_GET['_save'])) {
$rcmail->request_security_check(rcube_utils::INPUT_GET);
}
ob_end_clean();
// similar code as in program/steps/mail/get.inc
if ($uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET)) {
if ($pos = strpos($uid, '.')) {
$message = new rcube_message($uid);
$headers = $message->headers;
$part_id = substr($uid, $pos + 1);
}
else {
$headers = $rcmail->storage->get_message_headers($uid);
}
$charset = $headers->charset ?: $rcmail->config->get('default_charset', RCUBE_CHARSET);
if (!empty($_GET['_save'])) {
$subject = rcube_mime::decode_header($headers->subject, $headers->charset);
$filename = self::filename_from_subject(mb_substr($subject, 0, 128));
$filename = ($filename ?: $uid) . '.eml';
$rcmail->output->download_headers($filename, [
'length' => $headers->size,
'type' => 'text/plain',
'type_charset' => $charset,
]);
}
else {
// Make sure it works in an iframe (#9084)
$rcmail->output->page_headers();
header("Content-Type: text/plain; charset={$charset}");
}
if (isset($part_id) && isset($message)) {
$message->get_part_body($part_id, empty($_GET['_save']), 0, -1);
}
else {
$rcmail->storage->print_raw_body($uid, empty($_GET['_save']));
}
}
else {
rcube::raise_error([
'code' => 500,
'file' => __FILE__,
'line' => __LINE__,
'message' => "Message UID $uid not found"
],
true, true
);
}
exit;
}
/**
* Helper function to convert message subject into filename
*/
public static function filename_from_subject($str)
{
$str = preg_replace('/[:\t\n\r\0\x0B\/]+\s*/', ' ', $str);
return trim($str, " \t\n\r\0\x0B./_");
}
}
@@ -0,0 +1,147 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Display license information about program and enabled plugins |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_about extends rcmail_action
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$rcmail->output->set_pagetitle($rcmail->gettext('about'));
$rcmail->output->add_handlers([
'supportlink' => [$this, 'supportlink'],
'pluginlist' => [$this, 'plugins_list'],
'copyright' => function() {
return 'Copyright &copy; 2005-2022, The Roundcube Dev Team';
},
'license' => function() {
return 'This program is free software; you can redistribute it and/or modify it under the terms '
. 'of the <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a> '
. 'as published by the Free Software Foundation, either version 3 of the License, '
. 'or (at your option) any later version.<br/>'
. 'Some <a href="https://roundcube.net/license" target="_blank">exceptions</a> '
. 'for skins &amp; plugins apply.';
},
]);
$rcmail->output->send('about');
}
public static function supportlink($attrib)
{
$rcmail = rcmail::get_instance();
if ($url = $rcmail->config->get('support_url')) {
$label = !empty($attrib['label']) ? $attrib['label'] : 'support';
$attrib['href'] = $url;
return html::a($attrib, $rcmail->gettext($label));
}
}
public static function plugins_list($attrib)
{
$rcmail = rcmail::get_instance();
if (empty($attrib['id'])) {
$attrib['id'] = 'rcmpluginlist';
}
$plugins = array_filter($rcmail->plugins->active_plugins);
$plugin_info = [];
foreach ($plugins as $name) {
if ($info = $rcmail->plugins->get_info($name)) {
$plugin_info[$name] = $info;
}
}
// load info from required plugins, too
foreach ($plugin_info as $name => $info) {
if (!empty($info['require']) && is_array($info['require'])) {
foreach ($info['require'] as $req_name) {
if (!isset($plugin_info[$req_name]) && ($req_info = $rcmail->plugins->get_info($req_name))) {
$plugin_info[$req_name] = $req_info;
}
}
}
}
if (empty($plugin_info)) {
return '';
}
ksort($plugin_info, SORT_LOCALE_STRING);
$table = new html_table($attrib);
// add table header
$table->add_header('name', $rcmail->gettext('plugin'));
$table->add_header('version', $rcmail->gettext('version'));
$table->add_header('license', $rcmail->gettext('license'));
$table->add_header('source', $rcmail->gettext('source'));
foreach ($plugin_info as $name => $data) {
$uri = !empty($data['src_uri']) ? $data['src_uri'] : ($data['uri'] ?? '');
if ($uri && stripos($uri, 'http') !== 0) {
$uri = 'http://' . $uri;
}
if ($uri) {
$uri = html::a([
'target' => '_blank',
'href' => rcube::Q($uri)
],
rcube::Q($rcmail->gettext('download'))
);
}
$license = isset($data['license']) ? $data['license'] : '';
if (!empty($data['license_uri'])) {
$license = html::a([
'target' => '_blank',
'href' => rcube::Q($data['license_uri'])
],
rcube::Q($data['license'])
);
}
else {
$license = rcube::Q($license);
}
$table->add_row();
$table->add('name', rcube::Q(!empty($data['name']) ? $data['name'] : $name));
$table->add('version', !empty($data['version']) ? rcube::Q($data['version']) : '');
$table->add('license', $license);
$table->add('source', $uri);
}
return $table->show();
}
}
@@ -0,0 +1,22 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality to create a folder |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_create extends rcmail_action_settings_folder_edit
{
}
@@ -0,0 +1,66 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality to delete a folder |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_delete extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
if (strlen($mbox)) {
$plugin = $rcmail->plugins->exec_hook('folder_delete', ['name' => $mbox]);
if (empty($plugin['abort'])) {
$deleted = $storage->delete_folder($plugin['name']);
}
else {
$deleted = $plugin['result'];
}
// #1488692: update session
if ($deleted && isset($_SESSION['mbox']) && $_SESSION['mbox'] === $mbox) {
$rcmail->session->remove('mbox');
}
}
if (!empty($deleted)) {
// Remove folder and subfolders rows
$rcmail->output->command('remove_folder_row', $mbox);
$rcmail->output->show_message('folderdeleted', 'confirmation');
// Clear content frame
$rcmail->output->command('subscription_select');
$rcmail->output->command('set_quota', self::quota_content());
}
else {
self::display_server_error('errorsaving');
}
$rcmail->output->send();
}
}
@@ -0,0 +1,347 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality to edit a folder |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_edit extends rcmail_action_settings_folders
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$rcmail->output->add_handlers([
'folderdetails' => [$this, 'folder_form'],
]);
$rcmail->output->add_label('nonamewarning');
$rcmail->output->send('folderedit');
}
public static function folder_form($attrib)
{
// WARNING: folder names in UI are encoded with RCUBE_CHARSET
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
// edited folder name (empty in create-folder mode)
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_GPC, true);
// predefined path for new folder
$parent = rcube_utils::get_input_string('_path', rcube_utils::INPUT_GPC, true);
$threading_supported = $storage->get_capability('THREAD');
$dual_use_supported = $storage->get_capability(rcube_storage::DUAL_USE_FOLDERS);
$delimiter = $storage->get_hierarchy_delimiter();
// Get mailbox parameters
if (strlen($mbox)) {
$options = self::folder_options($mbox);
$namespace = $storage->get_namespace();
$path = explode($delimiter, $mbox);
$folder = array_pop($path);
$path = implode($delimiter, $path);
$folder = rcube_charset::convert($folder, 'UTF7-IMAP');
$hidden_fields = ['name' => '_mbox', 'value' => $mbox];
}
else {
$options = [];
$path = $parent;
$folder = '';
$hidden_fields = [];
// allow creating subfolders of INBOX folder
if ($path == 'INBOX') {
$path = $storage->mod_folder($path, 'in');
}
}
// remove personal namespace prefix
$path_id = null;
if (strlen($path)) {
$path_id = $path;
$path = $storage->mod_folder($path . $delimiter);
if (($path[strlen($path)-1] ?? '') == $delimiter) {
$path = substr($path, 0, -1);
}
}
$form = [];
// General tab
$form['props'] = [
'name' => $rcmail->gettext('properties'),
];
// Location (name)
if (!empty($options['protected'])) {
$foldername = str_replace($delimiter, ' &raquo; ', rcube::Q(self::localize_foldername($mbox, false, true)));
}
else if (!empty($options['norename'])) {
$foldername = rcube::Q($folder);
}
else {
if (isset($_POST['_name'])) {
$folder = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true));
}
$foldername = new html_inputfield(['name' => '_name', 'id' => '_name', 'size' => 30, 'class' => 'form-control']);
$foldername = '<span class="input-group">' . $foldername->show($folder);
if (!empty($options['special']) && ($sname = self::localize_foldername($mbox, false, true)) != $folder) {
$foldername .= ' <span class="input-group-append"><span class="input-group-text">(' . rcube::Q($sname) .')</span></span>';
}
$foldername .= '</span>';
}
$form['props']['fieldsets']['location'] = [
'name' => $rcmail->gettext('location'),
'content' => [
'name' => [
'label' => $rcmail->gettext('foldername'),
'value' => $foldername,
],
],
];
if (!empty($options) && (!empty($options['norename']) || !empty($options['protected']))) {
// prevent user from moving folder
$hidden_path = new html_hiddenfield(['name' => '_parent', 'value' => $path]);
$form['props']['fieldsets']['location']['content']['name']['value'] .= $hidden_path->show();
}
else {
$selected = $_POST['_parent'] ?? $path_id;
$exceptions = [$mbox];
// Exclude 'prefix' namespace from parent folders list (#1488349)
// If INBOX. namespace exists, folders created as INBOX subfolders
// will be listed at the same level - selecting INBOX as a parent does nothing
if ($prefix = $storage->get_namespace('prefix')) {
$exceptions[] = substr($prefix, 0, -1);
}
$select = self::folder_selector([
'id' => '_parent',
'name' => '_parent',
'noselection' => '---',
'maxlength' => 150,
'unsubscribed' => true,
'skip_noinferiors' => true,
'exceptions' => $exceptions,
'additional' => is_string($selected) && strlen($selected) ? [$selected] : null,
]);
$form['props']['fieldsets']['location']['content']['parent'] = [
'label' => $rcmail->gettext('parentfolder'),
'value' => $select->show($selected),
];
}
// Settings
$form['props']['fieldsets']['settings'] = [
'name' => $rcmail->gettext('settings'),
];
// For servers that do not support both sub-folders and messages in a folder
if (!$dual_use_supported) {
if (!strlen($mbox)) {
$select = new html_select(['name' => '_type', 'id' => '_type']);
$select->add($rcmail->gettext('dualusemail'), 'mail');
$select->add($rcmail->gettext('dualusefolder'), 'folder');
$value = rcube_utils::get_input_string('_type', rcube_utils::INPUT_POST);
$value = $select->show($value ?: 'mail');
}
else {
$value = $options['noselect'] ? 'folder' : 'mail';
$value = $rcmail->gettext('dualuse' . $value);
}
$form['props']['fieldsets']['settings']['content']['type'] = [
'label' => $rcmail->gettext('dualuselabel'),
'value' => $value,
];
}
// Settings: threading
if ($threading_supported && ($mbox == 'INBOX' || (empty($options['noselect']) && empty($options['is_root'])))) {
$value = 0;
$select = new html_select(['name' => '_viewmode', 'id' => '_viewmode']);
$select->add($rcmail->gettext('list'), 0);
$select->add($rcmail->gettext('threads'), 1);
if (isset($_POST['_viewmode'])) {
$value = (int) $_POST['_viewmode'];
}
else if (strlen($mbox)) {
$a_threaded = $rcmail->config->get('message_threading', []);
$default_mode = $rcmail->config->get('default_list_mode', 'list');
$value = (int) ($a_threaded[$mbox] ?? $default_mode == 'threads');
}
$form['props']['fieldsets']['settings']['content']['viewmode'] = [
'label' => $rcmail->gettext('listmode'),
'value' => $select->show($value),
];
}
$msgcount = 0;
// Information (count, size) - Edit mode
if (strlen($mbox)) {
// Number of messages
$form['props']['fieldsets']['info'] = [
'name' => $rcmail->gettext('info'),
'content' => []
];
if ((!$options['noselect'] && !$options['is_root']) || $mbox == 'INBOX') {
$msgcount = (int) $storage->count($mbox, 'ALL', true, false);
if ($msgcount) {
// Get the size on servers with supposed-to-be-fast method for that
if ($storage->get_capability('STATUS=SIZE')) {
$size = $storage->folder_size($mbox);
if ($size !== false) {
$size = self::show_bytes($size);
}
}
// create link with folder-size command
if (!isset($size) || $size === false) {
$onclick = sprintf("return %s.command('folder-size', '%s', this)",
rcmail_output::JS_OBJECT_NAME, rcube::JQ($mbox));
$attr = ['href' => '#', 'onclick' => $onclick, 'id' => 'folder-size'];
$size = html::a($attr, $rcmail->gettext('getfoldersize'));
}
}
else {
// no messages -> zero size
$size = 0;
}
$form['props']['fieldsets']['info']['content']['count'] = [
'label' => $rcmail->gettext('messagecount'),
'value' => $msgcount
];
$form['props']['fieldsets']['info']['content']['size'] = [
'label' => $rcmail->gettext('size'),
'value' => $size,
];
}
// show folder type only if we have non-private namespaces
if (!empty($namespace['shared']) || !empty($namespace['others'])) {
$form['props']['fieldsets']['info']['content']['foldertype'] = [
'label' => $rcmail->gettext('foldertype'),
'value' => $rcmail->gettext($options['namespace'] . 'folder')
];
}
}
// Allow plugins to modify folder form content
$plugin = $rcmail->plugins->exec_hook('folder_form', [
'form' => $form,
'options' => $options,
'name' => $mbox,
'parent_name' => $parent
]);
$form = $plugin['form'];
// Set form tags and hidden fields
list($form_start, $form_end) = self::get_form_tags($attrib, 'save-folder', null, $hidden_fields);
unset($attrib['form'], $attrib['id']);
// return the complete edit form as table
$out = "$form_start\n";
// Create form output
foreach ($form as $idx => $tab) {
if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) {
$content = '';
foreach ($tab['fieldsets'] as $fieldset) {
$subcontent = self::get_form_part($fieldset, $attrib);
if ($subcontent) {
$subcontent = html::tag('legend', null, rcube::Q($fieldset['name'])) . $subcontent;
$content .= html::tag('fieldset', null, $subcontent) ."\n";
}
}
}
else {
$content = self::get_form_part($tab, $attrib);
}
if ($idx != 'props') {
$out .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) ."\n";
}
else {
$out .= $content ."\n";
}
}
$out .= "\n$form_end";
$rcmail->output->set_env('messagecount', $msgcount);
$rcmail->output->set_env('folder', $mbox);
if ($mbox !== null && empty($_POST)) {
$rcmail->output->command('parent.set_quota', self::quota_content(null, $mbox));
}
return $out;
}
public static function get_form_part($form, $attrib = [])
{
$rcmail = rcmail::get_instance();
$content = '';
if (!empty($form['content']) && is_array($form['content'])) {
$table = new html_table(['cols' => 2]);
foreach ($form['content'] as $col => $colprop) {
$colprop['id'] = '_' . $col;
$label = !empty($colprop['label']) ? $colprop['label'] : $rcmail->gettext($col);
$table->add('title', html::label($colprop['id'], rcube::Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show($attrib);
}
else if (isset($form['content'])) {
$content = $form['content'];
}
return $content;
}
}
@@ -0,0 +1,70 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality of folder purge |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_purge extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
$delimiter = $storage->get_hierarchy_delimiter();
$trash_mbox = $rcmail->config->get('trash_mbox');
$trash_regexp = '/^' . preg_quote($trash_mbox . $delimiter, '/') . '/';
// we should only be purging trash (or their subfolders)
if (!strlen($trash_mbox) || $mbox === $trash_mbox || preg_match($trash_regexp, $mbox)) {
$success = $storage->delete_message('*', $mbox);
$delete = true;
}
// move to Trash
else {
$success = $storage->move_message('1:*', $trash_mbox, $mbox);
$delete = false;
}
if (!empty($success)) {
$rcmail->output->set_env('messagecount', 0);
if ($delete) {
$rcmail->output->show_message('folderpurged', 'confirmation');
$rcmail->output->command('set_quota', self::quota_content(null, $mbox));
}
else {
$rcmail->output->show_message('messagemoved', 'confirmation');
}
$_SESSION['unseen_count'][$mbox] = 0;
$rcmail->output->command('show_folder', $mbox, null, true);
}
else {
self::display_server_error('errorsaving');
}
$rcmail->output->send();
}
}
@@ -0,0 +1,94 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality of folder rename |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_rename extends rcmail_action_settings_folders
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$name = trim(rcube_utils::get_input_string('_folder_newname', rcube_utils::INPUT_POST, true));
$oldname = rcube_utils::get_input_string('_folder_oldname', rcube_utils::INPUT_POST, true);
if (strlen($name) && strlen($oldname)) {
$rename = self::rename_folder($oldname, $name);
}
if (!empty($rename)) {
self::update_folder_row($name, $oldname);
}
else {
self::display_server_error('errorsaving');
}
$rcmail->output->send();
}
public static function rename_folder($oldname, $newname)
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$plugin = $rcmail->plugins->exec_hook('folder_rename', [
'oldname' => $oldname, 'newname' => $newname]);
if (empty($plugin['abort'])) {
$renamed = $storage->rename_folder($oldname, $newname);
}
else {
$renamed = $plugin['result'];
}
// update per-folder options for modified folder and its subfolders
if ($renamed) {
$delimiter = $storage->get_hierarchy_delimiter();
$a_threaded = (array) $rcmail->config->get('message_threading', []);
$oldprefix = '/^' . preg_quote($oldname . $delimiter, '/') . '/';
foreach ($a_threaded as $key => $val) {
if ($key == $oldname) {
unset($a_threaded[$key]);
$a_threaded[$newname] = $val;
}
else if (preg_match($oldprefix, $key)) {
unset($a_threaded[$key]);
$a_threaded[preg_replace($oldprefix, $newname . $delimiter, $key)] = $val;
}
}
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
// #1488692: update session
if (isset($_SESSION['mbox']) && $_SESSION['mbox'] === $oldname) {
$_SESSION['mbox'] = $newname;
}
return true;
}
return false;
}
}
@@ -0,0 +1,235 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handler for saving the create/edit folder form |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_save extends rcmail_action_settings_folder_edit
{
protected static $mode = self::MODE_HTTP;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
// WARNING: folder names in UI are encoded with RCUBE_CHARSET
$name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true));
$path = rcube_utils::get_input_string('_parent', rcube_utils::INPUT_POST, true);
$old_imap = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
$type = rcube_utils::get_input_string('_type', rcube_utils::INPUT_POST);
$name_imap = rcube_charset::convert($name, RCUBE_CHARSET, 'UTF7-IMAP');
// $path is in UTF7-IMAP already
// init IMAP connection
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$delimiter = $storage->get_hierarchy_delimiter();
$options = strlen($old_imap) ? self::folder_options($old_imap) : [];
$char = null;
// Folder name checks
if (!empty($options['protected']) || !empty($options['norename'])) {
// do nothing
}
else if (!strlen($name)) {
$error = $rcmail->gettext('namecannotbeempty');
}
else if (mb_strlen($name) > 128) {
$error = $rcmail->gettext('nametoolong');
}
else if ($name[0] == '.' && $rcmail->config->get('imap_skip_hidden_folders')) {
$error = $rcmail->gettext('namedotforbidden');
}
else if (!$storage->folder_validate($name, $char)) {
$error = $rcmail->gettext('forbiddencharacter') . " ($char)";
}
if (!empty($error)) {
$rcmail->output->command('display_message', $error, 'error');
}
else {
if (!empty($options['protected']) || !empty($options['norename'])) {
$name_imap = $old_imap;
}
else if (strlen($path)) {
$name_imap = $path . $delimiter . $name_imap;
}
else {
$name_imap = $storage->mod_folder($name_imap, 'in');
}
}
$dual_use_supported = $storage->get_capability(rcube_storage::DUAL_USE_FOLDERS);
$acl_supported = $storage->get_capability('ACL');
// Check access rights to the parent folder
if (empty($error) && $acl_supported && strlen($path) && (!strlen($old_imap) || $old_imap != $name_imap)) {
$parent_opts = $storage->folder_info($path);
if ($parent_opts['namespace'] != 'personal'
&& (empty($parent_opts['rights']) || !preg_match('/[ck]/', implode($parent_opts['rights'])))
) {
$error = $rcmail->gettext('parentnotwritable');
}
}
if (!empty($error)) {
$rcmail->output->command('display_message', $error, 'error');
$folder = null;
}
else {
$folder = [
'name' => $name_imap,
'oldname' => $old_imap,
'class' => '',
'options' => $options,
'settings' => [
// List view mode: 0-list, 1-threads
'view_mode' => (int) rcube_utils::get_input_string('_viewmode', rcube_utils::INPUT_POST),
'sort_column' => rcube_utils::get_input_string('_sortcol', rcube_utils::INPUT_POST),
'sort_order' => rcube_utils::get_input_string('_sortord', rcube_utils::INPUT_POST),
],
'subscribe' => false,
'noselect' => false,
];
}
// create a new mailbox
if (empty($error) && !strlen($old_imap)) {
$folder['subscribe'] = true;
// Server does not support both sub-folders and messages in a folder
// For folders that are supposed to contain other folders we will:
// - disable subscription
// - add a separator at the end to make them \NoSelect
if (!$dual_use_supported && $type == 'folder') {
$folder['subscribe'] = false;
$folder['noselect'] = true;
}
$plugin = $rcmail->plugins->exec_hook('folder_create', ['record' => $folder]);
$folder = $plugin['record'];
if (!$plugin['abort']) {
$created = $storage->create_folder($folder['name'], $folder['subscribe'], null, $folder['noselect']);
}
else {
$created = $plugin['result'];
}
if ($created) {
// Save folder settings
if (isset($_POST['_viewmode'])) {
$a_threaded = (array) $rcmail->config->get('message_threading', []);
$a_threaded[$folder['name']] = (bool) $_POST['_viewmode'];
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
}
self::update_folder_row($folder['name'], null, $folder['subscribe'], $folder['class']);
$rcmail->output->show_message('foldercreated', 'confirmation');
// reset folder preview frame
$rcmail->output->command('subscription_select');
$rcmail->output->send('iframe');
}
else {
// show error message
if (!empty($plugin['message'])) {
$rcmail->output->show_message($plugin['message'], 'error', null, false);
}
else {
self::display_server_error('errorsaving');
}
}
}
// update a mailbox
else if (empty($error)) {
$plugin = $rcmail->plugins->exec_hook('folder_update', ['record' => $folder]);
$folder = $plugin['record'];
$rename = $folder['oldname'] != $folder['name'];
if (!$plugin['abort']) {
if ($rename) {
$updated = $storage->rename_folder($folder['oldname'], $folder['name']);
}
else {
$updated = true;
}
}
else {
$updated = $plugin['result'];
}
if ($updated) {
// Update folder settings,
if (isset($_POST['_viewmode'])) {
$a_threaded = (array) $rcmail->config->get('message_threading', []);
// In case of name change update names of children in settings
if ($rename) {
$oldprefix = '/^' . preg_quote($folder['oldname'] . $delimiter, '/') . '/';
foreach ($a_threaded as $key => $val) {
if ($key == $folder['oldname']) {
unset($a_threaded[$key]);
}
else if (preg_match($oldprefix, $key)) {
unset($a_threaded[$key]);
$a_threaded[preg_replace($oldprefix, $folder['name'].$delimiter, $key)] = $val;
}
}
}
$a_threaded[$folder['name']] = (bool) $_POST['_viewmode'];
$rcmail->user->save_prefs(['message_threading' => $a_threaded]);
}
$rcmail->output->show_message('folderupdated', 'confirmation');
$rcmail->output->set_env('folder', $folder['name']);
if ($rename) {
// #1488692: update session
if (isset($_SESSION['mbox']) && $_SESSION['mbox'] === $folder['oldname']) {
$_SESSION['mbox'] = $folder['name'];
}
self::update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']);
$rcmail->output->send('iframe');
}
else if (!empty($folder['class'])) {
self::update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']);
}
}
else {
// show error message
if (!empty($plugin['message'])) {
$rcmail->output->show_message($plugin['message'], 'error', null, false);
}
else {
self::display_server_error('errorsaving');
}
}
}
$rcmail->overwrite_action('edit-folder');
}
}
@@ -0,0 +1,49 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Provide functionality of getting folder size |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_size extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$name = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
$size = $storage->folder_size($name);
// @TODO: check quota and show percentage usage of specified mailbox?
if ($size !== false) {
$rcmail->output->command('folder_size_update', self::show_bytes($size));
}
else {
self::display_server_error();
}
$rcmail->output->send();
}
}
@@ -0,0 +1,67 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handler for folder subscribe action |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_subscribe extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
if (strlen($mbox)) {
$result = $storage->subscribe([$mbox]);
// Handle virtual (non-existing) folders
if (
!$result
&& $storage->get_error_code() == -1
&& $storage->get_response_code() == rcube_storage::TRYCREATE
) {
$result = $storage->create_folder($mbox, true);
if ($result) {
// @TODO: remove 'virtual' class of folder's row
}
}
}
if (!empty($result)) {
// Handle subscription of protected folder (#1487656)
if ($rcmail->config->get('protect_default_folders') && $storage->is_special_folder($mbox)) {
$rcmail->output->command('disable_subscription', $mbox);
}
$rcmail->output->show_message('foldersubscribed', 'confirmation');
}
else {
self::display_server_error('errorsaving');
$rcmail->output->command('reset_subscription', $mbox, false);
}
$rcmail->output->send();
}
}
@@ -0,0 +1,50 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Handler for folder unsubscribe action |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
class rcmail_action_settings_folder_unsubscribe extends rcmail_action
{
protected static $mode = self::MODE_AJAX;
/**
* Request handler.
*
* @param array $args Arguments from the previous step(s)
*/
public function run($args = [])
{
$rcmail = rcmail::get_instance();
$storage = $rcmail->get_storage();
$mbox = rcube_utils::get_input_string('_mbox', rcube_utils::INPUT_POST, true);
if (strlen($mbox)) {
$result = $storage->unsubscribe([$mbox]);
}
if (!empty($result)) {
$rcmail->output->show_message('folderunsubscribed', 'confirmation');
}
else {
self::display_server_error('errorsaving');
$rcmail->output->command('reset_subscription', $mbox, true);
}
$rcmail->output->send();
}
}

Some files were not shown because too many files have changed in this diff Show More