Compare commits

..

176 Commits

Author SHA1 Message Date
Felix Stupp b53d1c83ce
[bnet] Added plugins
- postfixadmin-user-identities
- swipe
4 years ago
Thomas Bruederli 5b53161198 Bump version to 1.4.6 4 years ago
Aleksander Machniak 9905b5adaa Installer: Fix regression in SMTP test section (#7417) 4 years ago
Thomas Bruederli 9898599d3e Bump version to 1.4.5 4 years ago
Aleksander Machniak ccaccae665 Security: Fix cross-site scripting (XSS) via malicious XML attachment 4 years ago
Aleksander Machniak b37433b199 Security: Better fix for CVE-2020-12641 4 years ago
Aleksander Machniak 4beec65d40 Security: Fix XSS issue in template object 'username' (#7406) 4 years ago
Aleksander Machniak 20ae604b9f Security: Fix couple of XSS issues in Installer (#7406) 4 years ago
Aleksander Machniak 5e4195a042 Fix bug where PDF attachments marked as inline could have not been attached on mail forward (#7382) 4 years ago
Aleksander Machniak 31df958caf Elastic: Fix aspect ratio of a contact photo in mail preview (#7339) 4 years ago
Aleksander Machniak bebc9ec1be Fix error when user-configured skin does not exist anymore (#7271)
We fallback to the system skin not the default one.
4 years ago
Aleksander Machniak c25616b3b5 Fix PHP warning: count(): Parameter must be an array or an object... in ID command handler (#7392) 4 years ago
Aleksander Machniak eba6fb20cf Fix typo 4 years ago
Aleksander Machniak 99b20bd82a Update changelog
[ci skip]
4 years ago
Christopher Gurnee 973217d960 Show Encrypt button w/Mailvelope, even if disabled
Before the Elastic skin would keep it hidden until enabled, closes #7353
4 years ago
Aleksander Machniak 9362bb9459 Fix changelog
[ci skip]
4 years ago
Christopher Gurnee a5c6bfb678 Let Mailvelope use sender's address to find pubkeys to check signatures 4 years ago
Christopher Gurnee 79e6042276 Add missing \'s to regexes in rcube_check_email() 4 years ago
Aleksander Machniak 90afff1929 Update changelog
[ci skip]
4 years ago
vaaguirre 9d0d072f36 Fix issue with Modoboa driver for password plugin (#7372)
Was having trouble with updating a password with the Modoboa API (1.9.1). API responded with an error message but 200 HTTP status code, so roundcube displayed a success message even though the password wasn't being updated. Added a line to include a required field in the update request.
4 years ago
Aleksander Machniak 656c706270 Fix default keyservers (use keys.openpgp.org), add note about CORS (#7373, #7367) 4 years ago
Aleksander Machniak 26d5fb1b55 Fix missing flag indication on collapsed thread in Larry and Elastic (#7366) 4 years ago
Aleksander Machniak dff0ee3e2a Update changelog
[ci skip]
4 years ago
johndoh f494cd22ce Markasjunk: Fix regression in jsevent driver #7361 (#7365) 4 years ago
Aleksander Machniak 2a250d7bc3 Fix so the database setup description is compatible with MySQL 8 (#7340)
[ci skip]
4 years ago
Aleksander Machniak 62a0936ecc Clarify des_key length requirement (#7350)
[ci skip]
4 years ago
Aleksander Machniak d15d929167 Fix bug in extracting required plugins from composer.json that led to spurious error in log (#7364) 4 years ago
Thomas Bruederli aadb13e25f Bump version to 1.4.4 4 years ago
Aleksander Machniak 9bbda422ff Fix CSRF bypass that could be used to log out an authenticated user (#7302) 4 years ago
Aleksander Machniak 814eadb699 Fix local file inclusion (and code execution) via crafted 'plugins' option 4 years ago
Aleksander Machniak fcfb099477 Fix remote code execution via crafted 'im_convert_path' or 'im_identify_path' settings 4 years ago
Aleksander Machniak 1c239c90d9 Fix XSS issue in handling of CDATA in HTML messages 4 years ago
Aleksander Machniak 301670f081 Fix so Print button for PDF attachments works on Firefox >= 75 (#5125) 4 years ago
Aleksander Machniak be68e3f679 Fix typo 4 years ago
Aleksander Machniak 37acfc2e03 Fix performance issue of parsing big HTML messages by disabling HTML5 parser for these (#7331) 4 years ago
Aleksander Machniak 080b0afd95 Make install-jsdeps.sh script working without the 'file' program installed (#7325) 4 years ago
Aleksander Machniak ecfee7470c Fix bug where some message/rfc822 parts could not be attached on forward (#7323) 4 years ago
Aleksander Machniak 343da344e0 Fix characters encoding in group rename input after group creation/rename (#7330) 4 years ago
Aleksander Machniak 0d43c51ce1 Mailvelope: Fix bug where recipients with name were not handled properly in mail compose (#7312) 4 years ago
Aleksander Machniak 808f374f42 Revert "Fix bug where session was destoryed with window close (#7251)"
This reverts commit 32fac136db.
4 years ago
Aleksander Machniak 15e924fd64 Fix bug where a special folder couldn't be created if a special-use flag is not supported (#7147) 4 years ago
Aleksander Machniak c99e093f8a Fix so imap error message is displayed to the user on folder create/update (#7245) 4 years ago
Aleksander Machniak 43f90d31c8 Fix bug where session was destoryed with window close (#7251) 4 years ago
Aleksander Machniak 5666ae00a8 Elastic: Restrict logo size in print view (#7275) 4 years ago
PhilW 2ba62d0c98 zipdownload: enable menu options when menu is opened 4 years ago
Aleksander Machniak 7c081d7d72 Update changelog 4 years ago
Aleksander Machniak a74a9e5fc2 Fix marking as spam/ham on moving messages with Move menu (#7189) 4 years ago
Aleksander Machniak cd73ca05d8 Fix PHP warning for real (#7206) 4 years ago
PhilW 1bc41d3a5f markasjunk: handle select all case (#7206) 4 years ago
Aleksander Machniak ae71f01b33 Elastic: Fix color of a folder with recent messages (#7281) 4 years ago
Aleksander Machniak 746ccb7fdf Fix literals handling again 4 years ago
Aleksander Machniak 59af9944ea Update changelog
[skip ci]
4 years ago
dessert1 2005b895be Fix handling keyservers configured with protocol prefix (#7295)
`|^[a-z]://|` matches only single-character protocol shortnames, to correctly exclude e.g. `hkps://` the expression should be `|^[a-z]+://|` instead.
4 years ago
Aleksander Machniak 734f97511a Fix bug where multiple images in a message were replaced by the first one on forward/reply/edit (#7293) 4 years ago
Aleksander Machniak ade470aac9 Support many string literals in a "line response", deduplicate code 4 years ago
Aleksander Machniak a71b33f568 Fix string literals handling in IMAP STATUS (and various other) responses (#7290) 4 years ago
Aleksander Machniak 518d1aea71 Fix internal cache use in rcube_imap::get_message()
Two folders, personal and shared, can contain the same UIDs, so
we should check UID and folder name when dealing with internally
cached message.
4 years ago
Aleksander Machniak 3a32a1e246 Fix cursor position after inserting a group to a recipient input using autocompletion (#7267)
... for larry and classic skins.
4 years ago
Aleksander Machniak 7793386683 Fix regression in testing database schema on MSSQL (#7227) 4 years ago
Aleksander Machniak edbcb03237 Fix so button label in Select image/media dialogs is "Close" not "Cancel" (#7246) 4 years ago
Aleksander Machniak 73e2b0584a Fix missing contact display name in QR Code data (#7257) 4 years ago
Aleksander Machniak f0aafa98ee Fix scroll-jump in MS Edge when using autoresizeable textarea (#7230) 4 years ago
Aleksander Machniak 5de09e9f21 Update changelog for #7261 4 years ago
Aleksander Machniak 81e55f8033 Elastic: Fix recipient input bug when using click to select a contact from autocomplete list (#7231) 4 years ago
Aleksander Machniak 1c5f83d41c Elastic: Fix text selection with Shift+PageUp and Shift+PageDown in plain text editor when using Chrome (#7230) 4 years ago
Aleksander Machniak 233beea354 Fix identity selection on reply when both sender and recipient addresses are included in identities (#7211) 4 years ago
Aleksander Machniak 88660cd465 Fix bug where original attachments with Content-Id were attached to the message on reply (#7122)
All Content-Disposition:inline parts that aren't used in the body are ignored on reply/forward/edit.
4 years ago
Thomas Bruederli 0c8b71059b Remove plugins/elastic4mobile submodule which was accidentally committed 4 years ago
Thomas Bruederli 273707f378 Bump version to 1.4.3 4 years ago
Aleksander Machniak 322ab30fb9 Update changelog 4 years ago
Paul J. Dorn 9cb796ee2b Fix using unix:///path/to/socket.file in memcached driver (#7210)
off by one when stripping the memcache (sans d) compatible AF_UNIX prefix
4 years ago
Aleksander Machniak 65d6a5aa51 Update localization 4 years ago
Aleksander Machniak ab3220d046 Enigma: Fix bug where "Send unencrypted" button didn't work in Elastic skin (#7205) 4 years ago
Aleksander Machniak ea1260ea72 Fix regression where using an absolute path to SQLite database file on Windows didn't work (#7196) 4 years ago
Aleksander Machniak bff226801d Enigma: Fix incorrect encrypted mail structure (boundary) with Mail_Mime >= 1.10.5 4 years ago
Aleksander Machniak b35bf87105 Fix bug where it wasn't possible to save flag actions (#7188)
Display proper error when no flag is selected.
4 years ago
Aleksander Machniak 7f45e47f00 Fix display issues with mail subject that contains line-breaks (#7191) 4 years ago
Aleksander Machniak 9d4638d94e Markasjunk: Fix bug where marking as spam/ham didn't work on moving messages with drag-and-drop (#7137) 4 years ago
Aleksander Machniak 3819e4e640 Fix bug where message parts with no Content-Disposition header and no name were not listed on attachments list (#7117) 4 years ago
Aleksander Machniak 62ed219821 Elastic: Fix disappearing sidebar in mail compose after clicking Mail button
Elastic: Fix incorrect aria-disabled attribute on Mail taskmenu button in mail compose
4 years ago
Aleksander Machniak f0dc915fd8 Enigma: Simplify and fix handling of IDN with not all parts being punny-coded 4 years ago
Aleksander Machniak 0aa724f73c Enigma: Display IDN domains of key users and identities in UTF8 4 years ago
Max Bosse 36aa132f51 Fix creation of pgp-keys for IDN emails 4 years ago
Aleksander Machniak 5d68245fa5 Fix bug where files in skins/ directory were listed on skins list (#7180) 4 years ago
Aleksander Machniak a3a260b613 Elastic: Fix non-working folder subscription checkbox for newly added folders (#7174) 4 years ago
Aleksander Machniak 229b9f5f2c Fix recipient duplicates in print-view when the recipient list has been expanded (#7169) 4 years ago
Aleksander Machniak d14d0c5ab5 Elastic: Fix missing Close button in "more recipients" dialog 4 years ago
Aleksander Machniak 1723aa936f Elastic: Fix text selection in recipient inputs (#7129) 4 years ago
Aleksander Machniak adc08946ef Fix unexpected error message when mail refresh involves folder auto-unsubscribe (#6923) 4 years ago
Aleksander Machniak b470f5a9f3 Add note about Windows and symlinks (#7151) 4 years ago
Aleksander Machniak cd1859609b Fix typo 4 years ago
Aleksander Machniak fbc9219d72 Enigma: Fix so using list checkbox selection does not load the key preview frame 4 years ago
Aleksander Machniak b5e76ab202 Fix PHP Warning: array_filter() expects parameter 1 to be array, null given in subscription_options plugin (#7165)
.. when IMAP connection fails
4 years ago
Aleksander Machniak c8ead80312 Enigma: Fix so key list selection is reset when opening key creation form (#7154) 4 years ago
Aleksander Machniak ef13807f17 Fix regression where "Open in new window" action didn't work (#7155) 4 years ago
Aleksander Machniak 9ecf84e3bd Update changelog 4 years ago
jelle van der Waa 2edcf5b52c Password: Make chpass-wrapper.py Python 3 compatible (#7135)
Remove the ", e" as the exception is never printed and this makes it
Python 3 compatible as well

Closes: #7118
4 years ago
Théo b421de8416 Updte copyright year in About (#7138) 4 years ago
Aleksander Machniak 348e557614 Elastic: Fix bug where it was possible to switch editor mode when 'htmleditor' was in 'dont_override' (#7143) 4 years ago
Thomas Bruederli 51b8137170 Bump version to 1.4.2 4 years ago
Aleksander Machniak 2fa6dd7ee5 Update localization 5 years ago
Aleksander Machniak d6c80e4b79 Skip install-jsdeps.sh execution when using "complete" package to update "custom" installation (#7087) 5 years ago
Aleksander Machniak ca870f034e Elastic: Fix hidden list widget on mobile/tablet when selecting folder while search menu is open (#7120) 5 years ago
Aleksander Machniak 880452f84e Markasjunk: Fix marking more than one message as spam/ham with email_learn driver (#7121) 5 years ago
Aleksander Machniak b25c649bb7 Plugin API: Make actionbefore, before<action>, actionafter and after<action> events working with plugin actions (#7106) 5 years ago
Aleksander Machniak d3bedca7f8 Fix malformed characters in HTML message with charset meta tag not in head (#7116) 5 years ago
Aleksander Machniak 72959279e2 Fix so number of contacts in a group is not limited to 200 when redirecting to mail composer from Contacts (#6972)
Use max_group_members with fallback to 999 if unset.
5 years ago
Aleksander Machniak 3bdcfc5623 Elastic: Simple search in pretty selects (#7072) 5 years ago
Aleksander Machniak b47e38447c Fix bug where next message wasn't displayed after delete in List mode (#7096) 5 years ago
Aleksander Machniak d025351d64 Fix bug where 'text' attribute on body tag was ignored when displaying HTML message (#7109) 5 years ago
Aleksander Machniak 906b223d30 Fix bug where listing tables in PostgreSQL database with db_prefix didn't work (#7093) 5 years ago
Aleksander Machniak bd7c38be0b Fix so contact's organization field accepts up to 128 characters (it was 50)
Also assistant, manager, spouse, website and im
5 years ago
Aleksander Machniak eecd78c3a1 Fix bug where 'skins_allowed' option didn't enforce user skin preference (#7080) 5 years ago
Sebastiaan Lokhorst 3a12de3fb6 Explain difference between ssl:// and tls:// for default_host (#7099) 5 years ago
Aleksander Machniak 5ec5ae57a5 Add 'filter' to required extensions 5 years ago
Aleksander Machniak 58be50418d Fix so displayed maximum attachment size depends also on 'max_message_size' (#7105) 5 years ago
Aleksander Machniak 8127d2615f Fix matching multiple X-Forwarded-For addresses with 'proxy_whitelist' (#7107) 5 years ago
Aleksander Machniak 1fe448ce41 Elastic: Fix contrast of warning toasts (#7058)
Add some color variables and slightly make bg-color stronger on "box" messages.
5 years ago
Aleksander Machniak e1dfd55185 Improve style tag handling in TinyMCE (#7088) 5 years ago
Aleksander Machniak bd03cc4541 Fix bug where HTML reply could add an empty line with extra indentation above the original message (#7088) 5 years ago
Aleksander Machniak 132be945bd Password: Fix kpasswd and smb drivers' double-escaping bug (#7092) 5 years ago
Aleksander Machniak 38f1727515 Fix filter selection after removing a first filter (#7079) 5 years ago
Aleksander Machniak dd59b59478 Fix bug where cancelling switching from HTML to plain text didn't set the flag properly (#7077) 5 years ago
Aleksander Machniak e0ae7765bc Fix/remove useless keyup event handler on username input in logon form (#6970) 5 years ago
Aleksander Machniak e29d44dcc3 Fix so use of Ctrl+A does not scroll the list (#7020) 5 years ago
Sebastiaan Lokhorst 1800678a40 Update SMTP TLS/STARTTLS explanation in config.php (#7066)
Implicit TLS (port 465) is no longer deprecated since RFC 8314.
5 years ago
Aleksander Machniak a270cf593e Add notes about file ownership (#7009) 5 years ago
Aleksander Machniak 03b3dd3389 Fix so install-jsdeps.sh removes Bootstrap's sourceMappingURL (#7035) 5 years ago
Aleksander Machniak c084d69d06 Fix DB Write test on SQLite database ("database is locked" error) (#7064)
Also fix so SQLite DSN with a relative path to the database file works in Installer
5 years ago
Aleksander Machniak fbb39470d5 Fix bug where a new saved search added after removing all searches wasn't added to the list (#7061) 5 years ago
Aleksander Machniak 0e7295fce6 Fix so modifier type select wasn't hidden after hiding modifier select on header change 5 years ago
Aleksander Machniak 90bdd77ada Fix bug where Ctype extension wasn't required in Installer and INSTALL file (#7049) 5 years ago
Aleksander Machniak 09af79f637 Replace "Filter disabled" with "Filter enabled" (#7028) 5 years ago
Aleksander Machniak e25f352472 Fix bug where a new contact group added after removing all groups from addressbook wasn't added to the list 5 years ago
Aleksander Machniak a80da7f678 Fix bug where deleting a saved search in addressbook caused display issue on sources/groups list (#7061)
also remove dead code.
5 years ago
Aleksander Machniak 840437c460 Fix bug where Enter key didn't work on messages list in "List" layout (#7052)
with some code improvements and better checking if selected message is a draft.
5 years ago
Aleksander Machniak 55dbf40133 Update changelog 5 years ago
johndoh 8952e9dddb Always update folder count after purge command (#7051)
... not only when it is the current folder.
5 years ago
Aleksander Machniak 720a0a3173 Update changelog, CS improvements 5 years ago
Aleksander Machniak aeec47b9bd Enigma: Add script to import keys from filesystem to the db storage (for multihost) 5 years ago
Aleksander Machniak b49d9f05aa Remove spaces from 'accept' attribute 5 years ago
Aleksander Machniak 8252bc3962 Code simplification and style fixes 5 years ago
Aleksander Machniak 7e9405dc3e Elastic: Remove redundant listmenulink elements 5 years ago
PhilW c7baf94531 remove redundant type attrib in script tag 5 years ago
Thomas Bruederli 0b1d6841f9 Bump version to 1.4.1 5 years ago
Aleksander Machniak fbc50b3791 Elastic: Fix language icon (#6624) 5 years ago
Aleksander Machniak ac3ce1d713 Fix bug where the Installer would not warn about required schema upgrade (#7042) 5 years ago
Aleksander Machniak ae7429a287 Small correction 5 years ago
Aleksander Machniak deee3442aa Fix so Elastic is also a default in jqueryui plugin (#7039) 5 years ago
Aleksander Machniak ebfd0f53bf Elastic: Fix data-fab-task handling (#7038)
Since the code is executed before rcmail init event, we have to use rcmail.env.task
instead of rcmail.task.
5 years ago
Aleksander Machniak 594a12b61e Fix tables listing routine when DSN contained a database with unsupported suffix (#7034) 5 years ago
Aleksander Machniak 8ca4bf8a3a Elastic: Fix position of mobile floating action button (#7038) 5 years ago
Aleksander Machniak 0c8e29fcbf Mention required min. version of lessc (#7031) 5 years ago
Aleksander Machniak f50521a399 Fixed typo 5 years ago
Aleksander Machniak 075803871a Fix so update.sh script warns about changed defaults (#7011) 5 years ago
Aleksander Machniak 0467670533 Fix misleading comment 5 years ago
Aleksander Machniak 90d1d58f8b Larry: Fix html editor toolbar background in Identities 5 years ago
Aleksander Machniak e3d18291ff Elastic: Fix regression in the new editor widget (#7021) 5 years ago
Aleksander Machniak 71990b59d6 Fix db_prefix handling in queries with `TRUNCATE TABLE <name>` and `UNIQUE <name>` (#7013) 5 years ago
Aleksander Machniak 29e7c16b33 Fix so 401 error is returned only on failed logon requests (#7010) 5 years ago
Aleksander Machniak 980f2b369f Elastic: Fix Edit responses button state in Mailvelope mode 5 years ago
Aleksander Machniak cab4c8a39c Fix invalid Signature button state after escaping Mailvelope mode (#7015) 5 years ago
Aleksander Machniak 659980923a Remove unused variables 5 years ago
Aleksander Machniak 35e41670e6 Fix regexp for version input to accept rcX releases 5 years ago
Aleksander Machniak 0ac39592fc Elastic: Change HTML editor widget to improve form flow (#6992) 5 years ago
Aleksander Machniak 392734f984 Fix bug where cache keys could exceed length limit specified in db schema (#7004) 5 years ago
Aleksander Machniak 83289cce1e Fix PHP warning: "array_merge(): Expected parameter 2 to be an array, null given in sendmail.inc (#7003) 5 years ago
Aleksander Machniak 1ef912555d Managesieve: Fix locked UI after opening filter frame (#7007) 5 years ago
Aleksander Machniak 59ac1ddb64 Fix displaying version number for rcX versions 5 years ago
Aleksander Machniak 17a6bd77a1 Remove a note about Larry being default 5 years ago
Aleksander Machniak 9a11d0e36c Correct file permissions 5 years ago
Aleksander Machniak 2464f5fed8 Small code improvements 5 years ago
Thomas Bruederli fdbdaec998 Remove unstable warning from README 5 years ago
Thomas Bruederli 579663e285 Set default skin to 'elastic' in sample config 5 years ago
Thomas Bruederli e774209b41 Bump version to 1.4.0 5 years ago
Aleksander Machniak dba4988bbe Elastic: Fix regression where recipient input didn't update internal input state (#6988) 5 years ago

@ -1,30 +0,0 @@
<?php
$config = array();
// Database configuration
$config['db_dsnw'] = 'sqlite:////tmp/sqlite.db?mode=0646';
// Test user credentials
$config['tests_username'] = 'test';
$config['tests_password'] = 'test';
// GreenMail
$config['smtp_port'] = 25;
// Settings required by the tests
$config['create_default_folders'] = true;
$config['skin'] = 'elastic';
$config['support_url'] = 'http://support.url';
// Plugins with tests
$config['plugins'] = [
'archive',
'attachment_reminder',
'markasjunk',
'zipdownload'
];
$config['archive_mbox'] = 'Archive';

@ -1,22 +0,0 @@
#!/bin/bash
# The script is intended for use on Travis with Trusty distribution
DIR=$(dirname $0)
# Enable xdebug for code coverage
if [ "$CODE_COVERAGE" != 1 ]; then phpenv config-rm xdebug.ini || true; fi
cd $DIR/..
cp composer.json-dist composer.json
# Add laravel/dusk for Browser tests
if [ "$BROWSER_TESTS" = 1 ]; then composer require "laravel/dusk:~5.9.1" --no-update; fi
# Remove qr-code as it requires php-gd which is not always available on Travis
# and we don't really need it for tests
composer remove endroid/qr-code --no-update
# Install PHP dependencies
composer install --prefer-dist

@ -1,25 +0,0 @@
#!/bin/bash
# The script is intended for use on Travis with Trusty distribution
# It executes unit and functional tests
DIR=$(dirname $0)
cd $DIR/..
if [ "$CODE_COVERAGE" = 1 ]
then
CODE_COVERAGE_ARGS="--coverage-text"
fi
vendor/bin/phpunit -c tests/phpunit.xml $CODE_COVERAGE_ARGS
if [ "$BROWSER_TESTS" = 1 ] && [ $? = 0 ]
then
.ci/setup.sh \
&& echo "TESTS_MODE: DESKTOP" \
&& TESTS_MODE=desktop vendor/bin/phpunit -c tests/Browser/phpunit.xml \
&& echo "TESTS_MODE: PHONE" \
&& TESTS_MODE=phone vendor/bin/phpunit -c tests/Browser/phpunit.xml \
&& echo "TESTS_MODE: TABLET" \
&& TESTS_MODE=tablet vendor/bin/phpunit -c tests/Browser/phpunit.xml
fi

@ -1,30 +0,0 @@
#!/bin/bash
# The script is intended for use on Travis with Trusty distribution
# It installs in-browser tests dependencies and prepares Roundcube instance
GMV=1.5.11
CHROMEVERSION=$(google-chrome-stable --version | tr -cd [:digit:]. | cut -d . -f 1)
GMARGS="-Dgreenmail.setup.all -Dgreenmail.users=test:test -Dgreenmail.startup.timeout=3000"
# Roundcube tests and instance configuration
cp .ci/config-test.inc.php config/config-test.inc.php
# Make temp and logs writeable
sudo chmod 777 temp logs
# Install javascript dependencies
bin/install-jsdeps.sh
# Compile Elastic's styles
lessc skins/elastic/styles/styles.less > skins/elastic/styles/styles.css
lessc skins/elastic/styles/print.less > skins/elastic/styles/print.css
lessc skins/elastic/styles/embed.less > skins/elastic/styles/embed.css
# Install proper WebDriver version for installed Chrome browser
php tests/Browser/install.php $CHROMEVERSION
# GreenMail server download, setup and start
wget https://repo1.maven.org/maven2/com/icegreen/greenmail-standalone/$GMV/greenmail-standalone-$GMV.jar \
&& (sudo java $GMARGS -jar greenmail-standalone-$GMV.jar &) \
&& sleep 5

6
.gitmodules vendored

@ -0,0 +1,6 @@
[submodule "plugins/postfixadmin-user-identities"]
path = plugins/postfixadmin-user-identities
url = git@git.banananet.work:banananetwork/roundcubemail-postfixadmin-user-identities.git
[submodule "plugins/swipe"]
path = plugins/swipe
url = git@git.banananet.work:banananetwork/roundcubemail-swipe.git

@ -34,8 +34,8 @@ Options -Indexes
# Disable page indexing
Header set X-Robots-Tag "noindex, nofollow"
# replace 'merge' with 'append' for Apache < 2.2.9
#Header merge Cache-Control public env=!NO_CACHE
# replace 'append' with 'merge' for Apache version 2.2.9 and later
#Header append Cache-Control public env=!NO_CACHE
# Optional security headers
# Only provides increased security if the browser supports those features

@ -1,6 +1,6 @@
language: php
dist: trusty #
dist: trusty
sudo: false
matrix:
@ -14,24 +14,18 @@ matrix:
env: CODE_COVERAGE=1
- php: 7.2
- php: 7.3
dist: bionic # for proper node-less version
env: BROWSER_TESTS=1
addons:
chrome: stable
apt:
packages:
- node-less
- php: 7.4
cache:
directories:
- $HOME/.composer
install:
- .ci/install.sh
- if [ "$CODE_COVERAGE" != 1 ]; then phpenv config-rm xdebug.ini || true; fi
- cp composer.json-dist composer.json
- composer install --prefer-dist
script:
- .ci/run.sh
- if [ "$CODE_COVERAGE" = 1 ]; then CODE_COVERAGE_ARGS="--coverage-text"; fi; vendor/bin/phpunit -c tests/phpunit.xml $CODE_COVERAGE_ARGS
notifications:
email: false

@ -1,40 +1,6 @@
CHANGELOG Roundcube Webmail
===========================
- Allow array in smtp_host config (#7296)
- Remove use of ext-iconv
- Support RFC8438: IMAP STATUS=SIZE - for faster folder size calculation (#7269)
- MySQL: Use utf8mb4 charset and utf8mb4_unicode_ci collation (#6535, #7113)
- Support for language codes up to 16 chars long (e.g. es-419) in database schema (#6851)
- Relaxed domain name validation for extended TLDs support (#5588)
- Allow opening application/octet-stream attachments according to filename extension (#6821)
- Added support for INSERT OR REPLACE queries (#6771)
- Allow skins to define which layout options they support (#7235)
- Extract RFC2231 attachment name from message headers (#6729, #6783)
- Archive: Added options to split archive by year or year+month and folder (#7216)
- Managesieve: Allow display name with email address in vacation :from field (#6760)
- Managesieve: Improve UX on custom header input (#7207)
- Managesieve: Fix bug where activation of forward/vacation rule could activate a wrong script (#7423)
- Managesieve: Fix bug where forward/vacation rule could end up being duplicated (#7349)
- Password: Added 'pwned' password strength driver (#7274)
- Add support for SameSite cookie attribute via session_samesite option (req PHP >= 7.3.0) (#6772)
- Elastic: Moving single recipients between recipient inputs with drag-n-drop (#5069)
- Elastic: Display a special icon for other users and shared namespace roots (#5012)
- Elastic: Support space-separated email addresses in recipient input (#6529, #6457)
- Change folders sorting so shared/other users namespaces are listed last (#5012)
- Display a warning and do not try to open empty attachments (#7332)
- Templates: Add support for expressions in object attributes (#7237)
- Templates: Add support for nested if conditions (#6818)
- Templates: Make [space][slash] ending of condition objects optional (#6954)
- Fix ISO-2022-JP-MS encoding issues (#7091)
- Fix so messages in threads with no root aren't displayed separately (#4999)
- Fix so anchor tags without href attribute are not modified (#7413)
- Fix bug where subfolders of special folders could have been duplicated on folder list
- Increase maximum size of contact jobtitle and department fields to 128 characters
- Fix missing newline after the logged line when writing to stdout (#7418)
- Elastic: Fix context menu (paste) on the recipient input (#7431)
- Fix problem with forwarding inline images attached to messages with no HTML part (#7414)
RELEASE 1.4.6
-------------
- Installer: Fix regression in SMTP test section (#7417)
@ -118,7 +84,6 @@ RELEASE 1.4.3
RELEASE 1.4.2
-------------
- Add support for PHPUnit 6 and 7 (#6870)
- Plugin API: Make actionbefore, before<action>, actionafter and after<action> events working with plugin actions (#7106)
- Managesieve: Replace "Filter disabled" with "Filter enabled" (#7028)
- Managesieve: Fix so modifier type select wasn't hidden after hiding modifier select on header change

@ -14,7 +14,7 @@ REQUIREMENTS
* PHP Version 5.4 or greater including:
- PCRE, DOM, JSON, Session, Sockets, OpenSSL, Mbstring, Filter, Ctype (required)
- PHP PDO with driver for either MySQL, PostgreSQL, SQL Server, Oracle or SQLite (required)
- Zip, Fileinfo, Intl, Exif (recommended)
- Iconv, Zip, Fileinfo, Intl, Exif (recommended)
- LDAP for LDAP addressbook support (optional)
- GD, Imagick (optional thumbnails generation, QR-code)
* PEAR and PEAR packages distributed with Roundcube or external:
@ -120,11 +120,6 @@ Note 1: 'password' is the master password for the roundcube user. It is strongly
recommended you replace this with a more secure password. Please keep in
mind: You need to specify this password later in 'config/db.inc.php'.
Note 2: When using MySQL < 5.7.7 or MariaDB < 10.2.2 it is required to configure
the database engine with:
innodb_large_prefix=true
innodb_file_format=Barracuda
* SQLite
--------
@ -255,6 +250,11 @@ To enable these features in apache the following modules need to be enabled:
The optimisation is already included in the .htaccess file in the top
directory of your installation.
If you are using Apache version 2.2.9 and later, in the .htaccess file
change the 'append' word to 'merge' for a more correct response. Keeping
as 'append' shouldn't cause any problems though changing to merge will
eliminate the possibility of duplicate 'public' headers in Cache-control.
Lighttpd:
---------
With Lightty the addition of Expire: tags by mod_expire is incompatible with

@ -4,13 +4,6 @@ Roundcube Webmail
[![Build Status](https://api.travis-ci.org/roundcube/roundcubemail.svg?branch=master)](https://travis-ci.org/roundcube/roundcubemail)
ATTENTION
---------
This is just a snapshot from the GIT repository and is **NOT A STABLE
version of Roundcube**. It's not recommended to replace an existing installation
of Roundcube with this version. Also using a separate database for this
installation is highly recommended.
INTRODUCTION
------------

@ -102,14 +102,14 @@ CREATE TABLE [dbo].[users] (
[last_login] [datetime] NULL ,
[failed_login] [datetime] NULL ,
[failed_login_counter] [int] NULL ,
[language] [varchar] (16) COLLATE Latin1_General_CI_AI NULL ,
[language] [varchar] (5) COLLATE Latin1_General_CI_AI NULL ,
[preferences] [text] COLLATE Latin1_General_CI_AI NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[dictionary] (
[user_id] [int] ,
[language] [varchar] (16) COLLATE Latin1_General_CI_AI NOT NULL ,
[language] [varchar] (5) COLLATE Latin1_General_CI_AI NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@ -422,6 +422,6 @@ CREATE TRIGGER [contact_delete_member] ON [dbo].[contacts]
WHERE [contact_id] IN (SELECT [contact_id] FROM deleted)
GO
INSERT INTO [dbo].[system] ([name], [value]) VALUES ('roundcube-version', '2020020101')
INSERT INTO [dbo].[system] ([name], [value]) VALUES ('roundcube-version', '2019092900')
GO

@ -1,4 +0,0 @@
ALTER TABLE [dbo].[users] ALTER COLUMN [language] [varchar] (16) COLLATE Latin1_General_CI_AI NULL
GO
ALTER TABLE [dbo].[dictionary] ALTER COLUMN [language] [varchar] (16) COLLATE Latin1_General_CI_AI NOT NULL
GO

@ -12,7 +12,7 @@ CREATE TABLE `session` (
`vars` mediumtext NOT NULL,
PRIMARY KEY(`sess_id`),
INDEX `changed_index` (`changed`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `users`
@ -25,11 +25,11 @@ CREATE TABLE `users` (
`last_login` datetime DEFAULT NULL,
`failed_login` datetime DEFAULT NULL,
`failed_login_counter` int(10) UNSIGNED DEFAULT NULL,
`language` varchar(16),
`language` varchar(5),
`preferences` longtext,
PRIMARY KEY(`user_id`),
UNIQUE `username` (`username`, `mail_host`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache`
@ -43,7 +43,7 @@ CREATE TABLE `cache` (
CONSTRAINT `user_id_fk_cache` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_shared`
@ -54,7 +54,7 @@ CREATE TABLE `cache_shared` (
`data` longtext NOT NULL,
PRIMARY KEY (`cache_key`),
INDEX `expires_index` (`expires`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_index`
@ -69,7 +69,7 @@ CREATE TABLE `cache_index` (
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_thread`
@ -83,7 +83,7 @@ CREATE TABLE `cache_thread` (
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `cache_messages`
@ -99,7 +99,7 @@ CREATE TABLE `cache_messages` (
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `expires_index` (`expires`),
PRIMARY KEY (`user_id`, `mailbox`, `uid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contacts`
@ -119,7 +119,7 @@ CREATE TABLE `contacts` (
CONSTRAINT `user_id_fk_contacts` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `user_contacts_index` (`user_id`,`del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `contactgroups`
@ -133,7 +133,7 @@ CREATE TABLE `contactgroups` (
CONSTRAINT `user_id_fk_contactgroups` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `contactgroups_user_index` (`user_id`,`del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
CREATE TABLE `contactgroupmembers` (
`contactgroup_id` int(10) UNSIGNED NOT NULL,
@ -168,7 +168,7 @@ CREATE TABLE `identities` (
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
INDEX `user_identities_index` (`user_id`, `del`),
INDEX `email_identities_index` (`email`, `del`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `dictionary`
@ -176,12 +176,12 @@ CREATE TABLE `identities` (
CREATE TABLE `dictionary` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -- redundant, for compat. with Galera Cluster
`user_id` int(10) UNSIGNED DEFAULT NULL, -- NULL here is for "shared dictionaries"
`language` varchar(16) NOT NULL,
`language` varchar(5) NOT NULL,
`data` longtext NOT NULL,
CONSTRAINT `user_id_fk_dictionary` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `language`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `searches`
@ -196,7 +196,7 @@ CREATE TABLE `searches` (
CONSTRAINT `user_id_fk_searches` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `type`, `name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `filestore`
@ -211,7 +211,7 @@ CREATE TABLE `filestore` (
CONSTRAINT `user_id_fk_filestore` FOREIGN KEY (`user_id`)
REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE `uniqueness` (`user_id`, `context`, `filename`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-- Table structure for table `system`
@ -219,8 +219,8 @@ CREATE TABLE `system` (
`name` varchar(64) NOT NULL,
`value` mediumtext,
PRIMARY KEY(`name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
/*!40014 SET FOREIGN_KEY_CHECKS=1 */;
INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2020020101');
INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2019092900');

@ -1,2 +0,0 @@
ALTER TABLE `users` MODIFY `language` varchar(16);
ALTER TABLE `dictionary` MODIFY `language` varchar(16) NOT NULL;

@ -1,21 +0,0 @@
ALTER TABLE `session` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `cache` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `cache_shared` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `cache_index` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `cache_thread` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `cache_messages` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `contacts` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `contactgroups` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `identities` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `dictionary` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `searches` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `filestore` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `system` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `users` CHANGE `username` `username` varchar(128) BINARY NOT NULL;
ALTER TABLE `cache` CHANGE `cache_key` `cache_key` varchar(128) BINARY NOT NULL;
ALTER TABLE `cache_shared` CHANGE `cache_key` `cache_key` varchar(255) BINARY NOT NULL;
ALTER TABLE `cache_index` CHANGE `mailbox` `mailbox` varchar(255) BINARY NOT NULL;
ALTER TABLE `cache_thread` CHANGE `mailbox` `mailbox` varchar(255) BINARY NOT NULL;
ALTER TABLE `cache_messages` CHANGE `mailbox` `mailbox` varchar(255) BINARY NOT NULL;

@ -9,7 +9,7 @@ CREATE TABLE "users" (
"last_login" timestamp with time zone DEFAULT NULL,
"failed_login" timestamp with time zone DEFAULT NULL,
"failed_login_counter" integer DEFAULT NULL,
"language" varchar(16),
"language" varchar(5),
"preferences" long DEFAULT NULL,
CONSTRAINT "users_username_key" UNIQUE ("username", "mail_host")
);
@ -186,7 +186,7 @@ CREATE INDEX "cache_messages_expires_idx" ON "cache_messages" ("expires");
CREATE TABLE "dictionary" (
"user_id" integer DEFAULT NULL
REFERENCES "users" ("user_id") ON DELETE CASCADE,
"language" varchar(16) NOT NULL,
"language" varchar(5) NOT NULL,
"data" long DEFAULT NULL,
CONSTRAINT "dictionary_user_id_lang_key" UNIQUE ("user_id", "language")
);
@ -238,4 +238,4 @@ CREATE TABLE "system" (
"value" long
);
INSERT INTO "system" ("name", "value") VALUES ('roundcube-version', '2020020101');
INSERT INTO "system" ("name", "value") VALUES ('roundcube-version', '2019092900');

@ -1,2 +0,0 @@
ALTER TABLE "users" MODIFY "language" varchar(16) NOT NULL;
ALTER TABLE "dictionary" MODIFY "language" varchar(16);

@ -24,7 +24,7 @@ CREATE TABLE users (
last_login timestamp with time zone DEFAULT NULL,
failed_login timestamp with time zone DEFAULT NULL,
failed_login_counter integer DEFAULT NULL,
"language" varchar(16),
"language" varchar(5),
preferences text DEFAULT ''::text NOT NULL,
CONSTRAINT users_username_key UNIQUE (username, mail_host)
);
@ -246,7 +246,7 @@ CREATE INDEX cache_messages_expires_idx ON cache_messages (expires);
CREATE TABLE dictionary (
user_id integer DEFAULT NULL
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
"language" varchar(16) NOT NULL,
"language" varchar(5) NOT NULL,
data text NOT NULL,
CONSTRAINT dictionary_user_id_language_key UNIQUE (user_id, "language")
);
@ -314,4 +314,4 @@ CREATE TABLE "system" (
value text
);
INSERT INTO "system" (name, value) VALUES ('roundcube-version', '2020020101');
INSERT INTO "system" (name, value) VALUES ('roundcube-version', '2019092900');

@ -1,2 +0,0 @@
ALTER TABLE "dictionary" ALTER COLUMN "language" TYPE varchar(16);
ALTER TABLE "users" ALTER COLUMN "language" TYPE varchar(16);

@ -74,7 +74,7 @@ CREATE TABLE users (
last_login datetime DEFAULT NULL,
failed_login datetime DEFAULT NULL,
failed_login_counter integer DEFAULT NULL,
language varchar(16),
language varchar(5),
preferences text NOT NULL default ''
);
@ -99,11 +99,11 @@ CREATE INDEX ix_session_changed ON session (changed);
CREATE TABLE dictionary (
user_id integer DEFAULT NULL,
language varchar(16) NOT NULL,
"language" varchar(5) NOT NULL,
data text NOT NULL
);
CREATE UNIQUE INDEX ix_dictionary_user_language ON dictionary (user_id, language);
CREATE UNIQUE INDEX ix_dictionary_user_language ON dictionary (user_id, "language");
--
-- Table structure for table searches
@ -215,4 +215,4 @@ CREATE TABLE system (
value text NOT NULL
);
INSERT INTO system (name, value) VALUES ('roundcube-version', '2020020101');
INSERT INTO system (name, value) VALUES ('roundcube-version', '2019092900');

@ -1,57 +0,0 @@
CREATE TABLE tmp_users (
user_id integer NOT NULL PRIMARY KEY,
username varchar(128) NOT NULL default '',
mail_host varchar(128) NOT NULL default '',
created datetime NOT NULL default '0000-00-00 00:00:00',
last_login datetime DEFAULT NULL,
failed_login datetime DEFAULT NULL,
failed_login_counter integer DEFAULT NULL,
language varchar(16),
preferences text NOT NULL default ''
);
INSERT INTO tmp_users (user_id, username, mail_host, created, last_login, failed_login, failed_login_counter, language, preferences)
SELECT user_id, username, mail_host, created, last_login, failed_login, failed_login_counter, language, preferences FROM users;
DROP TABLE users;
CREATE TABLE users (
user_id integer NOT NULL PRIMARY KEY,
username varchar(128) NOT NULL default '',
mail_host varchar(128) NOT NULL default '',
created datetime NOT NULL default '0000-00-00 00:00:00',
last_login datetime DEFAULT NULL,
failed_login datetime DEFAULT NULL,
failed_login_counter integer DEFAULT NULL,
language varchar(16),
preferences text NOT NULL default ''
);
INSERT INTO users (user_id, username, mail_host, created, last_login, failed_login, failed_login_counter, language, preferences)
SELECT user_id, username, mail_host, created, last_login, failed_login, failed_login_counter, language, preferences FROM tmp_users;
CREATE UNIQUE INDEX ix_users_username ON users(username, mail_host);
DROP TABLE tmp_users;
DROP TABLE users;
CREATE TABLE tmp_dictionary (
user_id integer DEFAULT NULL,
language varchar(16) NOT NULL,
data text NOT NULL
);
INSERT INTO tmp_dictionary (user_id, language, data) SELECT user_id, language, data FROM dictionary;
CREATE TABLE dictionary (
user_id integer DEFAULT NULL,
language varchar(16) NOT NULL,
data text NOT NULL
);
INSERT INTO dictionary (user_id, language, data) SELECT user_id, language, data FROM tmp_dictionary;
CREATE UNIQUE INDEX ix_dictionary_user_language ON dictionary (user_id, language);
DROP TABLE tmp_dictionary;

@ -23,9 +23,6 @@ removed again.
WARNING: Make sure files have proper owner/group for your setup. If you use
tar to extract the package, `--no-same-owner` option might be helpful.
WARNING: See Post-Upgrade Activities section below.
WARNING: If you use MySQL < 5.7.7 or MariaDB < 10.2.2 make sure to configure it with:
innodb_large_prefix=true
innodb_file_format=Barracuda
Updating manually

@ -22,7 +22,7 @@
"endroid/qr-code": "~1.6.5"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6 || ^7"
"phpunit/phpunit": "^4.8.36 || ^5.7.21"
},
"suggest": {
"kolab/net_ldap3": "~1.1.1 required for connecting to LDAP",

@ -50,8 +50,6 @@ $config['default_host'] = 'localhost';
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
// To specify differnt SMTP servers for different IMAP hosts provide an array
// of IMAP host (no prefix or port) and SMTP server e.g. array('imap.example.com' => 'smtp.example.net')
$config['smtp_server'] = 'localhost';
// SMTP port. Use 25 for cleartext, 465 for Implicit TLS, or 587 for STARTTLS (default)

@ -267,8 +267,6 @@ $config['messages_cache_threshold'] = 50;
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
// To specify differnt SMTP servers for different IMAP hosts provide an array
// of IMAP host (no prefix or port) and SMTP server e.g. array('imap.example.com' => 'smtp.example.net')
$config['smtp_server'] = 'localhost';
// SMTP port. Use 25 for cleartext, 465 for Implicit TLS, or 587 for STARTTLS (default)
@ -504,11 +502,6 @@ $config['session_auth_name'] = null;
// Session path. Defaults to PHP session.cookie_path setting.
$config['session_path'] = null;
// Session samesite. Defaults to PHP session.cookie_samesite setting.
// Requires PHP >= 7.3.0, see https://wiki.php.net/rfc/same-site-cookie for more info
// Possible values: null (default), 'Lax', or 'Strict'
$config['session_samesite'] = null;
// Backend to use for session storage. Can either be 'db' (default), 'redis', 'memcache', or 'php'
//
// If set to 'memcache' or 'memcached', a list of servers need to be specified in 'memcache_hosts'
@ -895,7 +888,6 @@ $config['ldap_public']['Verisign'] = array(
// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part)
// %z - IMAP domain (IMAP hostname without the first part)
// For example %n = mail.domain.tld, %t = domain.tld
// Note: Host can also be a full URI e.g. ldaps://hostname.local:636 (for SSL)
'hosts' => array('directory.verisign.com'),
'port' => 389,
'use_tls' => false,

@ -0,0 +1,27 @@
<?php
/*
+-----------------------------------------------------------------------+
| Roundcube Webmail Selenium Tests Entry Point |
| |
| 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: |
| This is the public entry point for all HTTP requests to the |
| Roundcube webmail application loading the 'tests' environment. |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <thomas@roundcube.net> |
+-----------------------------------------------------------------------+
*/
define('INSTALL_PATH', realpath(__DIR__) . '/');
$GLOBALS['env'] = 'test';
// include index.php from application root directory
include INSTALL_PATH . 'index.php';

@ -2,7 +2,7 @@
/**
+-------------------------------------------------------------------------+
| Roundcube Webmail IMAP Client |
| Version 1.5-git |
| Version 1.4.6 |
| |
| Copyright (C) The Roundcube Dev Team |
| |

@ -37,6 +37,7 @@ $required_php_exts = array(
$optional_php_exts = array(
'FileInfo' => 'fileinfo',
'Libiconv' => 'iconv',
'Intl' => 'intl',
'Exif' => 'exif',
'LDAP' => 'ldap',
@ -75,6 +76,7 @@ $source_urls = array(
'Session' => 'http://www.php.net/manual/en/book.session.php',
'PCRE' => 'http://www.php.net/manual/en/book.pcre.php',
'FileInfo' => 'http://www.php.net/manual/en/book.fileinfo.php',
'Libiconv' => 'http://www.php.net/manual/en/book.iconv.php',
'Multibyte' => 'http://www.php.net/manual/en/book.mbstring.php',
'OpenSSL' => 'http://www.php.net/manual/en/book.openssl.php',
'JSON' => 'http://www.php.net/manual/en/book.json.php',

@ -104,7 +104,7 @@ $input_support = new html_inputfield(array('name' => '_support_url', 'size' => 5
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>
<div>Provide an 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

@ -3,7 +3,7 @@
/**
+-------------------------------------------------------------------------+
| Roundcube Webmail setup tool |
| Version 1.5-git |
| Version 1.4.6 |
| |
| Copyright (C) The Roundcube Dev Team |
| |

@ -263,15 +263,6 @@ else {
echo "<br/>";
}
$smtp_hosts = $RCI->get_hostlist('smtp_server');
if (!empty($smtp_hosts)) {
$smtp_host_field = new html_select(array('name' => '_smtp_host', 'id' => 'smtp_server'));
$smtp_host_field->add($smtp_hosts);
}
else {
$smtp_host_field = new html_inputfield(array('name' => '_smtp_host', 'id' => 'smtp_server'));
}
$user = $RCI->getprop('smtp_user', '(none)');
$pass = $RCI->getprop('smtp_pass', '(none)');
@ -299,7 +290,7 @@ else {
<tbody>
<tr>
<td><label for="smtp_server">Server</label></td>
<td><?php echo $smtp_host_field->show($_POST['_smtp_host']); ?></td>
<td><?php echo rcube::Q(rcube_utils::parse_host($RCI->getprop('smtp_server', 'localhost'))); ?></td>
</tr>
<tr>
<td><label for="smtp_port">Port</label></td>
@ -326,9 +317,6 @@ if (isset($_POST['sendmail'])) {
echo '<p>Trying to send email...<br />';
$smtp_host = trim($_POST['_smtp_host']);
$smtp_port = $RCI->getprop('smtp_port');
$from = rcube_utils::idn_to_ascii(trim($_POST['_from']));
$to = rcube_utils::idn_to_ascii(trim($_POST['_to']));
@ -358,7 +346,8 @@ if (isset($_POST['sendmail'])) {
$head = $mail_object->txtHeaders($send_headers);
$SMTP = new rcube_smtp();
$SMTP->connect($smtp_host, $smtp_port, $CONFIG['smtp_user'], $CONFIG['smtp_pass']);
$SMTP->connect(rcube_utils::parse_host($RCI->getprop('smtp_server')),
$RCI->getprop('smtp_port'), $CONFIG['smtp_user'], $CONFIG['smtp_pass']);
$status = $SMTP->send_mail($headers['From'], $headers['To'], $head, $body);
$smtp_response = $SMTP->get_response();

@ -1,6 +1,6 @@
<?php
class Acl_Plugin extends PHPUnit\Framework\TestCase
class Acl_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class AdditionalMessageHeaders_Plugin extends PHPUnit\Framework\TestCase
class AdditionalMessageHeaders_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -199,17 +199,6 @@ class archive extends rcube_plugin
case 'sender':
$subfolder = $this->sender_subfolder($message->get('from'));
break;
case 'folderyear':
$subfolder = $rcmail->format_date($message->timestamp, 'Y')
. $delimiter . $mbox;
break;
case 'foldermonth':
$subfolder = $rcmail->format_date($message->timestamp, 'Y')
. $delimiter . $rcmail->format_date($message->timestamp, 'm')
. $delimiter . $mbox;
break;
}
// compose full folder path
@ -425,8 +414,6 @@ class archive extends rcube_plugin
$archive_type->add($this->gettext('archivetypetbmonth'), 'tbmonth');
$archive_type->add($this->gettext('archivetypesender'), 'sender');
$archive_type->add($this->gettext('archivetypefolder'), 'folder');
$archive_type->add($this->gettext('archivetypefolderyear'), 'folderyear');
$archive_type->add($this->gettext('archivetypefoldermonth'), 'foldermonth');
$args['blocks']['archive'] = array(
'name' => rcube::Q($this->gettext('settingstitle')),

@ -3,7 +3,7 @@
"type": "roundcube-plugin",
"description": "This adds a button to move the selected messages to an archive folder. The folder (and the optional structure of subfolders) can be selected in the settings panel.",
"license": "GPLv3+",
"version": "3.5",
"version": "3.4",
"authors": [
{
"name": "Thomas Bruederli",

@ -24,8 +24,6 @@ $labels['settingstitle'] = 'Archive';
$labels['archivetype'] = 'Divide archive by';
$labels['archivetypeyear'] = 'Year (e.g. Archive/2012)';
$labels['archivetypemonth'] = 'Month (e.g. Archive/2012/06)';
$labels['archivetypefolderyear'] = 'Year then original folder (e.g. Archive/...)';
$labels['archivetypefoldermonth'] = 'Month then original folder (e.g. Archive/2012/...)';
$labels['archivetypefolder'] = 'Original folder';
$labels['archivetypesender'] = 'Sender email';
$labels['unkownsender'] = 'unknown';

@ -26,8 +26,6 @@ $labels['archivetypeyear'] = 'Year (e.g. Archive/2012)';
$labels['archivetypemonth'] = 'Month (e.g. Archive/2012/06)';
$labels['archivetypetbmonth'] = 'Month - Thunderbird compatible (e.g. Archive/2012/2012-06)';
$labels['archivetypefolder'] = 'Original folder';
$labels['archivetypefolderyear'] = 'Year then original folder (e.g. Archive/...)';
$labels['archivetypefoldermonth'] = 'Month then original folder (e.g. Archive/2012/...)';
$labels['archivetypesender'] = 'Sender email';
$labels['unkownsender'] = 'unknown';
$labels['readonarchive'] = 'Mark the message as read on archive';

@ -27,8 +27,8 @@ $labels['archivetypeyear'] = 'Year (e.g. Archive/2012)';
$labels['archivetypemonth'] = 'Month (e.g. Archive/2012/06)';
$labels['archivetypetbmonth'] = 'Month - Thunderbird compatible (e.g. Archive/2012/2012-06)';
$labels['archivetypefolder'] = 'Original folder';
$labels['archivetypefolderyear'] = 'Year and the original folder (e.g. Archive/2012/...)';
$labels['archivetypefoldermonth'] = 'Year, month and the original folder (e.g. Archive/2012/06/...)';
$labels['archivetypesender'] = 'Sender email';
$labels['unkownsender'] = 'unknown';
$labels['readonarchive'] = 'Mark the message as read on archive';
?>

@ -26,8 +26,7 @@ $labels['archivetypeyear'] = 'Année (p. ex. Archives/2012)';
$labels['archivetypemonth'] = 'Mois (p. ex. Archives/2012/06)';
$labels['archivetypetbmonth'] = 'Mois compatible avec Thunderbird (p.ex. Archive/2012/2012-06)';
$labels['archivetypefolder'] = 'Dossier original';
$labels['archivetypefolderyear'] = 'Année puis dossier original (p. ex. Archive/...)';
$labels['archivetypefoldermonth'] = 'Mois puis dossier original (p. ex. Archive/2012/...)';
$labels['archivetypesender'] = 'Courriel de lexpéditeur';
$labels['unkownsender'] = 'inconnu';
$labels['readonarchive'] = 'Marquer le courriel comme lu lors de larchivage';
?>

@ -1,6 +1,6 @@
<?php
class Archive_Plugin extends PHPUnit\Framework\TestCase
class Archive_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,82 +0,0 @@
<?php
namespace Tests\Browser\Plugins\Archive;
use Tests\Browser\Components\Popupmenu;
class MailTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_db();
\bootstrap::init_imap();
\bootstrap::purge_mailbox('INBOX');
\bootstrap::purge_mailbox('Archive');
// import single email messages
foreach (glob(TESTS_DIR . 'data/mail/list_00.eml') as $f) {
\bootstrap::import_message($f, 'INBOX');
}
}
public function testMailUI()
{
$this->browse(function ($browser) {
$browser->go('mail');
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button');
}
// Folders list
$browser->whenAvailable('#mailboxlist', function ($browser) {
$browser->assertVisible('li.mailbox.archive')
->assertMissing('li.mailbox.archive .unreadcount');
});
if (!$browser->isDesktop()) {
$browser->click('.back-list-button');
}
// Toolbar menu (Archive button inactive)
$browser->assertToolbarMenu([], ['archive']);
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->ctrlClick('tr:last-child');
});
$browser->clickToolbarMenuItem('archive')
->waitForMessage('confirmation', 'Successfully archived')
->closeMessage('confirmation');
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button');
}
// Folders list
$browser->whenAvailable('#mailboxlist', function ($browser) {
$browser->assertSeeIn('li.mailbox.archive .unreadcount', '1')
->click('li.mailbox.archive')
->waitUntilNotBusy();
});
// Messages list contains the moved message
$browser->assertElementsCount('#messagelist tbody tr', 1);
// Toolbar menu (Archive button inactive again)
$browser->assertToolbarMenu([], ['archive']);
// Test archive class on folder in folder selector
$browser->ctrlClick('#messagelist tbody tr')
->clickToolbarMenuItem('more')
->with(new Popupmenu('message-menu'), function ($browser) {
$browser->clickMenuItem('move');
})
->with(new Popupmenu('folder-selector'), function ($browser) {
$browser->assertVisible('li.archive')
->assertSeeIn('li.archive', 'Archive');
})
->click(); // close menus
});
}
}

@ -1,149 +0,0 @@
<?php
namespace Tests\Browser\Plugins\Archive;
class SettingsTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_db();
}
/**
* Test Folders UI
*/
public function testFolders()
{
$this->browse(function ($browser) {
$browser->go('settings', 'folders');
// Folders list
$browser->with('#subscription-table', function ($browser) {
$browser->assertHasClass('li:nth-child(7)', 'archive')
->assertSeeIn('li:nth-child(7)', 'Archive')
->assertPresent('li:nth-child(7) [type=checkbox][disabled]');
});
});
}
/**
* Test Preferences UI
*/
public function testPreferences()
{
$this->browse(function ($browser) {
$browser->go('settings');
if (!$browser->isDesktop()) {
$browser->click('#settings-menu li.preferences')
->waitFor('#sections-table');
}
$browser->click('#sections-table tr.folders');
if ($browser->isPhone()) {
$browser->waitFor('#layout-content .footer a.button.submit:not(.disabled)');
}
$browser->withinFrame('#preferences-frame', function ($browser) {
if (!$browser->isPhone()) {
$browser->waitFor('.formbuttons button.submit');
}
// Main Options fieldset
$browser->with('form.propform fieldset.main', function ($browser) {
$browser->assertSeeIn('legend', 'Main Options');
$browser->assertSeeIn('label[for=_archive_mbox]', 'Archive')
->assertVisible('select[name=_archive_mbox]')
->assertSelected('select[name=_archive_mbox]', 'Archive');
$browser->select('_archive_mbox', 'Drafts');
});
// Archive fieldset
$browser->with('form.propform fieldset.archive', function ($browser) {
$browser->assertSeeIn('legend', 'Archive');
$browser->assertSeeIn('label[for=ff_archive_type]', 'Divide archive by')
->assertVisible('select[name=_archive_type]')
->assertSelected('select[name=_archive_type]', '')
->with('select[name=_archive_type]', function ($browser) {
$browser->assertValue('option:nth-child(1)', '')
->assertSeeIn('option:nth-child(1)', 'None')
->assertValue('option:nth-child(2)', 'year')
->assertSeeIn('option:nth-child(2)', 'Year (e.g. Archive/2012)')
->assertValue('option:nth-child(3)', 'month')
->assertSeeIn('option:nth-child(3)', 'Month (e.g. Archive/2012/06)')
->assertValue('option:nth-child(4)', 'tbmonth')
->assertSeeIn('option:nth-child(4)', 'Month - Thunderbird compatible (e.g. Archive/2012/2012-06)')
->assertValue('option:nth-child(5)', 'sender')
->assertSeeIn('option:nth-child(5)', 'Sender email')
->assertValue('option:nth-child(6)', 'folder')
->assertSeeIn('option:nth-child(6)', 'Original folder');
});
$browser->select('_archive_type', 'year');
});
// Submit form
if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit');
}
});
if ($browser->isPhone()) {
$browser->click('#layout-content .footer a.submit');
}
$browser->waitForMessage('confirmation', 'Successfully saved');
// Verify if every option has been updated
$browser->withinFrame('#preferences-frame', function ($browser) {
$browser->assertSelected('_archive_mbox', 'Drafts');
$browser->assertSelected('_archive_type', 'year');
});
});
}
/**
* Test Preferences UI (Server Settings)
*/
public function testServerSettings()
{
$this->browse(function ($browser) {
$browser->go('settings', 'preferences');
$browser->click('#sections-table tr.server');
$browser->withinFrame('#preferences-frame', function ($browser) {
if (!$browser->isPhone()) {
$browser->waitFor('.formbuttons button.submit');
}
// Main Options fieldset
$browser->with('form.propform fieldset.main', function ($browser) {
$browser->assertSeeIn('label[for=ff_read_on_archive]', 'Mark the message as read on archive')
->assertCheckboxState('_read_on_archive', false)
->setCheckboxState('_read_on_archive', true);
});
// Submit form
if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit');
}
});
if ($browser->isPhone()) {
$browser->click('#layout-content .footer a.submit');
}
$browser->waitForMessage('confirmation', 'Successfully saved');
// Verify if every option has been updated
$browser->withinFrame('#preferences-frame', function ($browser) {
$browser->assertCheckboxState('_read_on_archive', true);
});
});
}
}

@ -1,6 +1,6 @@
<?php
class AttachmentReminder_Plugin extends PHPUnit\Framework\TestCase
class AttachmentReminder_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,94 +0,0 @@
<?php
namespace Tests\Browser\Plugins\AttachmentReminder;
use Tests\Browser\Components\Dialog;
class PluginTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_db();
}
/**
* Test Preferences UI (Composing Messages)
*/
public function testPreferences()
{
$this->browse(function ($browser) {
$browser->go('settings', 'preferences');
$browser->click('#sections-table tr.compose');
$browser->withinFrame('#preferences-frame', function ($browser) {
if (!$browser->isPhone()) {
$browser->waitFor('.formbuttons button.submit');
}
// Main Options fieldset
$browser->with('form.propform fieldset.main', function ($browser) {
$browser->assertSeeIn('label[for=rcmfd_attachment_reminder]', 'Remind about forgotten attachments')
->assertCheckboxState('_attachment_reminder', false)
->setCheckboxState('_attachment_reminder', true);
});
// Submit form
if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit');
}
});
if ($browser->isPhone()) {
$browser->click('#layout-content .footer a.submit');
}
$browser->waitForMessage('confirmation', 'Successfully saved');
// Verify if every option has been updated
$browser->withinFrame('#preferences-frame', function ($browser) {
$browser->assertCheckboxState('_attachment_reminder', true);
});
});
}
/**
* Test Mail Compose page
*
* @depends testPreferences
*/
public function testMailCompose()
{
$this->browse(function ($browser) {
$send_btn = $browser->isPhone() ? '.buttons a.send' : '.formbuttons button.send';
$browser->go('mail', 'compose');
$browser->waitFor('#compose_to')
->type('#compose_to input', 'test@domain.tld')
->type('#compose-subject', 'subject')
->type('#composebody', 'File attached')
->click($send_btn);
// Expect a dialog, Click "Attach a file" button
$browser->with(new Dialog(), function ($browser) {
$browser->assertDialogTitle('Missing attachment?')
->assertDialogContent('Did you forget to attach a file?')
->assertButton('mainaction.attach', 'Attach a file')
->assertButton('send', 'Send')
->clickButton('mainaction.attach');
});
// Click the Send button again
$browser->click($send_btn);
// Expect the dialog again, click Send button (in the dialog)
$browser->with(new Dialog(), function ($browser) {
$browser->assertDialogTitle('Missing attachment?')
->clickButton('send');
});
$browser->waitForMessage('confirmation', 'Message sent successfully.');
});
}
}

@ -1,6 +1,6 @@
<?php
class Autologon_Plugin extends PHPUnit\Framework\TestCase
class Autologon_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class DatabaseAttachments_Plugin extends PHPUnit\Framework\TestCase
class DatabaseAttachments_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class DebugLogger_Plugin extends PHPUnit\Framework\TestCase
class DebugLogger_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Emoticons_Plugin extends PHPUnit\Framework\TestCase
class Emoticons_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class EmoticonsEngine extends PHPUnit\Framework\TestCase
class EmoticonsEngine extends PHPUnit_Framework_TestCase
{
function setUp()

@ -676,8 +676,17 @@ class enigma_driver_gnupg extends enigma_driver
continue;
}
$unique = array('user_id' => $this->rc->user->ID, 'context' => 'enigma', 'filename' => $filename);
$result = $db->insert_or_update($table, $unique, array('mtime', 'data'), array($mtime, $data));
if (empty($existing)) {
$result = $db->query(
"INSERT INTO $table (`user_id`, `context`, `filename`, `mtime`, `data`)"
. " VALUES(?, 'enigma', ?, ?, ?)",
$this->rc->user->ID, $filename, $mtime, $data);
}
else {
$result = $db->query(
"UPDATE $table SET `mtime` = ?, `data` = ? WHERE `file_id` = ?",
$mtime, $data, $existing['file_id']);
}
if ($db->is_error($result)) {
rcube::raise_error(array(

@ -66,7 +66,6 @@ $labels['keydisable'] = 'Disable';
$labels['keyrevoke'] = 'Revoke';
$labels['keysend'] = 'Send public key in a message';
$labels['keychpass'] = 'Change password';
$labels['keyadd'] = 'Add key';
$labels['newkeyident'] = 'Identity';
$labels['newkeypass'] = 'Password';
@ -83,6 +82,7 @@ $labels['sendunencrypted'] = 'Send unencrypted';
$labels['enterkeypasstitle'] = 'Enter key passphrase';
$labels['enterkeypass'] = 'A passphrase is needed to unlock the secret key ($keyid) for user: $user.';
$labels['arialabelkeyexportoptions'] = 'Keys export options';
$labels['attachpubkeymsg'] = 'Attach my public key';
$labels['keyexportprompt'] = 'Do you want to include secret keys in the saved OpenPGP keys file?';
@ -96,10 +96,6 @@ $labels['managekeys'] = 'Manage PGP keys';
$labels['identitymatchingprivkeys'] = 'You have $nr matching PGP private keys stored in your keyring:';
$labels['identitynoprivkeys'] = 'This sender identity doesn\'t yet have a PGP private key stored in your keyring.';
$labels['arialabelkeyexportoptions'] = 'Keys export options';
$labels['arialabelkeysearchform'] = 'Keys search form';
$labels['arialabelkeyoptions'] = 'Key options';
$messages = array();
$messages['sigvalid'] = 'Verified signature from $sender.';
$messages['sigvalidpartial'] = 'Verified signature from $sender, but part of the body was not signed.';

@ -17,7 +17,7 @@
<roundcube:object name="keyslist" id="keys-table" class="listing" role="listbox" noheader="true"
data-list="keys_list" data-label-msg="listempty" />
</div>
<div class="pagenav menu footer small" role="toolbar" aria-label="<roundcube:label name="arialabellistnav" />">
<div class="pagenav menu footer small" role="toolbar">
<roundcube:button command="firstpage" type="link" class="firstpage disabled" classAct="firstpage"
title="firstpage" label="first" innerclass="inner" />
<roundcube:button command="previouspage" type="link" class="prevpage disabled" classAct="prevpage"

@ -1,6 +1,6 @@
<?php
class Enigma_Plugin extends PHPUnit\Framework\TestCase
class Enigma_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class ExampleAddressbook_Plugin extends PHPUnit\Framework\TestCase
class ExampleAddressbook_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class FilesystemAttachments_Plugin extends PHPUnit\Framework\TestCase
class FilesystemAttachments_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Help_Plugin extends PHPUnit\Framework\TestCase
class Help_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -33,7 +33,7 @@ function hide_blockquote()
// from merging lines from different quoting level
$('blockquote').before(document.createTextNode("\n"));
text = q.text().trim();
text = $.trim(q.text());
res = text.split(/\n/);
if (res.length <= limit) {

@ -1,6 +1,6 @@
<?php
class HideBlockquote_Plugin extends PHPUnit\Framework\TestCase
class HideBlockquote_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class HttpAuthentication_Plugin extends PHPUnit\Framework\TestCase
class HttpAuthentication_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Identicon_Plugin extends PHPUnit\Framework\TestCase
class Identicon_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class IdentitySelect_Plugin extends PHPUnit\Framework\TestCase
class IdentitySelect_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Jqueryui_Plugin extends PHPUnit\Framework\TestCase
class Jqueryui_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class KrbAuthentication_Plugin extends PHPUnit\Framework\TestCase
class KrbAuthentication_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,11 +1,7 @@
- Improve UX on custom header input (#7207)
- Allow display name with email address in vacation :from field (#6760)
- Replace "Filter disabled" with "Filter enabled" (#7028)
- Fix so modifier type select wasn't hidden after hiding modifier select on header change
- Fix filter selection after removing a first filter (#7079)
- Fix bug where it wasn't possible to save flag actions (#7188)
- Fix bug where activation of forward/vacation rule could activate a wrong script (#7423)
- Fix bug where forward/vacation rule could end up being duplicated (#7349)
* version 9.3 [2019-04-21]
-----------------------------------------------------------

@ -476,7 +476,7 @@ class rcube_sieve
/**
* This is our own debug handler for connection
*/
public function debug_handler($sieve, $message)
public function debug_handler(&$sieve, $message)
{
rcube::write_log('sieve', preg_replace('/\r\n$/', '', $message));
}

@ -248,9 +248,25 @@ class rcube_sieve_engine
else if ($list) {
$script_name = $list[0];
}
// create a new (initial) script
else {
// if script does not exist create one with default content
$this->create_default_script();
// if script not exists build default script contents
$script_file = $this->rc->config->get('managesieve_default');
$script_name = $this->rc->config->get('managesieve_script_name');
if (empty($script_name)) {
$script_name = 'roundcube';
}
if ($script_file && is_readable($script_file) && !is_dir($script_file)) {
$content = file_get_contents($script_file);
}
// add script and set it active
if ($this->sieve->save_script($script_name, $content)) {
$this->activate_script($script_name);
$this->list[] = $script_name;
}
}
}
@ -660,7 +676,7 @@ class rcube_sieve_engine
$addresses = rcube_utils::get_input_value('_action_addresses', rcube_utils::INPUT_POST, true);
$intervals = rcube_utils::get_input_value('_action_interval', rcube_utils::INPUT_POST);
$interval_types = rcube_utils::get_input_value('_action_interval_type', rcube_utils::INPUT_POST);
$from = rcube_utils::get_input_value('_action_from', rcube_utils::INPUT_POST, true);
$from = rcube_utils::get_input_value('_action_from', rcube_utils::INPUT_POST);
$subject = rcube_utils::get_input_value('_action_subject', rcube_utils::INPUT_POST, true);
$flags = rcube_utils::get_input_value('_action_flags', rcube_utils::INPUT_POST);
$varnames = rcube_utils::get_input_value('_action_varname', rcube_utils::INPUT_POST);
@ -1140,30 +1156,8 @@ class rcube_sieve_engine
}
}
if (!empty($this->form['actions'][$i]['from'])) {
// According to RFC5230 the :from string must specify a valid [RFC2822] mailbox-list
// we'll try to extract addresses and validate them separately
$from = rcube_mime::decode_address_list($this->form['actions'][$i]['from'], null, true, RCUBE_CHARSET);
foreach ((array) $from as $idx => $addr) {
if (empty($addr['mailto']) || !rcube_utils::check_email($addr['mailto'])) {
$this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning');
break;
}
else {
$from[$idx] = format_email_recipient($addr['mailto'], $addr['name']);
}
}
// Only one address is allowed (at least on cyrus imap)
if (is_array($from) && count($from) > 1) {
$this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning');
}
// Then we convert it back to RFC2822 format
if (empty($this->errors['actions'][$i]['from']) && !empty($from)) {
$this->form['actions'][$i]['from'] = Mail_mimePart::encodeHeader(
'From', implode(', ', $from), RCUBE_CHARSET, 'base64', '');
}
if (!empty($this->form['actions'][$i]['from']) && !rcube_utils::check_email($this->form['actions'][$i]['from'])) {
$this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning');
}
if ($this->form['actions'][$i]['reason'] == '')
@ -1757,17 +1751,11 @@ class rcube_sieve_engine
}
// custom header and variable inputs
$aout .= $this->list_input($id, 'custom_header', $custom, 15, false, array(
'disabled' => !isset($custom),
'class' => $this->error_class($id, 'test', 'header', 'custom_header'),
'placeholder' => $this->plugin->gettext('headername'),
'title' => $this->plugin->gettext('headername'),
)) . "\n";
$aout .= $this->list_input($id, 'custom_var', $customv, 15, false, array(
'disabled' => !isset($customv),
'class' => $this->error_class($id, 'test', 'header', 'custom_var')
)) . "\n";
$aout .= $this->list_input($id, 'custom_header', $custom, isset($custom),
$this->error_class($id, 'test', 'header', 'custom_header'), 15) . "\n";
$aout .= $this->list_input($id, 'custom_var', $customv, isset($customv),
$this->error_class($id, 'test', 'header', 'custom_var'), 15) . "\n";
$test = self::rule_test($rule);
$target = '';
@ -1820,10 +1808,9 @@ class rcube_sieve_engine
}
$tout .= $this->match_type_selector('rule_op', $id, $test, $rule['test']);
$tout .= $this->list_input($id, 'rule_target', $target, null, false, array(
'disabled' => $rule['test'] == 'size' || $rule['test'] == 'exists' || $rule['test'] == 'duplicate',
'class' => $this->error_class($id, 'test', 'target', 'rule_target')
)) . "\n";
$tout .= $this->list_input($id, 'rule_target', $target,
$rule['test'] != 'size' && $rule['test'] != 'exists' && $rule['test'] != 'duplicate',
$this->error_class($id, 'test', 'target', 'rule_target')) . "\n";
$select_size_op = new html_select(array('name' => "_rule_size_op[$id]", 'id' => 'rule_size_op'.$id, 'class' => 'input-group-prepend'));
$select_size_op->add(rcube::Q($this->plugin->gettext('filterover')), 'over');
@ -1911,9 +1898,8 @@ class rcube_sieve_engine
$mout .= '<div id="rule_mime' .$id. '" class="adv input-group"' . (!$need_mime ? ' style="display:none"' : '') . '>';
$mout .= html::span('label input-group-prepend', html::span('input-group-text', rcube::Q($this->plugin->gettext('mime'))));
$mout .= $select_mime->show($mime_type);
$mout .= $this->list_input($id, 'rule_mime_param', $rule['mime-param'], 30, $mime_type != 'param', array(
'class' => $this->error_class($id, 'test', 'mime_param', 'rule_mime_param')
));
$mout .= $this->list_input($id, 'rule_mime_param', $rule['mime-param'], true,
$this->error_class($id, 'test', 'mime_param', 'rule_mime_param'), 30, $mime_type != 'param');
$mout .= '</div>';
}
@ -2207,16 +2193,7 @@ class rcube_sieve_engine
}
if ($from_addr) {
$default_identity = $this->rc->user->list_emails(true);
$action['from'] = format_email_recipient($default_identity['email'], $default_identity['name']);
}
}
else if (!empty($action['from'])) {
$from = rcube_mime::decode_address_list($action['from'], null, true, RCUBE_CHARSET);
foreach ((array) $from as $idx => $addr) {
$from[$idx] = format_email_recipient($addr['mailto'], $addr['name']);
}
if (!empty($from)) {
$action['from'] = implode(', ', $from);
$action['from'] = $default_identity['email'];
}
}
@ -2248,9 +2225,8 @@ class rcube_sieve_engine
'class' => $this->error_class($id, 'action', 'from', 'action_from'),
));
$out .= '<br><span class="label">' .rcube::Q($this->plugin->gettext('vacationaddr')) . '</span><br>';
$out .= $this->list_input($id, 'action_addresses', $action['addresses'], 30, false, array(
'class' => $this->error_class($id, 'action', 'addresses', 'action_addresses')
))
$out .= $this->list_input($id, 'action_addresses', $action['addresses'], true,
$this->error_class($id, 'action', 'addresses', 'action_addresses'), 30)
. html::a(array('href' => '#', 'onclick' => rcmail_output::JS_OBJECT_NAME . ".managesieve_vacation_addresses($id)"),
rcube::Q($this->plugin->gettext('filladdresses')));
$out .= '<br><span class="label">' . rcube::Q($this->plugin->gettext('vacationinterval')) . '</span><br>';
@ -2304,10 +2280,8 @@ class rcube_sieve_engine
. rcube::Q($this->plugin->gettext('flag'.$fidx))) . '<br>';
}
$flout .= $this->list_input($id, 'action_flags', $custom_flags, null, false, array(
'class' => $this->error_class($id, 'action', 'flag', 'action_flags_flag'),
'id' => "action_flags_flag{$id}"
));
$flout .= $this->list_input($id, 'action_flags', $custom_flags, true,
$this->error_class($id, 'action', 'flag', 'action_flags_flag'), null, false, "action_flags_flag{$id}");
$out .= html::div(array(
'id' => 'action_flags' . $id,
@ -2421,10 +2395,9 @@ class rcube_sieve_engine
$out .= '<br><span class="label">' . rcube::Q($this->plugin->gettext('notifyimportance')) . '</span><br>';
$out .= $select_importance->show($action['importance'] ? (int) $action['importance'] : 2);
$out .= '<div id="action_notifyoption_div' . $id . '">'
. '<span class="label">' . rcube::Q($this->plugin->gettext('notifyoptions')) . '</span><br>'
. $this->list_input($id, 'action_notifyoption', (array) $action['options'], 30, false, array(
'class' => $this->error_class($id, 'action', 'options', 'action_notifyoption')
)) . '</div>';
.'<span class="label">' . rcube::Q($this->plugin->gettext('notifyoptions')) . '</span><br>'
.$this->list_input($id, 'action_notifyoption', (array)$action['options'], true,
$this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>';
$out .= '</div>';
if (in_array('editheader', $this->exts)) {
@ -2480,9 +2453,8 @@ class rcube_sieve_engine
'class' => $this->error_class($id, 'action', 'name', 'action_delheader_name'),
));
$out .= '<br><label class="label" for="action_delheader_value' . $id .'">'. rcube::Q($this->plugin->gettext('headerpatterns')) .'</label><br>';
$out .= $this->list_input($id, 'action_delheader_value', $action['value'], null, false, array(
'class' => $this->error_class($id, 'action', 'value', 'action_delheader_value')
)) . "\n";
$out .= $this->list_input($id, 'action_delheader_value', $action['value'], true,
$this->error_class($id, 'action', 'value', 'action_delheader_value')) . "\n";
$out .= '<br><div class="adv input-group">';
$out .= html::span('label input-group-prepend', html::label(array(
'class' => 'input-group-text', 'for' => 'action_delheader_op'.$id), rcube::Q($this->plugin->gettext('headermatchtype'))));
@ -2603,25 +2575,22 @@ class rcube_sieve_engine
$this->rc->output->add_script($script, 'docready');
}
protected function list_input($id, $name, $value, $size = null, $hidden = false, $attrib = array())
protected function list_input($id, $name, $value, $enabled, $class, $size = null, $hidden = false, $elem_id = null)
{
$value = (array) $value;
$value = array_map(array('rcube', 'Q'), $value);
$value = implode("\n", $value);
$attrib = array_merge($attrib, array(
return html::tag('textarea', array(
'data-type' => 'list',
'data-size' => $size,
'data-hidden' => $hidden ?: null,
'name' => '_' . $name . '[' . $id . ']',
'id' => $elem_id ?: ($name.$id),
'disabled' => !$enabled,
'class' => $class,
'style' => 'display:none',
));
if (empty($attrib['id'])) {
$attrib['id'] = $name . $id;
}
return html::tag('textarea', $attrib, $value);
), $value);
}
/**
@ -3062,7 +3031,7 @@ class rcube_sieve_engine
'name' => "_{$name}[$id]",
'id' => "{$name}{$id}",
'style' => 'display:' .(!in_array($rule, array('size', 'duplicate')) ? 'inline' : 'none'),
'class' => 'operator_selector col-6',
'class' => 'operator_selector',
'onchange' => "{$name}_select(this, '{$id}')",
));
@ -3110,135 +3079,4 @@ class rcube_sieve_engine
return $select_comp->show($comparator);
}
/**
* Merge a rule into the script
*/
protected function merge_rule($rule, $existing, &$script_name = null)
{
// if script does not exist create a new one
if ($script_name === null || $script_name === false) {
$script_name = $this->create_default_script();
$this->sieve->load($script_name);
$this->init_script();
}
if (!$this->sieve->script) {
return false;
}
$script_active = in_array($script_name, $this->active);
$rule_active = empty($rule['disabled']);
$activate_script = false;
// If the script is not active, but the rule is,
// put the rule in an active script if there is one
if (!$script_active && $rule_active && !empty($this->active)) {
// Remove the rule from current (inactive) script
if (isset($existing['idx'])) {
unset($this->script[$existing['idx']]);
$this->sieve->script->content = $this->script;
$this->save_script($script_name);
}
// Load and init the active script, add the rule there
$this->sieve->load($script_name = $this->active[0]);
$this->init_script();
array_unshift($this->script, $rule);
}
// update original forward rule/script
else {
// re-order rules if needed
if (isset($rule['after']) && $rule['after'] !== '') {
// unset the original rule
if (isset($existing['idx'])) {
$this->script[$existing['idx']] = null;
}
// add at target position
if ($rule['after'] >= count($this->script) - 1) {
$this->script[] = $rule;
$this->script = array_values(array_filter($this->script));
$rule_index = count($this->script);
}
else {
$script = array();
foreach ($this->script as $idx => $r) {
if ($r) {
$script[] = $r;
}
if ($idx == $rule['after']) {
$script[] = $rule;
$rule_index = count($script);
}
}
$this->script = $script;
}
}
// rule exists, update it "in place"
else if (isset($existing['idx'])) {
$this->script[$existing['idx']] = $rule;
$rule_index = $existing['idx'];
}
// otherwise put the rule on top
else {
array_unshift($this->script, $rule);
$rule_index = 0;
}
// if the script is not active, but the rule is, we need to de-activate
// all rules except the forward rule
if (!$script_active && $rule_active) {
$activate_script = true;
foreach ($this->script as $idx => $r) {
if ($idx !== $rule_index) {
$this->script[$idx]['disabled'] = true;
}
}
}
}
$this->sieve->script->content = $this->script;
// save the script
$saved = $this->save_script($script_name);
// activate the script
if ($saved && $activate_script) {
$this->activate_script($script_name);
}
return $saved;
}
/**
* Create default script
*/
protected function create_default_script()
{
// if script not exists build default script contents
$script_file = $this->rc->config->get('managesieve_default');
$script_name = $this->rc->config->get('managesieve_script_name');
$kolab_master = $this->rc->config->get('managesieve_kolab_master');
$content = '';
if (empty($script_name)) {
$script_name = 'roundcube';
}
if ($script_file && !$kolab_master && is_readable($script_file) && !is_dir($script_file)) {
$content = file_get_contents($script_file);
}
// add script and set it active
if ($this->sieve->save_script($script_name, $content)) {
$this->activate_script($script_name);
$this->list[] = $script_name;
}
return $script_name;
}
}

@ -148,7 +148,7 @@ class rcube_sieve_forward extends rcube_sieve_engine
$action = 'copy';
}
else if ($act['type'] == 'stop') {
// we might loose information if there are rules after the stop
// we might loose information if there rules after the stop
$stop_found = true;
}
else if ($act['type'] == 'discard') {
@ -207,8 +207,10 @@ class rcube_sieve_forward extends rcube_sieve_engine
$date_extension = in_array('date', $this->exts);
$forward_tests = (array) $this->forward['tests'];
if (empty($target) || !rcube_utils::check_email($target)) {
$error = 'noemailwarning';
if ($action == 'redirect' || $action == 'copy') {
if (empty($target) || !rcube_utils::check_email($target)) {
$error = 'noemailwarning';
}
}
if (empty($forward_tests)) {
@ -222,13 +224,18 @@ class rcube_sieve_forward extends rcube_sieve_engine
$rule['disabled'] = $status == 'off';
$rule['tests'] = $forward_tests;
$rule['join'] = $date_extension ? count($forward_tests) > 1 : false;
$rule['actions'] = array(array(
'type' => 'redirect',
$rule['actions'] = array();
$rule['after'] = $after;
if ($action && $action != 'keep') {
$rule['actions'][] = array(
'type' => $action == 'discard' ? 'discard' : 'redirect',
'copy' => $action == 'copy',
'target' => $target,
));
'target' => $action != 'discard' ? $target : '',
);
}
if ($this->merge_rule($rule, $this->forward, $this->script_name)) {
if ($this->save_forward_script($rule)) {
$this->rc->output->show_message('managesieve.forwardsaved', 'confirmation');
$this->rc->output->send();
}
@ -313,6 +320,99 @@ class rcube_sieve_forward extends rcube_sieve_engine
return $out;
}
/**
* Saves forward script (adding some variables)
*/
protected function save_forward_script($rule)
{
// if script does not exist create a new one
if ($this->script_name === null || $this->script_name === false) {
$this->script_name = $this->rc->config->get('managesieve_script_name');
if (empty($this->script_name)) {
$this->script_name = 'roundcube';
}
// use default script contents
if (!$this->rc->config->get('managesieve_kolab_master')) {
$script_file = $this->rc->config->get('managesieve_default');
if ($script_file && is_readable($script_file)) {
$content = file_get_contents($script_file);
}
}
// create and load script
if ($this->sieve->save_script($this->script_name, $content)) {
$this->sieve->load($this->script_name);
}
}
$script_active = in_array($this->script_name, $this->active);
// re-order rules if needed
if (isset($rule['after']) && $rule['after'] !== '') {
// reset original forward rule
if (isset($this->forward['idx'])) {
$this->script[$this->forward['idx']] = null;
}
// add at target position
if ($rule['after'] >= count($this->script) - 1) {
$this->script[] = $rule;
}
else {
$script = array();
foreach ($this->script as $idx => $r) {
if ($r) {
$script[] = $r;
}
if ($idx == $rule['after']) {
$script[] = $rule;
}
}
$this->script = $script;
}
$this->script = array_values(array_filter($this->script));
}
// update original forward rule if it exists
else if (isset($this->forward['idx'])) {
$this->script[$this->forward['idx']] = $rule;
}
// otherwise put forward rule on top
else {
array_unshift($this->script, $rule);
}
// if the script was not active, we need to de-activate
// all rules except the forward rule, but only if it is not disabled
if (!$script_active && !$rule['disabled']) {
foreach ($this->script as $idx => $r) {
if (empty($r['actions']) || $r['actions'][0]['type'] != 'forward') {
$this->script[$idx]['disabled'] = true;
}
}
}
if (!$this->sieve->script) {
return false;
}
$this->sieve->script->content = $this->script;
// save the script
$saved = $this->save_script($this->script_name);
// activate the script
if ($saved && !$script_active && !$rule['disabled']) {
$this->activate_script($this->script_name);
}
return $saved;
}
/**
* API: get forward rule
*
@ -372,13 +472,17 @@ class rcube_sieve_forward extends rcube_sieve_engine
$rule['disabled'] = isset($data['enabled']) && !$data['enabled'];
$rule['tests'] = $forward_tests;
$rule['join'] = $date_extension ? count($forward_tests) > 1 : false;
$rule['actions'] = array(array(
'type' => 'redirect',
$rule['actions'] = array();
if ($data['action'] && $data['action'] != 'keep') {
$rule['actions'][] = array(
'type' => $data['action'] == 'discard' ? 'discard' : 'redirect',
'copy' => $data['action'] == 'copy',
'target' => $data['target'],
));
'target' => $data['action'] != 'discard' ? $data['target'] : '',
);
}
return $this->merge_rule($rule, $this->forward, $this->script_name);
return $this->save_forward_script($rule);
}
/**

@ -182,7 +182,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
$status = rcube_utils::get_input_value('vacation_status', rcube_utils::INPUT_POST);
$from = rcube_utils::get_input_value('vacation_from', rcube_utils::INPUT_POST, true);
$from = rcube_utils::get_input_value('vacation_from', rcube_utils::INPUT_POST);
$subject = rcube_utils::get_input_value('vacation_subject', rcube_utils::INPUT_POST, true);
$reason = rcube_utils::get_input_value('vacation_reason', rcube_utils::INPUT_POST, true);
$addresses = rcube_utils::get_input_value('vacation_addresses', rcube_utils::INPUT_POST, true);
@ -218,30 +218,8 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
}
if (!empty($vacation_action['from'])) {
// According to RFC5230 the :from string must specify a valid [RFC2822] mailbox-list
// we'll try to extract addresses and validate them separately
$from = rcube_mime::decode_address_list($vacation_action['from'], null, true, RCUBE_CHARSET);
foreach ((array) $from as $idx => $addr) {
if (empty($addr['mailto']) || !rcube_utils::check_email($addr['mailto'])) {
$error = $from_error = 'noemailwarning';
break;
}
else {
$from[$idx] = format_email_recipient($addr['mailto'], $addr['name']);
}
}
// Only one address is allowed (at least on cyrus imap)
if (is_array($from) && count($from) > 1) {
$error = $from_error = 'noemailwarning';
}
// Then we convert it back to RFC2822 format
if (empty($from_error) && !empty($from)) {
$vacation_action['from'] = Mail_mimePart::encodeHeader(
'From', implode(', ', $from), RCUBE_CHARSET, 'base64', '');
}
if (!empty($vacation_action['from']) && !rcube_utils::check_email($vacation_action['from'])) {
$error = 'noemailwarning';
}
if ($vacation_action['reason'] == '') {
@ -331,7 +309,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
);
}
if ($this->merge_rule($rule, $this->vacation, $this->script_name)) {
if ($this->save_vacation_script($rule)) {
$this->rc->output->show_message('managesieve.vacationsaved', 'confirmation');
$this->rc->output->send();
}
@ -371,15 +349,8 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
if ($from_addr) {
$default_identity = $this->rc->user->list_emails(true);
$this->vacation['from'] = format_email_recipient($default_identity['email'], $default_identity['name']);
}
}
else if (!empty($this->vacation['from'])) {
$from = rcube_mime::decode_address_list($this->vacation['from'], null, true, RCUBE_CHARSET);
foreach ((array) $from as $idx => $addr) {
$from[$idx] = format_email_recipient($addr['mailto'], $addr['name']);
$this->vacation['from'] = $default_identity['email'];
}
$this->vacation['from'] = implode(', ', $from);
}
// form elements
@ -408,11 +379,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
$action->add($this->plugin->gettext('vacation.copy'), 'copy');
}
if (
$this->rc->config->get('managesieve_vacation') != 2
&& !empty($this->vacation['list'])
&& in_array($this->script_name, $this->active)
) {
if ($this->rc->config->get('managesieve_vacation') != 2 && !empty($this->vacation['list'])) {
$after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after'));
$after->add('---', '');
@ -533,7 +500,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
$table->add('title', html::label('vacation_interval', $this->plugin->gettext('vacation.interval')));
$table->add(null, html::span('input-group', $interval_txt));
if (!empty($after)) {
if ($after) {
$table->add('title', html::label('vacation_after', $this->plugin->gettext('vacation.after')));
$table->add(null, $after->show($this->vacation['idx'] - 1));
}
@ -649,6 +616,99 @@ class rcube_sieve_vacation extends rcube_sieve_engine
return $interval ?: '';
}
/**
* Saves vacation script (adding some variables)
*/
protected function save_vacation_script($rule)
{
// if script does not exist create a new one
if ($this->script_name === null || $this->script_name === false) {
$this->script_name = $this->rc->config->get('managesieve_script_name');
if (empty($this->script_name)) {
$this->script_name = 'roundcube';
}
// use default script contents
if (!$this->rc->config->get('managesieve_kolab_master')) {
$script_file = $this->rc->config->get('managesieve_default');
if ($script_file && is_readable($script_file)) {
$content = file_get_contents($script_file);
}
}
// create and load script
if ($this->sieve->save_script($this->script_name, $content)) {
$this->sieve->load($this->script_name);
}
}
$script_active = in_array($this->script_name, $this->active);
// re-order rules if needed
if (isset($rule['after']) && $rule['after'] !== '') {
// reset original vacation rule
if (isset($this->vacation['idx'])) {
$this->script[$this->vacation['idx']] = null;
}
// add at target position
if ($rule['after'] >= count($this->script) - 1) {
$this->script[] = $rule;
}
else {
$script = array();
foreach ($this->script as $idx => $r) {
if ($r) {
$script[] = $r;
}
if ($idx == $rule['after']) {
$script[] = $rule;
}
}
$this->script = $script;
}
$this->script = array_values(array_filter($this->script));
}
// update original vacation rule if it exists
else if (isset($this->vacation['idx'])) {
$this->script[$this->vacation['idx']] = $rule;
}
// otherwise put vacation rule on top
else {
array_unshift($this->script, $rule);
}
// if the script was not active, we need to de-activate
// all rules except the vacation rule, but only if it is not disabled
if (!$script_active && !$rule['disabled']) {
foreach ($this->script as $idx => $r) {
if (empty($r['actions']) || $r['actions'][0]['type'] != 'vacation') {
$this->script[$idx]['disabled'] = true;
}
}
}
if (!$this->sieve->script) {
return false;
}
$this->sieve->script->content = $this->script;
// save the script
$saved = $this->save_script($this->script_name);
// activate the script
if ($saved && !$script_active && !$rule['disabled']) {
$this->activate_script($this->script_name);
}
return $saved;
}
/**
* API: get vacation rule
*
@ -848,7 +908,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
);
}
return $this->merge_rule($rule, $this->vacation, $this->script_name);
return $this->save_vacation_script($rule);
}
/**

@ -814,7 +814,7 @@ function smart_field_init(field)
// add input rows
$.each(list, function(i, v) {
area.append(smart_field_row(v, i, field));
area.append(smart_field_row(v, field.name, i, $(field).data('size')));
});
area.attr('id', id);
@ -837,26 +837,25 @@ function smart_field_init(field)
}
};
function smart_field_row(value, idx, field)
function smart_field_row(value, name, idx, size)
{
// build row element content
var input, content = '<span class="listelement">'
+ '<span class="reset"></span><input type="text"></span>',
elem = $(content),
attrs = {
value: value,
name: field.name + '[]',
size: $(field).data('size'),
title: field.title,
placeholder: $(field).attr('placeholder')
};
attrs = {value: value, name: name + '[]'};
if (size)
attrs.size = size;
input = elem.find('input').attr(attrs).keydown(function(e) {
input = $('input', elem).attr(attrs).keydown(function(e) {
var input = $(this);
// element creation event (on Enter)
if (e.which == 13) {
var elem = smart_field_row('', (new Date()).getTime(), field);
var name = input.attr('name').replace(/\[\]$/, ''),
dt = (new Date()).getTime(),
elem = smart_field_row('', name, dt, size);
input.parent().after(elem);
$('input', elem).focus();
@ -905,7 +904,7 @@ function smart_field_reset(field, data)
// add input rows
$.each(list, function(i, v) {
area.append(smart_field_row(v, i, field));
area.append(smart_field_row(v, field.name, i, $(field).data('size')));
});
}

@ -1,6 +1,6 @@
<?php
class Managesieve_Plugin extends PHPUnit\Framework\TestCase
class Managesieve_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Parser extends PHPUnit\Framework\TestCase
class Parser extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Tokenizer extends PHPUnit\Framework\TestCase
class Tokenizer extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class Managesieve_Vacation extends PHPUnit\Framework\TestCase
class Managesieve_Vacation extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,134 +0,0 @@
<?php
namespace Tests\Browser\Plugins\Markasjunk;
use Tests\Browser\Components\Popupmenu;
class MailTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_db();
\bootstrap::init_imap();
\bootstrap::purge_mailbox('INBOX');
\bootstrap::purge_mailbox('Junk');
// import single email message
\bootstrap::import_message(TESTS_DIR . 'data/mail/list_00.eml', 'INBOX');
}
/**
* Test plugin functionality in Mail UI
*/
public function testMailUI()
{
$this->browse(function ($browser) {
$browser->go('mail');
// Toolbar menu (Spam button inactive)
$browser->assertToolbarMenu([], ['junk']);
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->ctrlClick('tr:last-child');
});
$browser->clickToolbarMenuItem('junk')
->waitForMessage('confirmation', 'Successfully reported as junk')
->closeMessage('confirmation')
->assertElementsCount('#messagelist tbody tr', 0) // empty list
->waitForMessage('confirmation', 'Message(s) moved successfully.')
->closeMessage('confirmation');
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button');
}
// Folders list
$browser->whenAvailable('#mailboxlist', function ($browser) {
$browser->assertSeeIn('li.mailbox.junk .unreadcount', '1')
->assertMissing('li.mailbox.inbox .unreadcount')
->click('li.mailbox.junk')
->waitUntilNotBusy();
});
// Toolbar menu (Non-Junk button inactive)
$browser->assertToolbarMenu([], ['notjunk']);
// Messages list contains the moved message
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->assertElementsCount('tr', 1)
->ctrlClick('tr:last-child');
});
$browser->clickToolbarMenuItem('notjunk')
->waitForMessage('confirmation', 'Successfully reported as not junk')
->closeMessage('confirmation')
->assertElementsCount('#messagelist tbody tr', 0) // empty list
->waitForMessage('confirmation', 'Message(s) moved successfully.')
->closeMessage('confirmation');
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button');
}
// Folders list, the message is back in INBOX
$browser->whenAvailable('#mailboxlist', function ($browser) {
$browser->assertMissing('li.mailbox.junk .unreadcount')
->assertSeeIn('li.mailbox.inbox .unreadcount', '1')
->click('li.mailbox.inbox')
->waitUntilNotBusy();
});
// Messages list contains the moved message
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->assertElementsCount('tr', 1);
});
});
}
/**
* Test plugin functionality on email preview page
*
* @depends testMailUI
*/
public function testMailView()
{
$this->browse(function ($browser) {
$browser->go('mail');
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->mouseover('tr:last-child')->doubleClick();
});
$browser->waitFor('#message-content');
// Toolbar menu (Junk button active), click it
$browser->clickToolbarMenuItem('junk')
->waitFor('#messagelist')
->waitUntilNotBusy()
->assertElementsCount('#messagelist tbody tr', 0); // empty list
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button');
}
// Folders list
$browser->whenAvailable('#mailboxlist', function ($browser) {
$browser->click('li.mailbox.junk')
->waitUntilNotBusy();
});
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->mouseover('tr:last-child')->doubleClick();
});
$browser->waitFor('#message-content');
// Toolbar menu (Junk button active), click it
$browser->clickToolbarMenuItem('notjunk')
->waitFor('#messagelist')
->waitUntilNotBusy()
->assertElementsCount('#messagelist tbody tr', 0); // empty list
});
}
}

@ -1,6 +1,6 @@
<?php
class Markasjunk_Plugin extends PHPUnit\Framework\TestCase
class Markasjunk_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class NewUserDialog_Plugin extends PHPUnit\Framework\TestCase
class NewUserDialog_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class NewUserIdentity_Plugin extends PHPUnit\Framework\TestCase
class NewUserIdentity_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class NewmailNotifier_Plugin extends PHPUnit\Framework\TestCase
class NewmailNotifier_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -51,7 +51,6 @@
2.1.23. LDAP - Password Modify Extended Operation (ldap_exop)
2.2. Password Strength Drivers
2.2.1. Zxcvbn
2.2.2. Have I been pwned? (pwned)
3. Driver API
4. Sudo setup
@ -403,21 +402,6 @@
Set $config['password_zxcvbn_min_score'] to define minimum acceptable password strength score.
2.2.2. Have I been pwned? (pwned)
---------------------------------
Driver using "Have I been pwned?" (https://haveibeenpwned.com/Passwords) API to
check that entered passwords aren't already compromised (i.e., commonly known).
The check is performed locally, the actual password is *not* transmitted anywhere else.
Requires curl (preferred) or allow_url_fopen to work.
Example configuration:
$config['password_strength_driver'] = 'pwned';
$config['password_minimum_score'] = 3;
See the driver implementation file for more documentation.
3. Driver API
-------------

@ -3,7 +3,7 @@
"type": "roundcube-plugin",
"description": "Password Change for Roundcube. Plugin adds a possibility to change user password using many methods (drivers) via Settings/Password tab.",
"license": "GPLv3+",
"version": "5.2",
"version": "5.1",
"authors": [
{
"name": "Aleksander Machniak",

@ -1,223 +0,0 @@
<?php
/**
* Have I Been Pwned Password Strength Driver
*
* Driver to check passwords using HIBP:
* https://haveibeenpwned.com/Passwords
*
* This driver will return a strength of:
* 3: if the password WAS NOT found in HIBP
* 1: if the password WAS found in HIBP
* 2: if there was an ERROR retrieving data.
*
* To use this driver, configure (in ../config.inc.php):
*
* $config['password_strength_driver'] = 'pwned';
* $config['password_minimum_score'] = 3;
*
* Set the minimum score to 3 if you want to make sure that all
* passwords are successfully checked against HIBP (recommended).
*
* Set it to 2 if you still want to accept passwords in case a
* HIBP check fails for some (technical) reason.
*
* Setting the minimum score to 1 or less effectively renders
* the checks useless, as all passwords would be accepted.
* Setting it to 4 or more will effectively reject all passwords.
*
* This driver will only return a maximum score of 3 because not
* being listed in HIBP does not necessarily mean that the
* password is a good one. It is therefore recommended to also
* configure a minimum length for the password.
*
* Background reading (don't worry, your passwords are not sent anywhere):
* https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/#cloudflareprivacyandkanonymity
*
* @version 1.0
* @author Christoph Langguth
*
* Copyright (C) The Roundcube Dev Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
class rcube_pwned_password
{
// API URL. Note: the trailing slash is mandatory.
const API_URL = 'https://api.pwnedpasswords.com/range/';
// See https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
const ENHANCED_PRIVACY_CURL = 1;
// Score constants, these directly correspond to the score that is returned.
const SCORE_LISTED = 1;
const SCORE_ERROR = 2;
const SCORE_NOT_LISTED = 3;
/**
* Rule description.
*
* @return array human-readable description of the check rule.
*/
function strength_rules()
{
$rc = rcmail::get_instance();
return array($rc->gettext('password.pwned_mustnotbedisclosed'));
}
/**
* Password strength check.
* Return values:
* 1 - if password is definitely compromised.
* 2 - if status for password can't be determined (network failures etc.)
* 3 - if password is not publicly known to be compromised.
*
* @param string $passwd Password
*
* @return array password score (1 to 3) and (optional) reason message
*/
function check_strength($passwd)
{
$score = $this->check_pwned($passwd);
$message = null;
if ($score !== self::SCORE_NOT_LISTED) {
$rc = rcmail::get_instance();
if ($score === self::SCORE_LISTED) {
$message = $rc->gettext('password.pwned_isdisclosed');
}
else {
$message = $rc->gettext('password.pwned_fetcherror');
}
}
return array($score, $message);
}
/**
* Check password using HIBP.
*
* @param string $passwd
*
* @return int score, one of the SCORE_* constants (between 1 and 3).
*/
function check_pwned($passwd)
{
// initialize with error score
$result = self::SCORE_ERROR;
if (!$this->can_retrieve()) {
// Log the fact that we cannot check because of configuration error.
rcube::raise_error("Need curl or allow_url_fopen to check password strength with 'pwned'", true, true);
}
else {
list($prefix, $suffix) = $this->hash_split($passwd);
$suffixes = $this->retrieve_suffixes(self::API_URL . $prefix);
if ($suffixes) {
$result = $this->check_suffix_in_list($suffix, $suffixes);
}
}
return $result;
}
function hash_split($passwd)
{
$hash = strtolower(sha1($passwd));
$prefix = substr($hash, 0, 5);
$suffix = substr($hash, 5);
return array($prefix, $suffix);
}
function can_retrieve()
{
return $this->can_curl() || $this->can_fopen();
}
function can_curl()
{
return function_exists('curl_init');
}
function can_fopen()
{
return ini_get('allow_url_fopen');
}
function retrieve_suffixes($url)
{
if ($this->can_curl()) {
return $this->retrieve_curl($url);
}
else {
return $this->retrieve_fopen($url);
}
}
function retrieve_curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if (self::ENHANCED_PRIVACY_CURL == 1) {
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Add-Padding: true'));
}
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
function retrieve_fopen($url)
{
$output = '';
$ch = fopen($url, 'r');
while (!feof($ch)) {
$output .= fgets($ch);
}
fclose($ch);
return $output;
}
function check_suffix_in_list($candidate, $list)
{
// initialize to error in case there are no lines at all
$result = self::SCORE_ERROR;
foreach (preg_split('/[\r\n]+/', $list) as $line) {
$line = strtolower($line);
if (preg_match('/^([0-9a-f]{35}):(\d+)$/', $line, $matches)) {
if ($matches[2] > 0 && $matches[1] === $candidate) {
// more than 0 occurrences, and suffix matches
// -> password is compromised
return self::SCORE_LISTED;
}
// valid line, not matching the current password
$result = self::SCORE_NOT_LISTED;
}
else {
// invalid line
return self::SCORE_ERROR;
}
}
return $result;
}
}

@ -38,5 +38,3 @@ $messages['samepasswd'] = 'Das neue Passwort muss sich von dem Alten unterscheid
$messages['passwdexpirewarning'] = 'Achtung! Ihr Passwort läuft am $expirationdatetime ab. Ändern Sie es rechtzeitig.';
$messages['passwdexpired'] = 'Ihr Passwort ist abgelaufen, ändern Sie es jetzt!';
$messages['passwdconstraintviolation'] = 'Passwortbeschränkungsverletzung. Passwort wahrscheinlich zu schwach.';
$messages['pwned_mustnotbedisclosed'] = 'Passwort darf nicht&nbsp;<a href="https://haveibeenpwned.com/Passwords" target="_blank">allgemein bekannt</a>&nbsp;sein.';
$messages['pwned_isdisclosed'] = 'Dieses Passwort ist bereits allgemein bekannt.';

@ -41,6 +41,3 @@ $messages['samepasswd'] = 'New password have to be different from the old one.';
$messages['passwdexpirewarning'] = 'Warning! Your password will expire soon, change it before $expirationdatetime.';
$messages['passwdexpired'] = 'Your password has expired, you have to change it now!';
$messages['passwdconstraintviolation'] = 'Password constraint violation. Password probably too weak.';
$messages['pwned_mustnotbedisclosed'] = 'Password must not be&nbsp;<a href="https://haveibeenpwned.com/Passwords" target="_blank">commonly known</a>.';
$messages['pwned_isdisclosed'] = 'This password is commonly known.';
$messages['pwned_fetcherror'] = 'Failed to verify the password strength.';

@ -38,5 +38,3 @@ $messages['samepasswd'] = 'Le nouveau mot de passe doit être différent de l
$messages['passwdexpirewarning'] = 'Avertissement! Votre mot de passe arrivera prochainement à expiration. Changez-le avant le $expirationdatetime.';
$messages['passwdexpired'] = 'Votre mot de passe est expiré, vous devez le changer maintenant';
$messages['passwdconstraintviolation'] = 'Contrainte non respectée. Le mot de passe est probablement trop faible.';
$messages['pwned_mustnotbedisclosed'] = 'Le mot de passe ne doit pas être&nbsp;<a href="https://haveibeenpwned.com/Passwords" target="_blank">communément connu</a>.';
$messages['pwned_isdisclosed'] = 'Ce mot de passe est communément connu.';

@ -1,6 +1,6 @@
<?php
class Password_Plugin extends PHPUnit\Framework\TestCase
class Password_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -0,0 +1 @@
Subproject commit d56529721ad8a4bbfb81972c70ee697131fd89b4

@ -1,6 +1,6 @@
<?php
class RedundantAttachments_Plugin extends PHPUnit\Framework\TestCase
class RedundantAttachments_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class ShowAdditionalHeaders_Plugin extends PHPUnit\Framework\TestCase
class ShowAdditionalHeaders_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class SquirrelmailUsercopy_Plugin extends PHPUnit\Framework\TestCase
class SquirrelmailUsercopy_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class SubscriptionsOption_Plugin extends PHPUnit\Framework\TestCase
class SubscriptionsOption_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -0,0 +1 @@
Subproject commit 884c2bd51b747465a8b1d340cb4186082da1f786

@ -1,6 +1,6 @@
<?php
class Userinfo_Plugin extends PHPUnit\Framework\TestCase
class Userinfo_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class VcardAttachments_Plugin extends PHPUnit\Framework\TestCase
class VcardAttachments_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class VirtuserFile_Plugin extends PHPUnit\Framework\TestCase
class VirtuserFile_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,6 +1,6 @@
<?php
class VirtuserQuery_Plugin extends PHPUnit\Framework\TestCase
class VirtuserQuery_Plugin extends PHPUnit_Framework_TestCase
{
function setUp()

@ -1,125 +0,0 @@
<?php
namespace Tests\Browser\Plugins\Zipdownload;
use Tests\Browser\Components\Popupmenu;
class MailTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_imap();
\bootstrap::purge_mailbox('INBOX');
// import single email messages
foreach (glob(TESTS_DIR . 'data/mail/list_0?.eml') as $f) {
\bootstrap::import_message($f, 'INBOX');
}
}
public function testMailUI()
{
$this->browse(function ($browser) {
$browser->go('mail');
$browser->whenAvailable('#messagelist tbody', function ($browser) {
$browser->ctrlClick('tr:first-child');
});
// Test More > Download > Source (single message selected)
$browser->clickToolbarMenuItem('more')
->with(new Popupmenu('message-menu'), function ($browser) {
$browser->clickMenuItem('download');
})
->with(new Popupmenu('zipdownload-menu'), function ($browser) {
$browser->assertVisible('a.download.eml:not(.disabled)')
->assertSeeIn('a.download.eml', 'Source (.eml)')
->assertVisible('a.download.mbox.disabled')
->assertSeeIn('a.download.mbox', 'Mbox format (.zip)')
->assertVisible('a.download.maildir.disabled')
->assertSeeIn('a.download.maildir', 'Maildir format (.zip)')
->click('a.download.eml');
$filename = 'Test HTML with local and remote image.eml';
$email = $browser->readDownloadedFile($filename);
$browser->removeDownloadedFile($filename);
$this->assertTrue(strpos($email, 'Subject: Test HTML with local and remote image') !== false);
});
// Test More > Download > Mailbox format (two messages selected)
$browser->ctrlClick('#messagelist tbody tr:nth-of-type(2)')
->clickToolbarMenuItem('more')
->with(new Popupmenu('message-menu'), function ($browser) {
$browser->clickMenuItem('download');
})
->with(new Popupmenu('zipdownload-menu'), function ($browser) {
$browser->assertVisible('a.download.eml.disabled')
->assertVisible('a.download.mbox:not(.disabled)')
->assertVisible('a.download.maildir:not(.disabled)')
->click('a.download.mbox');
$filename = 'INBOX.zip';
$files = $this->getFilesFromZip($filename);
$browser->removeDownloadedFile($filename);
$this->assertSame(['INBOX.mbox'], $files);
});
// Test More > Download > Maildir format (two messages selected)
$browser->clickToolbarMenuItem('more')
->with(new Popupmenu('message-menu'), function ($browser) {
$browser->clickMenuItem('download');
})
->with(new Popupmenu('zipdownload-menu'), function ($browser) {
$browser->click('a.download.maildir');
$filename = 'INBOX.zip';
$files = $this->getFilesFromZip($filename);
$browser->removeDownloadedFile($filename);
$this->assertCount(2, $files);
});
// Test attachments download
$browser->click('#messagelist tbody tr:nth-of-type(2)')
->waitForMessage('loading', 'Loading...')
->waitFor('#messagecontframe')
->waitUntilMissing('#messagestack')
->withinFrame('#messagecontframe', function ($browser) {
$browser->waitFor('.header-links a.zipdownload')
->click('.header-links a.zipdownload');
});
$filename = 'Lines.zip';
$files = $this->getFilesFromZip($filename);
$browser->removeDownloadedFile($filename);
$expected = ['lines.txt', 'lines_lf.txt'];
$this->assertSame($expected, $files);
});
}
/**
* Helper to extract files list from downloaded zip file
*/
private function getFilesFromZip($filename)
{
$filename = TESTS_DIR . "downloads/$filename";
// Give the browser a chance to finish download
if (!file_exists($filename)) {
sleep(2);
}
$zip = new \ZipArchive;
$files = [];
if ($zip->open($filename)) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$files[] = $zip->getNameIndex($i);
}
}
$zip->close();
return $files;
}
}

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

Loading…
Cancel
Save