Compare commits

..

386 Commits

Author SHA1 Message Date
johndoh 279ae66120
Move all message list entry generation to client side (#7287)
Always return cols required for widescreen message list and control layout only on JS side
6 years ago
johndoh 13a066b24f
Elastic: small improvement to splitter code (#7435) 6 years ago
William Rankin 40250563e3
Specify 13px font-size for .html-editor > textarea (#7447)
This is a followup to PR #7375 which set the plaintext message area from 14px to 13px for greater consistency with Classic and Larry skins. With that PR I neglected to consider the font size in the compose screen, which should probably match the 13px of the message area, but currently inherits 14px from the html element which causes an odd jump in font size when replying to a message.

Reference #7375
6 years ago
Aleksander Machniak b22f1c9a1b Add skip-empty option to get_edit_field() (#7444) 6 years ago
Aleksander Machniak ec2e713ecb Fix problem with forwarding inline images attached to messages with no HTML part (#7414) 6 years ago
Gianluca Giacometti 4ada2c3f13
Use more strict selector (#7452) 6 years ago
Aleksander Machniak 98881a0cbd Small code simplification 6 years ago
Aleksander Machniak 1c76c8440f Elastic: Remove unused color variables 6 years ago
Aleksander Machniak 731be15d46 Elastic: Fix context menu (paste) on the recipient input (#7431) 6 years ago
Aleksander Machniak 9ee1f4b636 Merge branch 'master' of github.com:roundcube/roundcubemail 6 years ago
Aleksander Machniak 51eb8a6a68 Update changelog
[skip ci]
6 years ago
Aleksander Machniak 9508acf249 Update changelog
[skip ci]
6 years ago
Aleksander Machniak 30610e867e Merge branch 'fix_encode' of https://github.com/shirosaki/roundcubemail into shirosaki-fix_encode
Refactor the new code
6 years ago
johndoh 9dbe666d4c
Allow skins to define which layout options they support (#7235) 6 years ago
Aleksander Machniak 86d34960c8 Optimize image size 6 years ago
Théo 61185b9362
Update roundcube_logo.png (#7357) 6 years ago
William Rankin 13b03c3953
Specify 13px font-size for .message-part div.pre (#7375)
Default monospace fonts are larger than proportional fonts and so make the plain text message look oddly large, upsetting the aesthetics of the page.
6 years ago
Kent Varmedal b4dabff26c
Add newline when writing logs to stdout (#7418)
Add newline on the end of the line when printing to stdout.
6 years ago
Aleksander Machniak 278cf3c18e Increase maximum size of contact jobtitle and department fields to 128 characters
[skip ci]
6 years ago
Aleksander Machniak e9c592a6e8 Fix bug where subfolders of special folders could have been duplicated on folder list 6 years ago
Aleksander Machniak bb06645b8f Update changelog
[skip ci]
6 years ago
Aleksander Machniak 9b8ed0dfeb Update changelog
[skip ci]
6 years ago
Aleksander Machniak 218cd2ab4a Elastic: Support space-separated email addresses in recipient input (#6529) 6 years ago
Aleksander Machniak 4e00237cc4 Allow opening application/octet-stream attachments according to filename extension (#6821) 6 years ago
Aleksander Machniak eaebae1e54 Fix bug where activation of forward/vacation rule could activate a wrong script (#7423)
Also should fix bug where forward/vacation rule could end up being duplicated (#7349)
6 years ago
Aleksander Machniak 77174ff9ff Fix test after change for #7413 6 years ago
Aleksander Machniak 25772702ac Fix so anchor tags without href attribute are not modified (#7413) 6 years ago
Aleksander Machniak af0e98759d Installer: Fix regression in SMTP test section (#7417) 6 years ago
Aleksander Machniak cd76046237 Update changelog
[skip ci]
6 years ago
Aleksander Machniak 46d3cae2ff Security: Fix cross-site scripting (XSS) via malicious XML attachment 6 years ago
Aleksander Machniak bda02002de Security: Better fix for CVE-2020-12641 6 years ago
Aleksander Machniak ecabb1e667 Security: Fix XSS issue in template object 'username' (#7406) 6 years ago
Aleksander Machniak 31249d932d Security: Fix couple of XSS issues in Installer (#7406) 6 years ago
Aleksander Machniak e05c69afab Fix bug where PDF attachments marked as inline could have not been attached on mail forward (#7382) 6 years ago
Aleksander Machniak 6436c22d2d Elastic: Fix aspect ratio of a contact photo in mail preview (#7339) 6 years ago
Aleksander Machniak da2bb8af6d Fix error when user-configured skin does not exist anymore (#7271)
We fallback to the system skin not the default one.
6 years ago
Aleksander Machniak f6586c7cf7 Fix PHP warning: count(): Parameter must be an array or an object... in ID command handler (#7392) 6 years ago
Aleksander Machniak 82ee69df15 Update changelog
[ci skip]
6 years ago
johndoh 34a0af8964
Allow array in smtp_host config (#7296) 6 years ago
Aleksander Machniak 30d31c323b Improve UX on custom header input (#7207) 6 years ago
Aleksander Machniak a47c558df4 Fix typo 6 years ago
Aleksander Machniak 14a8a99ee2 Update changelog
[ci skip]
6 years ago
Christopher Gurnee 40ecd47f90 Show Encrypt button w/Mailvelope, even if disabled
Before the Elastic skin would keep it hidden until enabled, closes #7353
6 years ago
Aleksander Machniak 06a1964ef6 Fix changelog
[ci skip]
6 years ago
Christopher Gurnee 24d0cf5d34 Let Mailvelope use sender's address to find pubkeys to check signatures 6 years ago
Christopher Gurnee c3d4598985 Add missing \'s to regexes in rcube_check_email() 6 years ago
Aleksander Machniak 7dcba95605 Update changelog
[ci skip]
6 years ago
vaaguirre 9bec8dd506
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.
6 years ago
Aleksander Machniak 663f7a8c58 Fix default keyservers (use keys.openpgp.org), add note about CORS (#7373, #7367) 6 years ago
johndoh 3af6303ad7
Remove depreciated jquery.trim function (#7371) 6 years ago
Aleksander Machniak 35c29be9b2 Remove use of ext-iconv 6 years ago
Aleksander Machniak 87f42677c2 Don't resize alert and confirm dialogs 6 years ago
Aleksander Machniak 7ead0bb227 Fix empty attachment size handling (#7370) 6 years ago
Aleksander Machniak 014659b600 CS fixes, mostly around undefined variables 6 years ago
Aleksander Machniak f0f6234a1a Use merge instead of append (#7341) 6 years ago
Aleksander Machniak 87ca7a09dc Fix missing flag indication on collapsed thread in Larry and Elastic (#7366) 6 years ago
Aleksander Machniak 377239fa8e Display a warning and do not try to open empty attachments (#7332) 6 years ago
Aleksander Machniak a2ee9f7eab Merge branch 'master' of github.com:roundcube/roundcubemail 6 years ago
Aleksander Machniak 1aebb1985c Fix recipient rename/delete after drag'n'drop, also keep the order 6 years ago
Aleksander Machniak 09848515fe Merge branch 'master' of https://github.com/chilek/roundcubemail into chilek-master 6 years ago
Sander Nilsen 0efa899d6c
Grammar correction (#7346) 6 years ago
Aleksander Machniak 55343ffd7d Update changelog
[ci skip]
6 years ago
johndoh ec86e3cb7f
Markasjunk: Fix regression in jsevent driver #7361 (#7365) 6 years ago
Aleksander Machniak 8fac36e2c2 Fix so the database setup description is compatible with MySQL 8 (#7340)
[ci skip]
6 years ago
Aleksander Machniak 3d2bb47f8d Clarify des_key length requirement (#7350)
[ci skip]
6 years ago
Aleksander Machniak c39081b6a1 Fix bug in extracting required plugins from composer.json that led to spurious error in log (#7364) 6 years ago
Aleksander Machniak 12d4705935 Update changelog
[ci skip]
6 years ago
Tomasz Chiliński 0b09b24a55
enhancement: elastic skin: allow to drag & drop addresses between address input fields during compose message (code simplification) 6 years ago
Aleksander Machniak 8344f07d7f Fix CSRF bypass that could be used to log out an authenticated user (#7302) 6 years ago
Aleksander Machniak 219e353ac1 Fix local file inclusion (and code execution) via crafted 'plugins' option 6 years ago
Aleksander Machniak 4951d6603a Fix remote code execution via crafted 'im_convert_path' or 'im_identify_path' settings 6 years ago
Aleksander Machniak 87e4cd0cf2 Fix XSS issue in handling of CDATA in HTML messages 6 years ago
Tomasz Chiliński 8db9b8b6e2
enhancement: elastic skin: allow to drag & drop addresses between address input fields during compose message (code simplification) 6 years ago
Tomasz Chiliński 682cea5cc5
enhancement: elastic skin: allow to drag & drop addresses between address input fields during compose message (code simplification) 6 years ago
Tomasz Chiliński aae1189dbf
enhancement: elastic skin: allow to drag & drop addresses between address input fields during compose message (update source list address input field to) 6 years ago
Tomasz Chiliński 6f8663f7ef
enhancement: elastic skin: allow to drag & drop addresses between address input fields during compose message 6 years ago
Aleksander Machniak 6b5fc8db95 Fix so Print button for PDF attachments works on Firefox >= 75 (#5125) 6 years ago
Aleksander Machniak b35b5a1a26 Fix typo 6 years ago
Aleksander Machniak bf34e8cf9c Fix performance issue of parsing big HTML messages by disabling HTML5 parser for these (#7331) 6 years ago
Aleksander Machniak 36532345df Make install-jsdeps.sh script working without the 'file' program installed (#7325) 6 years ago
Aleksander Machniak 30b5803ee4 Fix bug where some message/rfc822 parts could not be attached on forward (#7323) 6 years ago
Aleksander Machniak cb9b22dcc1 Fix characters encoding in group rename input after group creation/rename (#7330) 6 years ago
Aleksander Machniak 3e9aefceef Mailvelope: Fix bug where recipients with name were not handled properly in mail compose (#7312) 6 years ago
Aleksander Machniak 15ee34a438 Revert "Fix bug where session was destoryed with window close (#7251)"
This reverts commit 32fac136db.
6 years ago
Aleksander Machniak 4a5efe09f9 Fix bug where a special folder couldn't be created if a special-use flag is not supported (#7147) 6 years ago
Aleksander Machniak 43456db922 Fix so imap error message is displayed to the user on folder create/update (#7245) 6 years ago
Aleksander Machniak 32fac136db Fix bug where session was destoryed with window close (#7251) 6 years ago
Aleksander Machniak 9ddb57f5bb Elastic: Restrict logo size in print view (#7275) 6 years ago
Aleksander Machniak 679cb1833c Merge branch 'zipdownload' of https://github.com/johndoh/roundcubemail into johndoh-zipdownload 6 years ago
Aleksander Machniak ae687d7a7a Update changelog 6 years ago
Aleksander Machniak cb8c07843b Fix marking as spam/ham on moving messages with Move menu (#7189) 6 years ago
PhilW ace991946f zipdownload: enable menu options when menu is opened 6 years ago
Aleksander Machniak e0b56aeb4d Fix PHP warning for real (#7206) 6 years ago
Aleksander Machniak 7d97c5b215 Merge branch 'markasjunk-select' of https://github.com/johndoh/roundcubemail into johndoh-markasjunk-select 6 years ago
johndoh 77e2ee9c9e
Add some public methods to elastic ui.js (#7240) 6 years ago
Aleksander Machniak c2b12d2b2b Elastic: Fix color of a folder with recent messages (#7281) 6 years ago
Aleksander Machniak e84dc4c385 Fix literals handling again 6 years ago
Aleksander Machniak b771cccbaa Update changelog
[skip ci]
6 years ago
dessert1 e66ffae856
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.
6 years ago
Aleksander Machniak 3d4a02a3a6 Fix bug where multiple images in a message were replaced by the first one on forward/reply/edit (#7293) 6 years ago
Aleksander Machniak 2965e60c1f Support many string literals in a "line response", deduplicate code 6 years ago
Aleksander Machniak f9c84e2646 Fix string literals handling in IMAP STATUS (and various other) responses (#7290) 6 years ago
Aleksander Machniak 7df358d64e 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.
6 years ago
Aleksander Machniak f0ec65ee70 Fix labels again 6 years ago
Christoph Langguth 81839093bb
Bugfix in password/pwned driver result parsing (#7288) 6 years ago
Aleksander Machniak 005da225f6 Merge branch 'master' of github.com:roundcube/roundcubemail 6 years ago
Aleksander Machniak c26ba1ee5d Improve some labels 6 years ago
johndoh 81dcf4a7de
Allow possibility to define the container of a table with a fixed header (#7239) 6 years ago
Aleksander Machniak 84505edac5 Update changelog
[ci skip]
6 years ago
johndoh 11aa9633b1
Improvements to options menu link template object (#7237) 6 years ago
Aleksander Machniak 623a642457 Update changelog, fix some localization labels 6 years ago
Aleksander Machniak 68350bd385 Merge branch 'master' of https://github.com/mildred/roundcubemail into mildred-master 6 years ago
Aleksander Machniak 3c8b270d8d Small code improvements, update changelog 6 years ago
Aleksander Machniak be7770b5a9 Merge branch 'password_pwned' of https://github.com/clangguth/roundcubemail into clangguth-password_pwned 6 years ago
Christoph Langguth a70baca3ae Refactoring of pwned passwords plugin, taking into account alecpl's comment. 6 years ago
johndoh 97732d5de7
Add selection element to theads as well as tbodies (#7238) 6 years ago
Aleksander Machniak dc1ce546b9 Fix sql syntax error introduced in last commit 6 years ago
Aleksander Machniak 2c1a22d283 Bring back BINARY for some columns after switch to utf8mb4 charset (#7277) 6 years ago
Aleksander Machniak 0fbd184294 Fix cursor position after inserting a group to a recipient input using autocompletion (#7267)
... for larry and classic skins.
6 years ago
Aleksander Machniak b3b5b9665d Fix regression in testing database schema on MSSQL (#7227) 6 years ago
Aleksander Machniak a748214595 Fix so button label in Select image/media dialogs is "Close" not "Cancel" (#7246) 6 years ago
Aleksander Machniak e5247e855e Fix missing contact display name in QR Code data (#7257) 6 years ago
Aleksander Machniak d194b238c7 Support RFC8438: IMAP STATUS=SIZE - for faster folder size calculation (#7269) 6 years ago
Christoph Langguth 370789c8c9 password plugin: add pwned passwords strength driver 6 years ago
Mildred Ki'Lya 88577119d7 Add new archival types to keep the folder structure
Add archival type folderyear and foldermonth to keep the original folder
structure after a YYYY or a YYYY/MM prefix.
6 years ago
Aleksander Machniak 088714619e Fix scroll-jump in MS Edge when using autoresizeable textarea (#7230) 6 years ago
Aleksander Machniak f452d33d44 Update changelog for #7261 6 years ago
Aleksander Machniak b2391417b3 Tests: Add browser test for #7231 6 years ago
Aleksander Machniak 68c8ee0a2b Elastic: Fix recipient input bug when using click to select a contact from autocomplete list (#7231) 6 years ago
Aleksander Machniak b7410ffe89 Use mySQL charset in connection string instead of SET NAMES (#7232)
And switch to utf8mb4 as it is what we're using right now in db schema.
6 years ago
Aleksander Machniak 80a244756d Tests: Add couple of HTML editor tests (including bug #7230) 6 years ago
Aleksander Machniak 38d6659384 Elastic: Fix text selection with Shift+PageUp and Shift+PageDown in plain text editor when using Chrome (#7230) 6 years ago
Aleksander Machniak e58c6547ca Add release mark
[skip ci]
6 years ago
Aleksander Machniak 770d818dd9 Fix undefined variable 6 years ago
Aleksander Machniak 4b219792a7 Tests: Add browser tests for attachment_reminder plugin 6 years ago
Aleksander Machniak f94b871aa6 Tests: Add browser tests for Markasjunk plugin 6 years ago
Aleksander Machniak db5ab6d3f6 Fix identity selection on reply when both sender and recipient addresses are included in identities (#7211) 6 years ago
PhilW 65a6a511a6 markasjunk: handle select all case (#7206) 6 years ago
Aleksander Machniak f2328bbe6b Add note about innodb_large_prefix=true requirement (#7212)
[skip ci]
6 years ago
Aleksander Machniak b80171f37b 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.
6 years ago
Aleksander Machniak 4e2e876101 Update changelog 6 years ago
Paul J. Dorn 305c9355ee
Fix using unix:///path/to/socket.file in memcached driver (#7210)
off by one when stripping the memcache (sans d) compatible AF_UNIX prefix
6 years ago
Aleksander Machniak 32b37fba33 Update localization 6 years ago
Aleksander Machniak ea2d0ad458 Enigma: Fix bug where "Send unencrypted" button didn't work in Elastic skin (#7205) 6 years ago
Aleksander Machniak b606d81cdf MySQL: Use utf8mb4 charset and utf8mb4_unicode_ci collation (#6535, #7113)
Fixes support for utf8 emoji in html signature.
6 years ago
MrSorcus 706912e534
Fix typo (#7203) 6 years ago
Aleksander Machniak 50d6ea30e0 Support for language codes up to 16 chars long (e.g. es-419) in database schema (#6851) 6 years ago
Aleksander Machniak 60c8dd2e23 Fix regression where using an absolute path to SQLite database file on Windows didn't work (#7196) 6 years ago
Aleksander Machniak 0b02e87afe Fix some strict mode warnings 6 years ago
Aleksander Machniak fe796d4040 Remove useless pass-by-reference 6 years ago
Aleksander Machniak 3478b002f8 Enigma: Fix incorrect encrypted mail structure (boundary) with Mail_Mime >= 1.10.5 6 years ago
Aleksander Machniak 4114dc932d Fix bug where it wasn't possible to save flag actions (#7188)
Display proper error when no flag is selected.
6 years ago
Aleksander Machniak a075616fb2 Tests: Add browser tests for Zipdownload plugin 6 years ago
Aleksander Machniak d594ce2483 Tests: Add browser tests for Server Settings section 6 years ago
Aleksander Machniak d340f18295 Fix display issues with mail subject that contains line-breaks (#7191) 6 years ago
Aleksander Machniak 8080eb2c71 Tests: Fix database locking bug 6 years ago
Aleksander Machniak 84aa5d93f1 Tests: Added browser tests for Archive plugin 6 years ago
Aleksander Machniak c6392f2168 Markasjunk: Fix bug where marking as spam/ham didn't work on moving messages with drag-and-drop (#7137) 6 years ago
Aleksander Machniak 6c1e8e1efe Tests: Add Identities tests 6 years ago
Aleksander Machniak cbcebad9c1 Tests: Add Responses edit test 6 years ago
Eli Schiff b6db3e4ad7 Fixed incorrect typeof comparison (#7186) 6 years ago
Aleksander Machniak bdf0a6539e Relaxed domain name validation for extended TLDs support (#5588) 6 years ago
Aleksander Machniak 8793baa006 Update changelog
[skip ci]
6 years ago
Aleksander Machniak 97e6065897
Extract RFC2231 attachment name from message headers (#6729) (#6783)
* Extract RFC2231 attachment name from message headers (#6729)
* Workaround for attachments with invalid content type (e.g. PDF) (#6816)
6 years ago
Aleksander Machniak 1613f3ab4c
INSERT OR REPLACE implementation (#6771)
For now with support in postgres and mysql databases.
For now used in rcube_cache, rcube_imap_cache and enigma plugin
6 years ago
Aleksander Machniak e4281ae6d4 Tests: Try to fix timing issue 6 years ago
Aleksander Machniak d1b61410fc Tests: Add Dialog component 6 years ago
Aleksander Machniak d979aa0584 Tests: Add more browser tests for Responses 6 years ago
Aleksander Machniak 1376b5a647 Fix bug where message parts with no Content-Disposition header and no name were not listed on attachments list (#7117) 6 years ago
Aleksander Machniak b536e18d97 Tests: Add contacts print test 6 years ago
Aleksander Machniak 2f20f43cbb Tests: Change structure of tests 6 years ago
Aleksander Machniak 93519df826 Elastic: Fix disappearing sidebar in mail compose after clicking Mail button
Elastic: Fix incorrect aria-disabled attribute on Mail taskmenu button in mail compose
6 years ago
Aleksander Machniak 689c761026 Enigma: Simplify and fix handling of IDN with not all parts being punny-coded 6 years ago
Aleksander Machniak e89376709c Enigma: Display IDN domains of key users and identities in UTF8 6 years ago
Aleksander Machniak 6e63db6487 Merge branch 'enigma_idn_fix' of https://github.com/MaxBosse/roundcubemail into MaxBosse-enigma_idn_fix 6 years ago
Aleksander Machniak c2c85b0a76 Tests: Fix Print test on mobile, make GreenMail quiet on folder deletion 6 years ago
Aleksander Machniak 2037f2612b Tests: Fix testing with create_default_folders=true 6 years ago
Aleksander Machniak b6e75ebea7 Travis: Use bigger GreenMail startup timeout 6 years ago
Aleksander Machniak c138567ab5 Support 'greenmail' as an imap vendor label, don't call ID when not supported 6 years ago
Aleksander Machniak 4258acf57f Tests: Return proper exit code on error from bootstrap 6 years ago
Max Bosse 3faa976fc8 Fix creation of pgp-keys for IDN emails 6 years ago
Aleksander Machniak 5005c6c3e3 Travis: Fix/Update greenmail download location 6 years ago
Aleksander Machniak 5a6606a245 Tests: Add folder creation test 6 years ago
Aleksander Machniak 4f47ac0405 Fix bug where files in skins/ directory were listed on skins list (#7180) 6 years ago
Aleksander Machniak 700031b3b7 Elastic: Fix non-working folder subscription checkbox for newly added folders (#7174) 6 years ago
Aleksander Machniak 62fb938260 Tests: Add browser test for mail print and bug #7169 6 years ago
Aleksander Machniak 38116c1ae5 Merge branch 'master' of https://github.com/ssodk/roundcubemail into ssodk-master 6 years ago
Aleksander Machniak 5c73848499 Tests: Add Browser::assertElementsCount() helper 6 years ago
Aleksander Machniak c2e348067e Add browser tests for "more recipients" dialog 6 years ago
Aleksander Machniak 47089e9265 Elastic: Fix missing Close button in "more recipients" dialog 6 years ago
Aleksander Machniak f93857fbab Elastic: Fix text selection in recipient inputs (#7129) 6 years ago
ssodk 10ec313034
Update func.inc
Remove duplicates in print-view when the recipient list is expanded
6 years ago
Aleksander Machniak 9311c49cf4 Fix unexpected error message when mail refresh involves folder auto-unsubscribe (#6923) 6 years ago
Aleksander Machniak 470e91e615 Tests: Add forgotten data file 6 years ago
Aleksander Machniak c4f114fa84 Add browser tests for mail preview 6 years ago
Aleksander Machniak 7d2b4f8d15 Enigma: Add missing localization labels used in Larry skin 6 years ago
Aleksander Machniak d8ebf290b2 Add note about Windows and symlinks (#7151) 6 years ago
Aleksander Machniak b26340c271 Fix typo 6 years ago
Aleksander Machniak 646e0b99ea Enigma: Fix so using list checkbox selection does not load the key preview frame 6 years ago
Aleksander Machniak e3556986a1 Fix PHP Warning: array_filter() expects parameter 1 to be array, null given in subscription_options plugin (#7165)
.. when IMAP connection fails
6 years ago
Aleksander Machniak 355ff138fd Enigma: Fix so key list selection is reset when opening key creation form (#7154) 6 years ago
Aleksander Machniak 5d025609d3 Tests: Add browser tests for contact groups 6 years ago
Aleksander Machniak a646b13715 Try to fix flaky test 6 years ago
Aleksander Machniak 31b0275573 Add basic browser tests for message menu and open in new window feature 6 years ago
Aleksander Machniak a9abe62d58 Fix regression where "Open in new window" action didn't work (#7155) 6 years ago
Aleksander Machniak fded360d84 Fix so messages in threads with no root aren't displayed separately (#4999) 6 years ago
Aleksander Machniak 3cab0ad493 Update changelog
[skip ci]
6 years ago
Aleksander Machniak 0eb4674216
Managesieve: Allow display name with email address in vacation :from field (#6760) (#6763) 6 years ago
Aleksander Machniak 1b376517e8 Update changelog
[skip ci]
6 years ago
johndoh 51a9dd631f Add support for SameSite cookie attribute (req PHP >= 7.3.0) (#6772) 6 years ago
Aleksander Machniak 5236f40823 Update changelog
[skip ci]
6 years ago
Aleksander Machniak e8e8c31eb3
Improve namespace roots presentation (#5012) (#6789)
- Display a special icon for other users and shared namespace roots (Elastic)
- Change folders sorting so shared/other users namespaces are listed last

Fixes #5012.
6 years ago
Aleksander Machniak 245e1aa9f9 Update changelog
[skip ci]
6 years ago
johndoh d51352b9cb Add support for nested if conditions (#6829)
* add support for nested if conditions
* add tests for condition parsing in templates
* make [space][slash] ending of condition objects optional (#6954)
6 years ago
Aleksander Machniak 530b53d92f Update changelog 6 years ago
jelle van der Waa 11e5c1af4f 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
6 years ago
Théo 5a1a33752e Updte copyright year in About (#7138) 6 years ago
Aleksander Machniak 094da1e653 Elastic: Fix bug where it was possible to switch editor mode when 'htmleditor' was in 'dont_override' (#7143) 6 years ago
Aleksander Machniak e2bd4548ed Tests: Move logon helpers to the App component 6 years ago
Aleksander Machniak 2c0dd762ed Update changelog
[skip ci]
6 years ago
Aleksander Machniak cf8277ea3e Tests: Try to fix test that fails on Travis, but not on my setup 6 years ago
Aleksander Machniak f72054e761 Tests: Create Browser and Components for better code structure 6 years ago
Aleksander Machniak 1edd7a4b3f Tests: Add functional tests for contacts export 6 years ago
Aleksander Machniak 57162ef481 Make install.php accept long Chrome version string
[skip ci]
6 years ago
Aleksander Machniak 421dbf5271 Tests: Mantion TESTS_MODE in README
[skip ci]
6 years ago
Aleksander Machniak 564287d6c8 Tests: Try to fix failing toolbar menu tests in phone mode 6 years ago
Aleksander Machniak accb5f63e2 Travis: Fix run.sh 6 years ago
Aleksander Machniak 9a0e71ddee Travis: Enable in-browser tests in phone and tablet mode 6 years ago
Aleksander Machniak 934382c91f Make testing in all phone/tablet/desktop modes of Elastic possible 6 years ago
Aleksander Machniak a32e2b4c6f Addressbook -> Contacts 6 years ago
Aleksander Machniak 59e1efb595 Add more in-browser tests 6 years ago
Aleksander Machniak 7abd4f2c15 Travis: Cleanup 6 years ago
Aleksander Machniak 5defccb919 Travis: Run browser tests on bionic with node-less 6 years ago
Aleksander Machniak 7911c75b1e Travis: Corrected paths 6 years ago
Aleksander Machniak ef0d23241a Travis: Run in-browser tests not in devel_mode 6 years ago
Aleksander Machniak f32b9ae51d Travis: Move install commands to .ci/install.sh 6 years ago
Aleksander Machniak 581bc6886b Travis: Install js deps for in-browser tests 6 years ago
Aleksander Machniak 724f5c740c Travis: Install proper version of WebDriver 6 years ago
Aleksander Machniak 31b33c0f39 Don't require config.inc.php when only config-<env>.inc.php exists 6 years ago
Aleksander Machniak 15885055ca Travis: Try to debug why in-browser tests do not work 6 years ago
Aleksander Machniak 9d1e4b4828 Travis: Fix syntax error 6 years ago
Aleksander Machniak 87304c29d1 Travis: Fix in-browser tests setup 6 years ago
Aleksander Machniak 53a111f8c3 Travis: Debugging setup.sh script 6 years ago
Aleksander Machniak 1f656e5a07 Travis: In-browser tests with GreenMail server 6 years ago
Aleksander Machniak 895b6f54f3 Add more in-browser tests 6 years ago
Aleksander Machniak a88798ab18 We don't need index-test.php anymore 6 years ago
Aleksander Machniak 7ebc159780 Travis: Remove in-browser tests
Yeah, we'd need to setup a test account on some IMAP server which
would be publicly available. I'm not sure we can do that.
Also, we'd need to add config-tests.inc.php file and maybe use sqlite database.
6 years ago
Aleksander Machniak 0ea8e9c289 Travis: Try in-browser tests on PHP 7.3
as PHP 7.4 does not yet have php-zip extension
6 years ago
Aleksander Machniak dd7b51bcd3 Travis: Fix syntax error 6 years ago
Aleksander Machniak d67b288cbc Travis: Fix syntax error 6 years ago
Aleksander Machniak eac409ac4a Travis: Enable in-browser tests 6 years ago
Aleksander Machniak 0b9ff6426e Rewrite Selenium tests with use of laravel/dusk 6 years ago
Aleksander Machniak 47d9ed6d0c Add support for PHPUnit 6 and 7 (#6870)
Fixes composer dependencies: Package phpunit/phpunit-mock-objects is abandoned

We cannot support v8 yet because of errors like:
Declaration of MailFunc::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void
It would require dropping PHP < 7.1 support.
6 years ago
Aleksander Machniak ad84b2df3f Update localization 6 years ago
Aleksander Machniak f610707554 Skip install-jsdeps.sh execution when using "complete" package to update "custom" installation (#7087) 6 years ago
Aleksander Machniak df2acc5eb8 Elastic: Fix hidden list widget on mobile/tablet when selecting folder while search menu is open (#7120) 6 years ago
Aleksander Machniak 9bb95471bf Merge branch 'master' of github.com:roundcube/roundcubemail 6 years ago
Aleksander Machniak 226097fc3f Markasjunk: Fix marking more than one message as spam/ham with email_learn driver (#7121) 6 years ago
Aleksander Machniak 52caed0d66 Remove optional argument leftover 6 years ago
Aleksander Machniak 37cfa0a43b Fix malformed characters in HTML message with charset meta tag not in head (#7116) 6 years ago
Aleksander Machniak 5f30dc68a3 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.
6 years ago
Aleksander Machniak eadbe91c92 Elastic: Simple search in pretty selects (#7072) 6 years ago
Aleksander Machniak 29defb82e9 Fix bug where next message wasn't displayed after delete in List mode (#7096) 6 years ago
Aleksander Machniak cf90c69ad7 Fix bug where 'text' attribute on body tag was ignored when displaying HTML message (#7109) 6 years ago
Aleksander Machniak 545ea62dfc Fix bug where listing tables in PostgreSQL database with db_prefix didn't work (#7093) 6 years ago
Aleksander Machniak 34e2507aac Fix so contact's organization field accepts up to 128 characters (it was 50)
Also assistant, manager, spouse, website and im
6 years ago
Aleksander Machniak 93c0e02f07 Plugin API: Make actionbefore, before<action>, actionafter and after<action> events working with plugin actions (#7106) 6 years ago
Aleksander Machniak d8d29241a6 Remove redundant spellcheck_langs env variable 6 years ago
Aleksander Machniak 0aa5eca1fd Fix bug where 'skins_allowed' option didn't enforce user skin preference (#7080) 6 years ago
Aleksander Machniak db4bf5573e Travis: Ignore endroid/qr-code in a try to make PHP 7.4 tests working
Travis' PHP 7.4 build does not have php-gd available yet.
6 years ago
Aleksander Machniak a5b15da155 Fix so displayed maximum attachment size depends also on 'max_message_size' (#7105) 6 years ago
Aleksander Machniak 0b45c3c6b0 Fix matching multiple X-Forwarded-For addresses with 'proxy_whitelist' (#7107) 6 years ago
Aleksander Machniak df7b76b023 Travis: Add PHP 7.4 6 years ago
Aleksander Machniak a51beef56c Travis: Remove ppc64le 6 years ago
Aleksander Machniak f3381f12d3 Travis: Move ppc64le arch to the matrix, test it only with the most recent PHP version 6 years ago
Aleksander Machniak 3fda927858 Try tests on different cpu architectures 6 years ago
Sebastiaan Lokhorst 259b7fa065 Explain difference between ssl:// and tls:// for default_host (#7099) 6 years ago
Hiroshi Shirosaki a335102e47 Fix email address name encoding with ISO-2022-JP
Convert to UTF-8 to split addresses correctly.
Base64 encode ISO-2022-JP name.
Add a unit test.
6 years ago
Hiroshi Shirosaki d9667295d8 Fix wordwrap with ISO-2022-JP
A message with ISO-2022-JP-MS characters currupts by wordwrap.
Use ISO-2022-JP-MS charset in wordwrap.
Add a unit test.
6 years ago
Hiroshi Shirosaki 869d1d1eaf Fix ISO-2022-JP-MS encoding convert
ISO-2022-JP-MS characters are removed by iconv on sending mail.
Use mbstring functions on sending.
Add a unit test.
6 years ago
Aleksander Machniak 8471930652 Add 'filter' to required extensions 6 years ago
Aleksander Machniak 5b253fb498 Elastic: Fix contrast of warning toasts (#7058)
Add some color variables and slightly make bg-color stronger on "box" messages.
6 years ago
Aleksander Machniak 90781bff3b Add aria-label to list navigation toolbars 6 years ago
Aleksander Machniak 58aea024b4 Improve style tag handling in TinyMCE (#7088) 6 years ago
Aleksander Machniak 94c795b539 Fix bug where HTML reply could add an empty line with extra indentation above the original message (#7088) 6 years ago
Aleksander Machniak e7cb9bf604 Password: Fix kpasswd and smb drivers' double-escaping bug (#7092) 6 years ago
Aleksander Machniak e3c6989494 Log X-Real-IP only when it's different than REMOTE_ADDR 6 years ago
Aleksander Machniak 3b3dd0cf7a Add note about URI format of hostnames in ldap config (#7082) 6 years ago
Aleksander Machniak 35101ca44a Fix filter selection after removing a first filter (#7079) 6 years ago
Aleksander Machniak 57476c09f9 Fix bug where cancelling switching from HTML to plain text didn't set the flag properly (#7077) 6 years ago
Aleksander Machniak 604e814898 Fix/remove useless keyup event handler on username input in logon form (#6970) 6 years ago
Aleksander Machniak 9219f4cd65 Fix so use of Ctrl+A does not scroll the list (#7020) 6 years ago
Christian Weiske 1c1a73e4d5 Set the content-type of JSON responses to application/json, not text/plain (#6573) 6 years ago
Sebastiaan Lokhorst 511e99ee1a Update SMTP TLS/STARTTLS explanation in config.php (#7066)
Implicit TLS (port 465) is no longer deprecated since RFC 8314.
6 years ago
Aleksander Machniak 2321bce469 Add notes about file ownership (#7009) 6 years ago
Aleksander Machniak 04aff2efa3 Fix so install-jsdeps.sh removes Bootstrap's sourceMappingURL (#7035) 6 years ago
Aleksander Machniak 2f928a516d 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
6 years ago
Aleksander Machniak fa34e5ebcd Fix bug where a new saved search added after removing all searches wasn't added to the list (#7061) 6 years ago
Aleksander Machniak 006f7d1087 Fix so modifier type select wasn't hidden after hiding modifier select on header change 6 years ago
Aleksander Machniak ffeebff3f9 Fix bug where Ctype extension wasn't required in Installer and INSTALL file (#7049) 6 years ago
Aleksander Machniak 45546508d2 Replace "Filter disabled" with "Filter enabled" (#7028) 6 years ago
Aleksander Machniak 8d81df7dc3 Fix bug where a new contact group added after removing all groups from addressbook wasn't added to the list 6 years ago
Aleksander Machniak dbc2b5e11c Fix bug where deleting a saved search in addressbook caused display issue on sources/groups list (#7061)
also remove dead code.
6 years ago
Aleksander Machniak 4485940d0e 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.
6 years ago
Aleksander Machniak 4cf2363f43 Update changelog 6 years ago
johndoh b68c141e1d Always update folder count after purge command (#7051)
... not only when it is the current folder.
6 years ago
Aleksander Machniak e0574d909e Merge branch 'johndoh-html5' 6 years ago
Aleksander Machniak 8a25ddbf53 Update changelog, CS improvements 6 years ago
Aleksander Machniak feb4ecdf51 Elastic: Fix language icon (#6624) 6 years ago
Aleksander Machniak 022a1b683f Enigma: Add script to import keys from filesystem to the db storage (for multihost) 6 years ago
Aleksander Machniak d4a8642d5a Code simplification and style fixes 6 years ago
Aleksander Machniak d2738d1e8b Remove spaces from 'accept' attribute 6 years ago
Aleksander Machniak ba83042b1a Elastic: Remove redundant listmenulink elements 6 years ago
Aleksander Machniak ea3ad31ce0 Fix bug where the Installer would not warn about required schema upgrade (#7042) 6 years ago
Aleksander Machniak 47f72303ac Small correction 6 years ago
Aleksander Machniak 4b607e2ed3 Fix so Elastic is also a default in jqueryui plugin (#7039) 6 years ago
Aleksander Machniak fecff809fe 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.
6 years ago
Aleksander Machniak 0113ea9602 Fix tables listing routine when DSN contained a database with unsupported suffix (#7034) 6 years ago
Aleksander Machniak 2e2028ae78 Elastic: Fix position of mobile floating action button (#7038) 6 years ago
Aleksander Machniak b6810cc163 Mention required min. version of lessc (#7031) 6 years ago
Aleksander Machniak f4722cb451 Fixed typo 6 years ago
Aleksander Machniak d0dd042b73 Fix so update.sh script warns about changed defaults (#7011) 6 years ago
Aleksander Machniak b63bc1ae22 Fix misleading comment 6 years ago
Aleksander Machniak 81bccd00ad Larry: Fix html editor toolbar background in Identities 6 years ago
Aleksander Machniak 22d629a0e9 Elastic: Fix regression in the new editor widget (#7021) 6 years ago
Aleksander Machniak 110eebdd1b Fix db_prefix handling in queries with `TRUNCATE TABLE <name>` and `UNIQUE <name>` (#7013) 6 years ago
Aleksander Machniak ee1f01a0df Fix so 401 error is returned only on failed logon requests (#7010) 6 years ago
Aleksander Machniak 157edd72bb Elastic: Fix Edit responses button state in Mailvelope mode 6 years ago
Aleksander Machniak b4b400c91d Fix invalid Signature button state after escaping Mailvelope mode (#7015) 6 years ago
Aleksander Machniak 1eb50c7cd3 Remove unused variables 6 years ago
Aleksander Machniak 71bcf2e245 Fix regexp for version input to accept rcX releases 6 years ago
Aleksander Machniak b63d549834 Elastic: Change HTML editor widget to improve form flow (#6992) 6 years ago
Aleksander Machniak 4b24ba1372 Fix bug where cache keys could exceed length limit specified in db schema (#7004) 6 years ago
Aleksander Machniak 4cf8952afa Fix PHP warning: "array_merge(): Expected parameter 2 to be an array, null given in sendmail.inc (#7003) 6 years ago
Aleksander Machniak 338b25dec9 Managesieve: Fix locked UI after opening filter frame (#7007) 6 years ago
Aleksander Machniak 03cee8f554 Add a release mark 6 years ago
Aleksander Machniak 8aaf7d35c1 Fix displaying version number for rcX versions 6 years ago
Aleksander Machniak 30af42be87 Remove a note about Larry being default 6 years ago
Aleksander Machniak 757d5fc1b1 Correct file permissions 6 years ago
Thomas Bruederli 9ebba7c2ce Set default skin to 'elastic' in sample config 6 years ago
Aleksander Machniak 6aab238773 Small code improvements 6 years ago
Aleksander Machniak c5c5a9325c Elastic: Fix regression where recipient input didn't update internal input state (#6988) 6 years ago
Aleksander Machniak 508e83470c Master is 1.5-git now 6 years ago
Aleksander Machniak f768ff1464 Update changelog and cleanup 6 years ago
PhilW 75125cbac1 add unit tests for rcmail_output_html::get_template_logo 6 years ago
PhilW 97001e9221 rewrite skin_logo config 6 years ago
Aleksander Machniak 727f8f04d8 Elastic: Fix regression where Encrypt button wasn't displayed in mail compose toolbar (#6982)
Also fix Reply-All button handling
6 years ago
Aleksander Machniak 52c755207d Enigma: Fix bug where signing option was set to disabled after saving a draft in Elastic skin (#6515) 6 years ago
Aleksander Machniak 3a097e6836 Fix bug where inline images could have been ignored if Content-Id header contained redundant spaces (#6980) 6 years ago
Aleksander Machniak df414aa30c Elastic: Fix unread filter icon and search state on folder change (#6978) 6 years ago
Aleksander Machniak 5543240e67 Elastic: Fix language icon (#6624) 6 years ago
Aleksander Machniak 5a0843de4b Enigma: Add script to import keys from filesystem to the db storage (for multihost) 6 years ago
Aleksander Machniak 5b6a50a4e4 Code simplification and style fixes 6 years ago
Aleksander Machniak 6f4c2cf231 Remove spaces from 'accept' attribute 6 years ago
Aleksander Machniak bdce0ba3f2 Elastic: Remove redundant listmenulink elements 6 years ago
Aleksander Machniak 3cd3d8eecb Fix bug where the Installer would not warn about required schema upgrade (#7042) 6 years ago
Aleksander Machniak bd21d43ac9 Small correction 6 years ago
Aleksander Machniak 90738d7a36 Fix so Elastic is also a default in jqueryui plugin (#7039) 6 years ago
Aleksander Machniak 40aef395bb 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.
6 years ago
Aleksander Machniak 67898b23e4 Fix tables listing routine when DSN contained a database with unsupported suffix (#7034) 6 years ago
Aleksander Machniak 2e26aee2b6 Elastic: Fix position of mobile floating action button (#7038) 6 years ago
Aleksander Machniak f2c878c82b Mention required min. version of lessc (#7031) 6 years ago
Aleksander Machniak 814db5a33e Fixed typo 6 years ago
Aleksander Machniak 294683b390 Fix so update.sh script warns about changed defaults (#7011) 6 years ago
Aleksander Machniak a269e7c106 Fix misleading comment 6 years ago
Aleksander Machniak 4d54074ad2 Larry: Fix html editor toolbar background in Identities 6 years ago
Aleksander Machniak c2bd60f1aa Elastic: Fix regression in the new editor widget (#7021) 6 years ago
Aleksander Machniak a80d73602f Fix db_prefix handling in queries with `TRUNCATE TABLE <name>` and `UNIQUE <name>` (#7013) 6 years ago
Aleksander Machniak b8555ce4f3 Fix so 401 error is returned only on failed logon requests (#7010) 6 years ago
Aleksander Machniak 4e840c7669 Elastic: Fix Edit responses button state in Mailvelope mode 6 years ago
Aleksander Machniak 7f6dd16479 Fix invalid Signature button state after escaping Mailvelope mode (#7015) 6 years ago
Aleksander Machniak 98c71ee62e Remove unused variables 6 years ago
Aleksander Machniak c734f3e1ab Fix regexp for version input to accept rcX releases 6 years ago
Aleksander Machniak 3c01f472e0 Elastic: Change HTML editor widget to improve form flow (#6992) 6 years ago
Aleksander Machniak 4cc20eef72 Fix bug where cache keys could exceed length limit specified in db schema (#7004) 6 years ago
Aleksander Machniak 700cc5a055 Fix PHP warning: "array_merge(): Expected parameter 2 to be an array, null given in sendmail.inc (#7003) 6 years ago
Aleksander Machniak 58850f0b93 Managesieve: Fix locked UI after opening filter frame (#7007) 6 years ago
Aleksander Machniak 5677b2fc9d Add a release mark 6 years ago
Aleksander Machniak 8a726316bb Fix displaying version number for rcX versions 6 years ago
Aleksander Machniak 57aa69206a Remove a note about Larry being default 6 years ago
Aleksander Machniak 358f647273 Correct file permissions 6 years ago
Thomas Bruederli 8db933e440 Set default skin to 'elastic' in sample config 6 years ago
Aleksander Machniak 0e2d3789ce Small code improvements 6 years ago
Aleksander Machniak 959654c0c2 Elastic: Fix regression where recipient input didn't update internal input state (#6988) 6 years ago
Aleksander Machniak 9f1d185c44 Master is 1.5-git now 6 years ago
PhilW 32775f0e43 remove redundant type attrib in script tag 6 years ago

@ -0,0 +1,30 @@
<?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';

@ -0,0 +1,22 @@
#!/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

@ -0,0 +1,25 @@
#!/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

@ -0,0 +1,30 @@
#!/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

@ -1,6 +0,0 @@
[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 'append' with 'merge' for Apache version 2.2.9 and later
#Header append Cache-Control public env=!NO_CACHE
# replace 'merge' with 'append' for Apache < 2.2.9
#Header merge 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,18 +14,24 @@ 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:
- if [ "$CODE_COVERAGE" != 1 ]; then phpenv config-rm xdebug.ini || true; fi
- cp composer.json-dist composer.json
- composer install --prefer-dist
- .ci/install.sh
script:
- if [ "$CODE_COVERAGE" = 1 ]; then CODE_COVERAGE_ARGS="--coverage-text"; fi; vendor/bin/phpunit -c tests/phpunit.xml $CODE_COVERAGE_ARGS
- .ci/run.sh
notifications:
email: false

@ -1,6 +1,40 @@
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)
@ -84,6 +118,7 @@ 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)
- Iconv, Zip, Fileinfo, Intl, Exif (recommended)
- 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,6 +120,11 @@ 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
--------
@ -250,11 +255,6 @@ 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,6 +4,13 @@ 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] (5) COLLATE Latin1_General_CI_AI NULL ,
[language] [varchar] (16) 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] (5) COLLATE Latin1_General_CI_AI NOT NULL ,
[language] [varchar] (16) 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', '2019092900')
INSERT INTO [dbo].[system] ([name], [value]) VALUES ('roundcube-version', '2020020101')
GO

@ -0,0 +1,4 @@
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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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(5),
`language` varchar(16),
`preferences` longtext,
PRIMARY KEY(`user_id`),
UNIQUE `username` (`username`, `mail_host`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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(5) NOT NULL,
`language` varchar(16) 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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_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 utf8 COLLATE utf8_general_ci */;
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
/*!40014 SET FOREIGN_KEY_CHECKS=1 */;
INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2019092900');
INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2020020101');

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

@ -0,0 +1,21 @@
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(5),
"language" varchar(16),
"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(5) NOT NULL,
"language" varchar(16) 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', '2019092900');
INSERT INTO "system" ("name", "value") VALUES ('roundcube-version', '2020020101');

@ -0,0 +1,2 @@
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(5),
"language" varchar(16),
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(5) NOT NULL,
"language" varchar(16) 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', '2019092900');
INSERT INTO "system" (name, value) VALUES ('roundcube-version', '2020020101');

@ -0,0 +1,2 @@
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(5),
language varchar(16),
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(5) NOT NULL,
language varchar(16) 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', '2019092900');
INSERT INTO system (name, value) VALUES ('roundcube-version', '2020020101');

@ -0,0 +1,57 @@
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,6 +23,9 @@ 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"
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6 || ^7"
},
"suggest": {
"kolab/net_ldap3": "~1.1.1 required for connecting to LDAP",

@ -50,6 +50,8 @@ $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,6 +267,8 @@ $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)
@ -502,6 +504,11 @@ $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'
@ -888,6 +895,7 @@ $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,

@ -1,27 +0,0 @@
<?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.4.6 |
| Version 1.5-git |
| |
| Copyright (C) The Roundcube Dev Team |
| |

@ -37,7 +37,6 @@ $required_php_exts = array(
$optional_php_exts = array(
'FileInfo' => 'fileinfo',
'Libiconv' => 'iconv',
'Intl' => 'intl',
'Exif' => 'exif',
'LDAP' => 'ldap',
@ -76,7 +75,6 @@ $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 an 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 a URL where a user can get support for this Roundcube installation.<br/>PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!</div>
<p class="hint">Enter an absolute URL (including http://) to a support page/form or a mailto: link.</p>
</dd>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

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

@ -263,6 +263,15 @@ 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)');
@ -290,7 +299,7 @@ else {
<tbody>
<tr>
<td><label for="smtp_server">Server</label></td>
<td><?php echo rcube::Q(rcube_utils::parse_host($RCI->getprop('smtp_server', 'localhost'))); ?></td>
<td><?php echo $smtp_host_field->show($_POST['_smtp_host']); ?></td>
</tr>
<tr>
<td><label for="smtp_port">Port</label></td>
@ -317,6 +326,9 @@ 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']));
@ -346,8 +358,7 @@ if (isset($_POST['sendmail'])) {
$head = $mail_object->txtHeaders($send_headers);
$SMTP = new rcube_smtp();
$SMTP->connect(rcube_utils::parse_host($RCI->getprop('smtp_server')),
$RCI->getprop('smtp_port'), $CONFIG['smtp_user'], $CONFIG['smtp_pass']);
$SMTP->connect($smtp_host, $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,6 +199,17 @@ 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
@ -414,6 +425,8 @@ 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.4",
"version": "3.5",
"authors": [
{
"name": "Thomas Bruederli",

@ -24,6 +24,8 @@ $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,6 +26,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 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,7 +26,8 @@ $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()

@ -0,0 +1,82 @@
<?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
});
}
}

@ -0,0 +1,149 @@
<?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()

@ -0,0 +1,94 @@
<?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,17 +676,8 @@ class enigma_driver_gnupg extends enigma_driver
continue;
}
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']);
}
$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 ($db->is_error($result)) {
rcube::raise_error(array(

@ -66,6 +66,7 @@ $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';
@ -82,7 +83,6 @@ $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,6 +96,10 @@ $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">
<div class="pagenav menu footer small" role="toolbar" aria-label="<roundcube:label name="arialabellistnav" />">
<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 = $.trim(q.text());
text = q.text().trim();
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,7 +1,11 @@
- 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,25 +248,9 @@ class rcube_sieve_engine
else if ($list) {
$script_name = $list[0];
}
// create a new (initial) script
else {
// 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;
}
// if script does not exist create one with default content
$this->create_default_script();
}
}
@ -676,7 +660,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);
$from = rcube_utils::get_input_value('_action_from', rcube_utils::INPUT_POST, true);
$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);
@ -1156,8 +1140,30 @@ class rcube_sieve_engine
}
}
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 (!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 ($this->form['actions'][$i]['reason'] == '')
@ -1751,11 +1757,17 @@ class rcube_sieve_engine
}
// custom header and variable inputs
$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";
$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";
$test = self::rule_test($rule);
$target = '';
@ -1808,9 +1820,10 @@ class rcube_sieve_engine
}
$tout .= $this->match_type_selector('rule_op', $id, $test, $rule['test']);
$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";
$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";
$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');
@ -1898,8 +1911,9 @@ 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'], true,
$this->error_class($id, 'test', 'mime_param', 'rule_mime_param'), 30, $mime_type != 'param');
$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 .= '</div>';
}
@ -2193,7 +2207,16 @@ class rcube_sieve_engine
}
if ($from_addr) {
$default_identity = $this->rc->user->list_emails(true);
$action['from'] = $default_identity['email'];
$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);
}
}
@ -2225,8 +2248,9 @@ 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'], true,
$this->error_class($id, 'action', 'addresses', 'action_addresses'), 30)
$out .= $this->list_input($id, 'action_addresses', $action['addresses'], 30, false, array(
'class' => $this->error_class($id, 'action', 'addresses', 'action_addresses')
))
. 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>';
@ -2280,8 +2304,10 @@ class rcube_sieve_engine
. rcube::Q($this->plugin->gettext('flag'.$fidx))) . '<br>';
}
$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}");
$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}"
));
$out .= html::div(array(
'id' => 'action_flags' . $id,
@ -2395,9 +2421,10 @@ 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'], true,
$this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>';
. '<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>';
$out .= '</div>';
if (in_array('editheader', $this->exts)) {
@ -2453,8 +2480,9 @@ 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'], true,
$this->error_class($id, 'action', 'value', 'action_delheader_value')) . "\n";
$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 .= '<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'))));
@ -2575,22 +2603,25 @@ class rcube_sieve_engine
$this->rc->output->add_script($script, 'docready');
}
protected function list_input($id, $name, $value, $enabled, $class, $size = null, $hidden = false, $elem_id = null)
protected function list_input($id, $name, $value, $size = null, $hidden = false, $attrib = array())
{
$value = (array) $value;
$value = array_map(array('rcube', 'Q'), $value);
$value = implode("\n", $value);
return html::tag('textarea', array(
$attrib = array_merge($attrib, 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',
), $value);
));
if (empty($attrib['id'])) {
$attrib['id'] = $name . $id;
}
return html::tag('textarea', $attrib, $value);
}
/**
@ -3031,7 +3062,7 @@ class rcube_sieve_engine
'name' => "_{$name}[$id]",
'id' => "{$name}{$id}",
'style' => 'display:' .(!in_array($rule, array('size', 'duplicate')) ? 'inline' : 'none'),
'class' => 'operator_selector',
'class' => 'operator_selector col-6',
'onchange' => "{$name}_select(this, '{$id}')",
));
@ -3079,4 +3110,135 @@ 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 rules after the stop
// we might loose information if there are rules after the stop
$stop_found = true;
}
else if ($act['type'] == 'discard') {
@ -207,10 +207,8 @@ class rcube_sieve_forward extends rcube_sieve_engine
$date_extension = in_array('date', $this->exts);
$forward_tests = (array) $this->forward['tests'];
if ($action == 'redirect' || $action == 'copy') {
if (empty($target) || !rcube_utils::check_email($target)) {
$error = 'noemailwarning';
}
if (empty($target) || !rcube_utils::check_email($target)) {
$error = 'noemailwarning';
}
if (empty($forward_tests)) {
@ -224,18 +222,13 @@ 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();
$rule['after'] = $after;
if ($action && $action != 'keep') {
$rule['actions'][] = array(
'type' => $action == 'discard' ? 'discard' : 'redirect',
$rule['actions'] = array(array(
'type' => 'redirect',
'copy' => $action == 'copy',
'target' => $action != 'discard' ? $target : '',
);
}
'target' => $target,
));
if ($this->save_forward_script($rule)) {
if ($this->merge_rule($rule, $this->forward, $this->script_name)) {
$this->rc->output->show_message('managesieve.forwardsaved', 'confirmation');
$this->rc->output->send();
}
@ -320,99 +313,6 @@ 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
*
@ -472,17 +372,13 @@ 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();
if ($data['action'] && $data['action'] != 'keep') {
$rule['actions'][] = array(
'type' => $data['action'] == 'discard' ? 'discard' : 'redirect',
$rule['actions'] = array(array(
'type' => 'redirect',
'copy' => $data['action'] == 'copy',
'target' => $data['action'] != 'discard' ? $data['target'] : '',
);
}
'target' => $data['target'],
));
return $this->save_forward_script($rule);
return $this->merge_rule($rule, $this->forward, $this->script_name);
}
/**

@ -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);
$from = rcube_utils::get_input_value('vacation_from', rcube_utils::INPUT_POST, true);
$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,8 +218,30 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
}
if (!empty($vacation_action['from']) && !rcube_utils::check_email($vacation_action['from'])) {
$error = 'noemailwarning';
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 ($vacation_action['reason'] == '') {
@ -309,7 +331,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
);
}
if ($this->save_vacation_script($rule)) {
if ($this->merge_rule($rule, $this->vacation, $this->script_name)) {
$this->rc->output->show_message('managesieve.vacationsaved', 'confirmation');
$this->rc->output->send();
}
@ -349,8 +371,15 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
if ($from_addr) {
$default_identity = $this->rc->user->list_emails(true);
$this->vacation['from'] = $default_identity['email'];
$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'] = implode(', ', $from);
}
// form elements
@ -379,7 +408,11 @@ 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'])) {
if (
$this->rc->config->get('managesieve_vacation') != 2
&& !empty($this->vacation['list'])
&& in_array($this->script_name, $this->active)
) {
$after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after'));
$after->add('---', '');
@ -500,7 +533,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 ($after) {
if (!empty($after)) {
$table->add('title', html::label('vacation_after', $this->plugin->gettext('vacation.after')));
$table->add(null, $after->show($this->vacation['idx'] - 1));
}
@ -616,99 +649,6 @@ 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
*
@ -908,7 +848,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
);
}
return $this->save_vacation_script($rule);
return $this->merge_rule($rule, $this->vacation, $this->script_name);
}
/**

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

@ -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()

@ -0,0 +1,134 @@
<?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,6 +51,7 @@
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
@ -402,6 +403,21 @@
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.1",
"version": "5.2",
"authors": [
{
"name": "Aleksander Machniak",

@ -0,0 +1,223 @@
<?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,3 +38,5 @@ $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,3 +41,6 @@ $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,3 +38,5 @@ $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()

@ -1 +0,0 @@
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()

@ -1 +0,0 @@
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()

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

Loading…
Cancel
Save