From 985f1ab41862e093a130a4f1eb1b4cdf5d687a2c Mon Sep 17 00:00:00 2001 From: Whykioh Date: Mon, 11 Mar 2024 00:58:34 +0100 Subject: [PATCH] Import Ruty --- ruty/mails/bin/cleandb.sh | 31 + ruty/mails/bin/cssshrink.sh | 44 + ruty/mails/bin/decrypt.sh | 65 + ruty/mails/bin/deluser.sh | 141 + ruty/mails/bin/gc.sh | 37 + ruty/mails/bin/indexcontacts.sh | 26 + ruty/mails/bin/initdb.sh | 47 + ruty/mails/bin/installto.sh | 148 + ruty/mails/bin/jsshrink.sh | 51 + ruty/mails/bin/makedoc.sh | 24 + ruty/mails/bin/moduserprefs.sh | 68 + ruty/mails/bin/msgexport.sh | 149 + ruty/mails/bin/msgimport.sh | 117 + ruty/mails/bin/update.sh | 328 + ruty/mails/bin/updatecss.sh | 117 + ruty/mails/bin/updatedb.sh | 39 + ruty/mails/config/.htaccess | 7 + ruty/mails/config/config.inc.php | 89 + ruty/mails/config/config.inc.php.sample | 66 + ruty/mails/config/defaults.inc.php | 1475 +++ ruty/mails/config/mimetypes.php | 56 + ruty/mails/installer/check.php | 290 + ruty/mails/installer/client.js | 48 + ruty/mails/installer/config.php | 647 + ruty/mails/installer/images/add.png | Bin 0 -> 653 bytes .../installer/images/banner_gradient.gif | Bin 0 -> 506 bytes .../installer/images/banner_schraffur.gif | Bin 0 -> 12454 bytes ruty/mails/installer/images/delete.png | Bin 0 -> 637 bytes ruty/mails/installer/images/error.png | Bin 0 -> 565 bytes .../mails/installer/images/roundcube_logo.png | Bin 0 -> 3783 bytes ruty/mails/installer/index.php | 173 + ruty/mails/installer/styles.css | 240 + ruty/mails/installer/test.php | 499 + ruty/mails/logs/.htaccess | 7 + ruty/mails/logs/errors.log | 11 + ruty/mails/program/actions/contacts/copy.php | 150 + .../mails/program/actions/contacts/delete.php | 172 + ruty/mails/program/actions/contacts/edit.php | 269 + .../mails/program/actions/contacts/export.php | 192 + .../actions/contacts/group_addmembers.php | 86 + .../program/actions/contacts/group_create.php | 67 + .../program/actions/contacts/group_delete.php | 67 + .../actions/contacts/group_delmembers.php | 71 + .../program/actions/contacts/group_rename.php | 77 + .../mails/program/actions/contacts/import.php | 486 + ruty/mails/program/actions/contacts/index.php | 1524 +++ ruty/mails/program/actions/contacts/list.php | 94 + .../mails/program/actions/contacts/mailto.php | 91 + ruty/mails/program/actions/contacts/move.php | 243 + ruty/mails/program/actions/contacts/photo.php | 118 + ruty/mails/program/actions/contacts/print.php | 147 + .../mails/program/actions/contacts/qrcode.php | 128 + ruty/mails/program/actions/contacts/save.php | 323 + .../mails/program/actions/contacts/search.php | 313 + .../actions/contacts/search_create.php | 73 + .../actions/contacts/search_delete.php | 63 + ruty/mails/program/actions/contacts/show.php | 243 + ruty/mails/program/actions/contacts/undo.php | 68 + .../program/actions/contacts/upload_photo.php | 94 + ruty/mails/program/actions/login/oauth.php | 101 + .../mails/program/actions/mail/addcontact.php | 98 + .../actions/mail/attachment_delete.php | 48 + .../actions/mail/attachment_display.php | 35 + .../actions/mail/attachment_rename.php | 54 + .../actions/mail/attachment_upload.php | 290 + .../program/actions/mail/autocomplete.php | 214 + ruty/mails/program/actions/mail/bounce.php | 140 + .../program/actions/mail/check_recent.php | 211 + ruty/mails/program/actions/mail/compose.php | 1766 +++ ruty/mails/program/actions/mail/copy.php | 67 + ruty/mails/program/actions/mail/delete.php | 140 + .../program/actions/mail/folder_expunge.php | 54 + .../program/actions/mail/folder_purge.php | 82 + ruty/mails/program/actions/mail/get.php | 372 + ruty/mails/program/actions/mail/getunread.php | 69 + .../program/actions/mail/group_expand.php | 56 + ruty/mails/program/actions/mail/headers.php | 87 + ruty/mails/program/actions/mail/import.php | 200 + ruty/mails/program/actions/mail/index.php | 1707 +++ ruty/mails/program/actions/mail/list.php | 162 + .../program/actions/mail/list_contacts.php | 204 + ruty/mails/program/actions/mail/mark.php | 196 + ruty/mails/program/actions/mail/move.php | 164 + ruty/mails/program/actions/mail/pagenav.php | 78 + ruty/mails/program/actions/mail/search.php | 287 + .../program/actions/mail/search_contacts.php | 139 + ruty/mails/program/actions/mail/send.php | 424 + ruty/mails/program/actions/mail/sendmdn.php | 150 + ruty/mails/program/actions/mail/show.php | 953 ++ .../mails/program/actions/mail/viewsource.php | 98 + ruty/mails/program/actions/settings/about.php | 147 + .../actions/settings/folder_create.php | 22 + .../actions/settings/folder_delete.php | 66 + .../program/actions/settings/folder_edit.php | 347 + .../program/actions/settings/folder_purge.php | 70 + .../actions/settings/folder_rename.php | 94 + .../program/actions/settings/folder_save.php | 235 + .../program/actions/settings/folder_size.php | 49 + .../actions/settings/folder_subscribe.php | 67 + .../actions/settings/folder_unsubscribe.php | 50 + .../program/actions/settings/folders.php | 377 + .../program/actions/settings/identities.php | 73 + .../actions/settings/identity_create.php | 22 + .../actions/settings/identity_delete.php | 52 + .../actions/settings/identity_edit.php | 240 + .../actions/settings/identity_save.php | 212 + ruty/mails/program/actions/settings/index.php | 1792 +++ .../program/actions/settings/prefs_edit.php | 96 + .../program/actions/settings/prefs_save.php | 298 + .../actions/settings/response_create.php | 22 + .../actions/settings/response_delete.php | 50 + .../actions/settings/response_edit.php | 124 + .../program/actions/settings/response_get.php | 59 + .../actions/settings/response_save.php | 112 + .../program/actions/settings/responses.php | 73 + .../mails/program/actions/settings/upload.php | 122 + .../actions/settings/upload_display.php | 49 + ruty/mails/program/actions/utils/error.php | 146 + .../mails/program/actions/utils/html2text.php | 41 + .../mails/program/actions/utils/killcache.php | 66 + ruty/mails/program/actions/utils/modcss.php | 73 + .../mails/program/actions/utils/save_pref.php | 90 + ruty/mails/program/actions/utils/spell.php | 81 + .../program/actions/utils/spell_html.php | 76 + .../mails/program/actions/utils/text2html.php | 41 + ruty/mails/program/include/clisetup.php | 30 + ruty/mails/program/include/iniset.php | 139 + ruty/mails/program/include/rcmail.php | 2048 ++++ ruty/mails/program/include/rcmail_action.php | 1496 +++ .../include/rcmail_attachment_handler.php | 393 + .../program/include/rcmail_html_page.php | 87 + ruty/mails/program/include/rcmail_install.php | 932 ++ ruty/mails/program/include/rcmail_oauth.php | 588 + ruty/mails/program/include/rcmail_output.php | 150 + .../program/include/rcmail_output_cli.php | 82 + .../program/include/rcmail_output_html.php | 2782 +++++ .../program/include/rcmail_output_json.php | 280 + .../program/include/rcmail_resend_mail.php | 196 + .../mails/program/include/rcmail_sendmail.php | 1764 +++ .../include/rcmail_string_replacer.php | 66 + ruty/mails/program/include/rcmail_utils.php | 375 + ruty/mails/program/js/app.js | 10230 ++++++++++++++++ ruty/mails/program/js/app.min.js | 35 + ruty/mails/program/js/common.js | 810 ++ ruty/mails/program/js/common.min.js | 28 + ruty/mails/program/js/editor.js | 868 ++ ruty/mails/program/js/editor.min.js | 31 + ruty/mails/program/js/googiespell.js | 985 ++ ruty/mails/program/js/googiespell.min.js | 34 + ruty/mails/program/js/jquery.min.js | 36 + ruty/mails/program/js/jstz.min.js | 36 + ruty/mails/program/js/list.js | 1985 +++ ruty/mails/program/js/list.min.js | 33 + ruty/mails/program/js/publickey.js | 483 + ruty/mails/program/js/publickey.min.js | 28 + .../js/tinymce/icons/default/icons.min.js | 1 + ruty/mails/program/js/tinymce/langs/README.md | 3 + ruty/mails/program/js/tinymce/langs/ar.js | 420 + ruty/mails/program/js/tinymce/langs/az.js | 261 + ruty/mails/program/js/tinymce/langs/be.js | 261 + ruty/mails/program/js/tinymce/langs/bg_BG.js | 419 + ruty/mails/program/js/tinymce/langs/bs.js | 219 + ruty/mails/program/js/tinymce/langs/ca.js | 419 + ruty/mails/program/js/tinymce/langs/cs.js | 462 + ruty/mails/program/js/tinymce/langs/cs_CZ.js | 462 + ruty/mails/program/js/tinymce/langs/cy.js | 461 + ruty/mails/program/js/tinymce/langs/da.js | 462 + ruty/mails/program/js/tinymce/langs/de.js | 462 + ruty/mails/program/js/tinymce/langs/de_AT.js | 261 + ruty/mails/program/js/tinymce/langs/el.js | 261 + ruty/mails/program/js/tinymce/langs/en_CA.js | 261 + ruty/mails/program/js/tinymce/langs/en_GB.js | 261 + ruty/mails/program/js/tinymce/langs/eo.js | 415 + ruty/mails/program/js/tinymce/langs/es.js | 419 + ruty/mails/program/js/tinymce/langs/es_MX.js | 419 + ruty/mails/program/js/tinymce/langs/et.js | 419 + ruty/mails/program/js/tinymce/langs/eu.js | 419 + ruty/mails/program/js/tinymce/langs/fa.js | 462 + ruty/mails/program/js/tinymce/langs/fa_IR.js | 420 + ruty/mails/program/js/tinymce/langs/fi.js | 419 + ruty/mails/program/js/tinymce/langs/fo.js | 219 + ruty/mails/program/js/tinymce/langs/fr_CH.js | 219 + ruty/mails/program/js/tinymce/langs/fr_FR.js | 462 + ruty/mails/program/js/tinymce/langs/ga.js | 261 + ruty/mails/program/js/tinymce/langs/gd.js | 219 + ruty/mails/program/js/tinymce/langs/gl.js | 388 + ruty/mails/program/js/tinymce/langs/he_IL.js | 420 + ruty/mails/program/js/tinymce/langs/hi_IN.js | 219 + ruty/mails/program/js/tinymce/langs/hr.js | 418 + ruty/mails/program/js/tinymce/langs/hu_HU.js | 462 + ruty/mails/program/js/tinymce/langs/hy.js | 419 + ruty/mails/program/js/tinymce/langs/id.js | 419 + ruty/mails/program/js/tinymce/langs/is_IS.js | 415 + ruty/mails/program/js/tinymce/langs/it.js | 419 + ruty/mails/program/js/tinymce/langs/ja.js | 462 + ruty/mails/program/js/tinymce/langs/ka_GE.js | 230 + ruty/mails/program/js/tinymce/langs/kab.js | 419 + ruty/mails/program/js/tinymce/langs/km_KH.js | 253 + ruty/mails/program/js/tinymce/langs/ko_KR.js | 419 + ruty/mails/program/js/tinymce/langs/ku.js | 415 + ruty/mails/program/js/tinymce/langs/ku_IQ.js | 261 + ruty/mails/program/js/tinymce/langs/lb.js | 219 + ruty/mails/program/js/tinymce/langs/lt.js | 419 + ruty/mails/program/js/tinymce/langs/lv.js | 260 + ruty/mails/program/js/tinymce/langs/mk_MK.js | 219 + ruty/mails/program/js/tinymce/langs/ml_IN.js | 261 + ruty/mails/program/js/tinymce/langs/nb_NO.js | 462 + ruty/mails/program/js/tinymce/langs/nl.js | 462 + ruty/mails/program/js/tinymce/langs/oc.js | 415 + ruty/mails/program/js/tinymce/langs/pl.js | 462 + ruty/mails/program/js/tinymce/langs/pt_BR.js | 462 + ruty/mails/program/js/tinymce/langs/pt_PT.js | 462 + ruty/mails/program/js/tinymce/langs/ro.js | 461 + ruty/mails/program/js/tinymce/langs/ru.js | 462 + ruty/mails/program/js/tinymce/langs/sk.js | 418 + ruty/mails/program/js/tinymce/langs/sl_SI.js | 388 + ruty/mails/program/js/tinymce/langs/sv_SE.js | 419 + ruty/mails/program/js/tinymce/langs/ta.js | 462 + ruty/mails/program/js/tinymce/langs/ta_IN.js | 462 + ruty/mails/program/js/tinymce/langs/tg.js | 415 + ruty/mails/program/js/tinymce/langs/th_TH.js | 462 + ruty/mails/program/js/tinymce/langs/tr.js | 419 + ruty/mails/program/js/tinymce/langs/tr_TR.js | 462 + ruty/mails/program/js/tinymce/langs/tt.js | 219 + ruty/mails/program/js/tinymce/langs/ug.js | 462 + ruty/mails/program/js/tinymce/langs/uk.js | 419 + ruty/mails/program/js/tinymce/langs/uk_UA.js | 261 + ruty/mails/program/js/tinymce/langs/vi.js | 419 + ruty/mails/program/js/tinymce/langs/vi_VN.js | 260 + ruty/mails/program/js/tinymce/langs/zh_CN.js | 462 + ruty/mails/program/js/tinymce/langs/zh_TW.js | 419 + .../js/tinymce/plugins/advlist/plugin.min.js | 9 + .../js/tinymce/plugins/anchor/plugin.min.js | 9 + .../js/tinymce/plugins/autolink/plugin.min.js | 9 + .../tinymce/plugins/autoresize/plugin.min.js | 9 + .../js/tinymce/plugins/autosave/plugin.min.js | 9 + .../js/tinymce/plugins/bbcode/plugin.min.js | 9 + .../js/tinymce/plugins/charmap/plugin.min.js | 9 + .../js/tinymce/plugins/code/plugin.min.js | 9 + .../tinymce/plugins/codesample/plugin.min.js | 9 + .../tinymce/plugins/colorpicker/plugin.min.js | 9 + .../tinymce/plugins/contextmenu/plugin.min.js | 9 + .../plugins/directionality/plugin.min.js | 9 + .../plugins/emoticons/js/emojiimages.js | 9424 ++++++++++++++ .../plugins/emoticons/js/emojiimages.min.js | 3 + .../js/tinymce/plugins/emoticons/js/emojis.js | 9423 ++++++++++++++ .../plugins/emoticons/js/emojis.min.js | 2 + .../tinymce/plugins/emoticons/plugin.min.js | 9 + .../js/tinymce/plugins/fullpage/plugin.min.js | 9 + .../tinymce/plugins/fullscreen/plugin.min.js | 9 + .../js/tinymce/plugins/help/plugin.min.js | 9 + .../js/tinymce/plugins/hr/plugin.min.js | 9 + .../js/tinymce/plugins/image/plugin.min.js | 9 + .../tinymce/plugins/imagetools/plugin.min.js | 9 + .../tinymce/plugins/importcss/plugin.min.js | 9 + .../plugins/insertdatetime/plugin.min.js | 9 + .../plugins/legacyoutput/plugin.min.js | 9 + .../js/tinymce/plugins/link/plugin.min.js | 9 + .../js/tinymce/plugins/lists/plugin.min.js | 9 + .../js/tinymce/plugins/media/plugin.min.js | 9 + .../tinymce/plugins/nonbreaking/plugin.min.js | 9 + .../tinymce/plugins/noneditable/plugin.min.js | 9 + .../tinymce/plugins/pagebreak/plugin.min.js | 9 + .../js/tinymce/plugins/paste/plugin.min.js | 9 + .../js/tinymce/plugins/preview/plugin.min.js | 9 + .../js/tinymce/plugins/print/plugin.min.js | 9 + .../tinymce/plugins/quickbars/plugin.min.js | 9 + .../js/tinymce/plugins/save/plugin.min.js | 9 + .../plugins/searchreplace/plugin.min.js | 9 + .../plugins/spellchecker/plugin.min.js | 9 + .../js/tinymce/plugins/tabfocus/plugin.min.js | 9 + .../js/tinymce/plugins/table/plugin.min.js | 9 + .../js/tinymce/plugins/template/plugin.min.js | 9 + .../tinymce/plugins/textcolor/plugin.min.js | 9 + .../tinymce/plugins/textpattern/plugin.min.js | 9 + .../js/tinymce/plugins/toc/plugin.min.js | 9 + .../plugins/visualblocks/plugin.min.js | 9 + .../tinymce/plugins/visualchars/plugin.min.js | 9 + .../tinymce/plugins/wordcount/plugin.min.js | 9 + .../skins/content/dark/content.min.css | 7 + .../skins/content/default/content.min.css | 7 + .../skins/content/document/content.min.css | 7 + .../skins/content/writer/content.min.css | 7 + .../ui/oxide-dark/content.inline.min.css | 7 + .../skins/ui/oxide-dark/content.min.css | 7 + .../ui/oxide-dark/content.mobile.min.css | 7 + .../ui/oxide-dark/fonts/tinymce-mobile.woff | Bin 0 -> 4624 bytes .../tinymce/skins/ui/oxide-dark/skin.min.css | 7 + .../skins/ui/oxide-dark/skin.mobile.min.css | 7 + .../ui/oxide-dark/skin.shadowdom.min.css | 7 + .../skins/ui/oxide/content.inline.min.css | 7 + .../js/tinymce/skins/ui/oxide/content.min.css | 7 + .../skins/ui/oxide/content.mobile.min.css | 7 + .../skins/ui/oxide/fonts/tinymce-mobile.woff | Bin 0 -> 4624 bytes .../js/tinymce/skins/ui/oxide/skin.min.css | 7 + .../skins/ui/oxide/skin.mobile.min.css | 7 + .../skins/ui/oxide/skin.shadowdom.min.css | 7 + .../js/tinymce/themes/silver/theme.min.js | 9 + ruty/mails/program/js/tinymce/tinymce.min.js | 9 + ruty/mails/program/js/treelist.js | 1312 ++ ruty/mails/program/js/treelist.min.js | 31 + ruty/mails/program/lib/Roundcube/README.md | 105 + .../mails/program/lib/Roundcube/bootstrap.php | 450 + .../mails/program/lib/Roundcube/cache/apc.php | 138 + ruty/mails/program/lib/Roundcube/cache/db.php | 237 + .../program/lib/Roundcube/cache/memcache.php | 210 + .../program/lib/Roundcube/cache/memcached.php | 210 + .../program/lib/Roundcube/cache/redis.php | 261 + ruty/mails/program/lib/Roundcube/db/mssql.php | 185 + ruty/mails/program/lib/Roundcube/db/mysql.php | 245 + .../mails/program/lib/Roundcube/db/oracle.php | 631 + ruty/mails/program/lib/Roundcube/db/param.php | 58 + ruty/mails/program/lib/Roundcube/db/pgsql.php | 336 + .../mails/program/lib/Roundcube/db/sqlite.php | 171 + .../mails/program/lib/Roundcube/db/sqlsrv.php | 101 + ruty/mails/program/lib/Roundcube/html.php | 1015 ++ ruty/mails/program/lib/Roundcube/rcube.php | 1866 +++ .../lib/Roundcube/rcube_addressbook.php | 890 ++ .../program/lib/Roundcube/rcube_addresses.php | 412 + .../lib/Roundcube/rcube_base_replacer.php | 123 + .../program/lib/Roundcube/rcube_browser.php | 103 + .../program/lib/Roundcube/rcube_cache.php | 638 + .../program/lib/Roundcube/rcube_charset.php | 603 + .../program/lib/Roundcube/rcube_config.php | 944 ++ .../program/lib/Roundcube/rcube_contacts.php | 1095 ++ .../lib/Roundcube/rcube_content_filter.php | 59 + .../program/lib/Roundcube/rcube_csv2vcard.php | 695 ++ ruty/mails/program/lib/Roundcube/rcube_db.php | 1511 +++ .../program/lib/Roundcube/rcube_enriched.php | 160 + .../program/lib/Roundcube/rcube_html2text.php | 814 ++ .../program/lib/Roundcube/rcube_image.php | 514 + .../program/lib/Roundcube/rcube_imap.php | 4698 +++++++ .../lib/Roundcube/rcube_imap_cache.php | 1264 ++ .../lib/Roundcube/rcube_imap_generic.php | 4268 +++++++ .../lib/Roundcube/rcube_imap_search.php | 282 + .../program/lib/Roundcube/rcube_ldap.php | 2320 ++++ .../lib/Roundcube/rcube_ldap_generic.php | 356 + .../program/lib/Roundcube/rcube_message.php | 1248 ++ .../lib/Roundcube/rcube_message_header.php | 430 + .../lib/Roundcube/rcube_message_part.php | 130 + .../program/lib/Roundcube/rcube_mime.php | 992 ++ .../lib/Roundcube/rcube_mime_decode.php | 425 + .../program/lib/Roundcube/rcube_output.php | 374 + .../program/lib/Roundcube/rcube_plugin.php | 446 + .../lib/Roundcube/rcube_plugin_api.php | 809 ++ .../lib/Roundcube/rcube_result_index.php | 446 + .../Roundcube/rcube_result_multifolder.php | 369 + .../lib/Roundcube/rcube_result_set.php | 146 + .../lib/Roundcube/rcube_result_thread.php | 699 ++ .../program/lib/Roundcube/rcube_session.php | 748 ++ .../program/lib/Roundcube/rcube_smtp.php | 606 + .../lib/Roundcube/rcube_spellchecker.php | 440 + .../lib/Roundcube/rcube_spoofchecker.php | 73 + .../program/lib/Roundcube/rcube_storage.php | 1019 ++ .../lib/Roundcube/rcube_string_replacer.php | 345 + .../program/lib/Roundcube/rcube_text2html.php | 343 + .../lib/Roundcube/rcube_tnef_decoder.php | 797 ++ .../program/lib/Roundcube/rcube_user.php | 1043 ++ .../program/lib/Roundcube/rcube_utils.php | 1687 +++ .../program/lib/Roundcube/rcube_vcard.php | 1075 ++ .../program/lib/Roundcube/rcube_washtml.php | 1015 ++ .../program/lib/Roundcube/session/db.php | 208 + .../lib/Roundcube/session/memcache.php | 200 + .../lib/Roundcube/session/memcached.php | 199 + .../program/lib/Roundcube/session/php.php | 75 + .../program/lib/Roundcube/session/redis.php | 227 + .../lib/Roundcube/spellchecker/atd.php | 212 + .../lib/Roundcube/spellchecker/enchant.php | 186 + .../lib/Roundcube/spellchecker/engine.php | 91 + .../lib/Roundcube/spellchecker/googie.php | 170 + .../lib/Roundcube/spellchecker/pspell.php | 191 + ruty/mails/program/localization/ar/labels.inc | 523 + .../program/localization/ar/messages.inc | 151 + .../program/localization/ar/timezones.inc | 163 + .../program/localization/ar_SA/labels.inc | 539 + .../program/localization/ar_SA/messages.inc | 228 + .../program/localization/ar_SA/timezones.inc | 110 + .../mails/program/localization/ast/labels.inc | 556 + .../program/localization/ast/messages.inc | 167 + .../program/localization/az_AZ/labels.inc | 525 + .../program/localization/az_AZ/messages.inc | 163 + .../program/localization/be_BE/labels.inc | 714 ++ .../program/localization/be_BE/messages.inc | 232 + .../mails/program/localization/ber/labels.inc | 17 + .../program/localization/bg_BG/labels.inc | 714 ++ .../program/localization/bg_BG/messages.inc | 232 + .../program/localization/bg_BG/timezones.inc | 455 + .../program/localization/bn_BD/labels.inc | 258 + .../program/localization/bn_BD/messages.inc | 75 + ruty/mails/program/localization/br/labels.inc | 606 + .../program/localization/br/messages.inc | 196 + .../program/localization/bs_BA/labels.inc | 584 + .../program/localization/bs_BA/messages.inc | 177 + .../program/localization/ca_ES/labels.inc | 714 ++ .../program/localization/ca_ES/messages.inc | 232 + .../program/localization/ca_ES/timezones.inc | 455 + .../program/localization/cs_CZ/labels.inc | 714 ++ .../program/localization/cs_CZ/messages.inc | 232 + .../program/localization/cs_CZ/timezones.inc | 455 + .../program/localization/cy_GB/labels.inc | 714 ++ .../program/localization/cy_GB/messages.inc | 232 + .../program/localization/da_DK/labels.inc | 687 ++ .../program/localization/da_DK/messages.inc | 210 + .../program/localization/da_DK/timezones.inc | 28 + .../program/localization/de_CH/csv2vcard.inc | 99 + .../program/localization/de_CH/labels.inc | 714 ++ .../program/localization/de_CH/messages.inc | 233 + .../program/localization/de_CH/timezones.inc | 95 + .../program/localization/de_DE/csv2vcard.inc | 101 + .../program/localization/de_DE/labels.inc | 707 ++ .../program/localization/de_DE/messages.inc | 232 + .../program/localization/de_DE/timezones.inc | 455 + .../program/localization/el_GR/labels.inc | 714 ++ .../program/localization/el_GR/messages.inc | 232 + .../program/localization/el_GR/timezones.inc | 455 + .../program/localization/en_CA/labels.inc | 492 + .../program/localization/en_CA/messages.inc | 165 + .../program/localization/en_GB/labels.inc | 714 ++ .../program/localization/en_GB/messages.inc | 232 + .../program/localization/en_GB/timezones.inc | 455 + .../program/localization/en_US/csv2vcard.inc | 108 + .../program/localization/en_US/labels.inc | 782 ++ .../program/localization/en_US/messages.inc | 233 + .../program/localization/en_US/timezones.inc | 456 + ruty/mails/program/localization/eo/labels.inc | 342 + .../program/localization/eo/messages.inc | 78 + .../program/localization/es_419/labels.inc | 654 + .../program/localization/es_419/messages.inc | 167 + .../program/localization/es_419/timezones.inc | 17 + .../program/localization/es_AR/labels.inc | 713 ++ .../program/localization/es_AR/messages.inc | 232 + .../program/localization/es_AR/timezones.inc | 455 + .../program/localization/es_ES/csv2vcard.inc | 101 + .../program/localization/es_ES/labels.inc | 714 ++ .../program/localization/es_ES/messages.inc | 232 + .../program/localization/es_ES/timezones.inc | 455 + .../program/localization/et_EE/labels.inc | 698 ++ .../program/localization/et_EE/messages.inc | 213 + .../program/localization/et_EE/timezones.inc | 392 + .../program/localization/eu_ES/labels.inc | 716 ++ .../program/localization/eu_ES/messages.inc | 232 + .../program/localization/eu_ES/timezones.inc | 455 + .../program/localization/fa_AF/labels.inc | 282 + .../program/localization/fa_AF/messages.inc | 123 + .../program/localization/fa_IR/labels.inc | 603 + .../program/localization/fa_IR/messages.inc | 173 + .../program/localization/fa_IR/timezones.inc | 18 + .../program/localization/fi_FI/csv2vcard.inc | 49 + .../program/localization/fi_FI/labels.inc | 714 ++ .../program/localization/fi_FI/messages.inc | 232 + .../program/localization/fo_FO/labels.inc | 654 + .../program/localization/fo_FO/messages.inc | 201 + .../program/localization/fr_FR/csv2vcard.inc | 85 + .../program/localization/fr_FR/labels.inc | 715 ++ .../program/localization/fr_FR/messages.inc | 232 + .../program/localization/fr_FR/timezones.inc | 455 + .../program/localization/fy_NL/labels.inc | 608 + .../program/localization/fy_NL/messages.inc | 201 + .../program/localization/ga_IE/labels.inc | 537 + .../program/localization/ga_IE/messages.inc | 188 + .../program/localization/ga_IE/timezones.inc | 455 + .../program/localization/gl_ES/labels.inc | 553 + .../program/localization/gl_ES/messages.inc | 167 + .../program/localization/he_IL/labels.inc | 715 ++ .../program/localization/he_IL/messages.inc | 238 + .../program/localization/he_IL/timezones.inc | 455 + .../program/localization/hi_IN/labels.inc | 184 + .../program/localization/hi_IN/messages.inc | 65 + .../program/localization/hr_HR/labels.inc | 503 + .../program/localization/hr_HR/messages.inc | 214 + .../program/localization/hu_HU/labels.inc | 714 ++ .../program/localization/hu_HU/messages.inc | 232 + .../program/localization/hu_HU/timezones.inc | 455 + .../program/localization/hy_AM/labels.inc | 467 + .../program/localization/hy_AM/messages.inc | 141 + ruty/mails/program/localization/ia/labels.inc | 633 + .../program/localization/ia/messages.inc | 202 + .../program/localization/id_ID/labels.inc | 620 + .../program/localization/id_ID/messages.inc | 222 + .../program/localization/id_ID/timezones.inc | 455 + ruty/mails/program/localization/index.inc | 175 + .../program/localization/is_IS/labels.inc | 714 ++ .../program/localization/is_IS/messages.inc | 232 + .../program/localization/is_IS/timezones.inc | 455 + .../program/localization/it_IT/csv2vcard.inc | 101 + .../program/localization/it_IT/labels.inc | 714 ++ .../program/localization/it_IT/messages.inc | 232 + .../program/localization/ja_JP/labels.inc | 714 ++ .../program/localization/ja_JP/messages.inc | 232 + .../program/localization/ja_JP/timezones.inc | 455 + .../program/localization/ka_GE/labels.inc | 423 + .../program/localization/ka_GE/messages.inc | 162 + .../mails/program/localization/kab/labels.inc | 23 + .../program/localization/kab/messages.inc | 17 + .../program/localization/km_KH/labels.inc | 429 + .../program/localization/km_KH/messages.inc | 132 + .../program/localization/kn_IN/labels.inc | 166 + .../program/localization/kn_IN/messages.inc | 49 + .../program/localization/ko_KR/labels.inc | 714 ++ .../program/localization/ko_KR/messages.inc | 233 + .../program/localization/ko_KR/timezones.inc | 455 + ruty/mails/program/localization/ku/labels.inc | 365 + .../program/localization/ku/messages.inc | 97 + .../program/localization/ku_IQ/labels.inc | 182 + .../program/localization/ku_IQ/messages.inc | 18 + .../program/localization/ku_IQ/timezones.inc | 455 + .../program/localization/lb_LU/labels.inc | 503 + .../program/localization/lb_LU/messages.inc | 170 + .../program/localization/lb_LU/timezones.inc | 17 + .../program/localization/lt_LT/labels.inc | 701 ++ .../program/localization/lt_LT/messages.inc | 228 + .../program/localization/lt_LT/timezones.inc | 162 + .../program/localization/lv_LV/labels.inc | 709 ++ .../program/localization/lv_LV/messages.inc | 232 + .../program/localization/lv_LV/timezones.inc | 157 + .../program/localization/mk_MK/labels.inc | 612 + .../program/localization/mk_MK/messages.inc | 201 + .../program/localization/mk_MK/timezones.inc | 455 + .../program/localization/ml_IN/labels.inc | 281 + .../program/localization/ml_IN/messages.inc | 39 + .../program/localization/mn_MN/labels.inc | 58 + .../program/localization/mn_MN/messages.inc | 46 + .../program/localization/mr_IN/labels.inc | 373 + .../program/localization/mr_IN/messages.inc | 101 + .../program/localization/ms_MY/labels.inc | 273 + .../program/localization/ms_MY/messages.inc | 66 + .../program/localization/nb_NO/labels.inc | 714 ++ .../program/localization/nb_NO/messages.inc | 232 + .../program/localization/nb_NO/timezones.inc | 19 + .../program/localization/ne_NP/labels.inc | 203 + .../program/localization/ne_NP/messages.inc | 62 + .../program/localization/nl_BE/labels.inc | 486 + .../program/localization/nl_BE/messages.inc | 179 + .../program/localization/nl_NL/labels.inc | 704 ++ .../program/localization/nl_NL/messages.inc | 229 + .../program/localization/nl_NL/timezones.inc | 455 + .../program/localization/nn_NO/labels.inc | 459 + .../program/localization/nn_NO/messages.inc | 146 + .../program/localization/pl_PL/csv2vcard.inc | 55 + .../program/localization/pl_PL/labels.inc | 711 ++ .../program/localization/pl_PL/messages.inc | 232 + .../program/localization/pl_PL/timezones.inc | 375 + ruty/mails/program/localization/ps/labels.inc | 244 + .../program/localization/ps/messages.inc | 75 + .../program/localization/pt_BR/csv2vcard.inc | 101 + .../program/localization/pt_BR/labels.inc | 714 ++ .../program/localization/pt_BR/messages.inc | 232 + .../program/localization/pt_BR/timezones.inc | 455 + .../program/localization/pt_PT/labels.inc | 715 ++ .../program/localization/pt_PT/messages.inc | 233 + .../program/localization/pt_PT/timezones.inc | 455 + .../program/localization/ro_RO/labels.inc | 600 + .../program/localization/ro_RO/messages.inc | 202 + .../program/localization/ro_RO/timezones.inc | 64 + .../program/localization/ru_RU/csv2vcard.inc | 45 + .../program/localization/ru_RU/labels.inc | 714 ++ .../program/localization/ru_RU/messages.inc | 232 + .../program/localization/ru_RU/timezones.inc | 455 + .../program/localization/si_LK/labels.inc | 314 + .../program/localization/si_LK/messages.inc | 80 + .../program/localization/sk_SK/csv2vcard.inc | 84 + .../program/localization/sk_SK/labels.inc | 714 ++ .../program/localization/sk_SK/messages.inc | 232 + .../program/localization/sk_SK/timezones.inc | 455 + .../program/localization/sl_SI/labels.inc | 685 ++ .../program/localization/sl_SI/messages.inc | 206 + .../program/localization/sl_SI/timezones.inc | 42 + .../program/localization/sq_AL/labels.inc | 712 ++ .../program/localization/sq_AL/messages.inc | 232 + .../program/localization/sq_AL/timezones.inc | 455 + .../program/localization/sr_CS/labels.inc | 561 + .../program/localization/sr_CS/messages.inc | 224 + .../program/localization/sv_SE/labels.inc | 714 ++ .../program/localization/sv_SE/messages.inc | 232 + .../program/localization/sv_SE/timezones.inc | 455 + .../program/localization/ta_IN/labels.inc | 263 + .../program/localization/ta_IN/messages.inc | 90 + .../program/localization/th_TH/labels.inc | 480 + .../program/localization/th_TH/messages.inc | 78 + ruty/mails/program/localization/ti/labels.inc | 70 + .../program/localization/ti/messages.inc | 22 + .../program/localization/tr_TR/labels.inc | 714 ++ .../program/localization/tr_TR/messages.inc | 232 + .../program/localization/tr_TR/timezones.inc | 455 + .../mails/program/localization/tzl/labels.inc | 118 + .../program/localization/tzl/messages.inc | 17 + ruty/mails/program/localization/ug/labels.inc | 714 ++ .../program/localization/ug/messages.inc | 232 + .../program/localization/ug/timezones.inc | 455 + .../program/localization/uk_UA/labels.inc | 611 + .../program/localization/uk_UA/messages.inc | 196 + .../program/localization/ur_PK/labels.inc | 109 + ruty/mails/program/localization/uz/labels.inc | 625 + .../program/localization/uz/messages.inc | 201 + .../program/localization/uz/timezones.inc | 35 + .../program/localization/vi_VN/labels.inc | 531 + .../program/localization/vi_VN/messages.inc | 167 + .../program/localization/zh_CN/labels.inc | 673 + .../program/localization/zh_CN/messages.inc | 215 + .../program/localization/zh_TW/csv2vcard.inc | 90 + .../program/localization/zh_TW/labels.inc | 711 ++ .../program/localization/zh_TW/messages.inc | 228 + .../program/localization/zh_TW/timezones.inc | 455 + ruty/mails/program/resources/blank.gif | Bin 0 -> 54 bytes ruty/mails/program/resources/blank.tiff | Bin 0 -> 270 bytes ruty/mails/program/resources/blank.webp | Bin 0 -> 86 bytes ruty/mails/program/resources/blocked.gif | Bin 0 -> 118 bytes ruty/mails/program/resources/dummy.pdf | Bin 0 -> 1058 bytes ruty/mails/program/resources/error.html | 28 + .../program/resources/tinymce/browser.css | 82 + .../program/resources/tinymce/content.css | 24 + .../mails/program/resources/tinymce/video.png | Bin 0 -> 463 bytes ruty/mails/public_html/.htaccess | 1 + ruty/mails/public_html/index.php | 26 + ruty/mails/public_html/plugins | 1 + ruty/mails/public_html/program/js | 1 + ruty/mails/public_html/program/resources | 1 + ruty/mails/public_html/skins | 1 + 618 files changed, 225414 insertions(+) create mode 100644 ruty/mails/bin/cleandb.sh create mode 100644 ruty/mails/bin/cssshrink.sh create mode 100644 ruty/mails/bin/decrypt.sh create mode 100644 ruty/mails/bin/deluser.sh create mode 100644 ruty/mails/bin/gc.sh create mode 100644 ruty/mails/bin/indexcontacts.sh create mode 100644 ruty/mails/bin/initdb.sh create mode 100644 ruty/mails/bin/installto.sh create mode 100644 ruty/mails/bin/jsshrink.sh create mode 100644 ruty/mails/bin/makedoc.sh create mode 100644 ruty/mails/bin/moduserprefs.sh create mode 100644 ruty/mails/bin/msgexport.sh create mode 100644 ruty/mails/bin/msgimport.sh create mode 100644 ruty/mails/bin/update.sh create mode 100644 ruty/mails/bin/updatecss.sh create mode 100644 ruty/mails/bin/updatedb.sh create mode 100644 ruty/mails/config/.htaccess create mode 100644 ruty/mails/config/config.inc.php create mode 100644 ruty/mails/config/config.inc.php.sample create mode 100644 ruty/mails/config/defaults.inc.php create mode 100644 ruty/mails/config/mimetypes.php create mode 100644 ruty/mails/installer/check.php create mode 100644 ruty/mails/installer/client.js create mode 100644 ruty/mails/installer/config.php create mode 100644 ruty/mails/installer/images/add.png create mode 100644 ruty/mails/installer/images/banner_gradient.gif create mode 100644 ruty/mails/installer/images/banner_schraffur.gif create mode 100644 ruty/mails/installer/images/delete.png create mode 100644 ruty/mails/installer/images/error.png create mode 100644 ruty/mails/installer/images/roundcube_logo.png create mode 100644 ruty/mails/installer/index.php create mode 100644 ruty/mails/installer/styles.css create mode 100644 ruty/mails/installer/test.php create mode 100644 ruty/mails/logs/.htaccess create mode 100644 ruty/mails/logs/errors.log create mode 100644 ruty/mails/program/actions/contacts/copy.php create mode 100644 ruty/mails/program/actions/contacts/delete.php create mode 100644 ruty/mails/program/actions/contacts/edit.php create mode 100644 ruty/mails/program/actions/contacts/export.php create mode 100644 ruty/mails/program/actions/contacts/group_addmembers.php create mode 100644 ruty/mails/program/actions/contacts/group_create.php create mode 100644 ruty/mails/program/actions/contacts/group_delete.php create mode 100644 ruty/mails/program/actions/contacts/group_delmembers.php create mode 100644 ruty/mails/program/actions/contacts/group_rename.php create mode 100644 ruty/mails/program/actions/contacts/import.php create mode 100644 ruty/mails/program/actions/contacts/index.php create mode 100644 ruty/mails/program/actions/contacts/list.php create mode 100644 ruty/mails/program/actions/contacts/mailto.php create mode 100644 ruty/mails/program/actions/contacts/move.php create mode 100644 ruty/mails/program/actions/contacts/photo.php create mode 100644 ruty/mails/program/actions/contacts/print.php create mode 100644 ruty/mails/program/actions/contacts/qrcode.php create mode 100644 ruty/mails/program/actions/contacts/save.php create mode 100644 ruty/mails/program/actions/contacts/search.php create mode 100644 ruty/mails/program/actions/contacts/search_create.php create mode 100644 ruty/mails/program/actions/contacts/search_delete.php create mode 100644 ruty/mails/program/actions/contacts/show.php create mode 100644 ruty/mails/program/actions/contacts/undo.php create mode 100644 ruty/mails/program/actions/contacts/upload_photo.php create mode 100644 ruty/mails/program/actions/login/oauth.php create mode 100644 ruty/mails/program/actions/mail/addcontact.php create mode 100644 ruty/mails/program/actions/mail/attachment_delete.php create mode 100644 ruty/mails/program/actions/mail/attachment_display.php create mode 100644 ruty/mails/program/actions/mail/attachment_rename.php create mode 100644 ruty/mails/program/actions/mail/attachment_upload.php create mode 100644 ruty/mails/program/actions/mail/autocomplete.php create mode 100644 ruty/mails/program/actions/mail/bounce.php create mode 100644 ruty/mails/program/actions/mail/check_recent.php create mode 100644 ruty/mails/program/actions/mail/compose.php create mode 100644 ruty/mails/program/actions/mail/copy.php create mode 100644 ruty/mails/program/actions/mail/delete.php create mode 100644 ruty/mails/program/actions/mail/folder_expunge.php create mode 100644 ruty/mails/program/actions/mail/folder_purge.php create mode 100644 ruty/mails/program/actions/mail/get.php create mode 100644 ruty/mails/program/actions/mail/getunread.php create mode 100644 ruty/mails/program/actions/mail/group_expand.php create mode 100644 ruty/mails/program/actions/mail/headers.php create mode 100644 ruty/mails/program/actions/mail/import.php create mode 100644 ruty/mails/program/actions/mail/index.php create mode 100644 ruty/mails/program/actions/mail/list.php create mode 100644 ruty/mails/program/actions/mail/list_contacts.php create mode 100644 ruty/mails/program/actions/mail/mark.php create mode 100644 ruty/mails/program/actions/mail/move.php create mode 100644 ruty/mails/program/actions/mail/pagenav.php create mode 100644 ruty/mails/program/actions/mail/search.php create mode 100644 ruty/mails/program/actions/mail/search_contacts.php create mode 100644 ruty/mails/program/actions/mail/send.php create mode 100644 ruty/mails/program/actions/mail/sendmdn.php create mode 100644 ruty/mails/program/actions/mail/show.php create mode 100644 ruty/mails/program/actions/mail/viewsource.php create mode 100644 ruty/mails/program/actions/settings/about.php create mode 100644 ruty/mails/program/actions/settings/folder_create.php create mode 100644 ruty/mails/program/actions/settings/folder_delete.php create mode 100644 ruty/mails/program/actions/settings/folder_edit.php create mode 100644 ruty/mails/program/actions/settings/folder_purge.php create mode 100644 ruty/mails/program/actions/settings/folder_rename.php create mode 100644 ruty/mails/program/actions/settings/folder_save.php create mode 100644 ruty/mails/program/actions/settings/folder_size.php create mode 100644 ruty/mails/program/actions/settings/folder_subscribe.php create mode 100644 ruty/mails/program/actions/settings/folder_unsubscribe.php create mode 100644 ruty/mails/program/actions/settings/folders.php create mode 100644 ruty/mails/program/actions/settings/identities.php create mode 100644 ruty/mails/program/actions/settings/identity_create.php create mode 100644 ruty/mails/program/actions/settings/identity_delete.php create mode 100644 ruty/mails/program/actions/settings/identity_edit.php create mode 100644 ruty/mails/program/actions/settings/identity_save.php create mode 100644 ruty/mails/program/actions/settings/index.php create mode 100644 ruty/mails/program/actions/settings/prefs_edit.php create mode 100644 ruty/mails/program/actions/settings/prefs_save.php create mode 100644 ruty/mails/program/actions/settings/response_create.php create mode 100644 ruty/mails/program/actions/settings/response_delete.php create mode 100644 ruty/mails/program/actions/settings/response_edit.php create mode 100644 ruty/mails/program/actions/settings/response_get.php create mode 100644 ruty/mails/program/actions/settings/response_save.php create mode 100644 ruty/mails/program/actions/settings/responses.php create mode 100644 ruty/mails/program/actions/settings/upload.php create mode 100644 ruty/mails/program/actions/settings/upload_display.php create mode 100644 ruty/mails/program/actions/utils/error.php create mode 100644 ruty/mails/program/actions/utils/html2text.php create mode 100644 ruty/mails/program/actions/utils/killcache.php create mode 100644 ruty/mails/program/actions/utils/modcss.php create mode 100644 ruty/mails/program/actions/utils/save_pref.php create mode 100644 ruty/mails/program/actions/utils/spell.php create mode 100644 ruty/mails/program/actions/utils/spell_html.php create mode 100644 ruty/mails/program/actions/utils/text2html.php create mode 100644 ruty/mails/program/include/clisetup.php create mode 100644 ruty/mails/program/include/iniset.php create mode 100644 ruty/mails/program/include/rcmail.php create mode 100644 ruty/mails/program/include/rcmail_action.php create mode 100644 ruty/mails/program/include/rcmail_attachment_handler.php create mode 100644 ruty/mails/program/include/rcmail_html_page.php create mode 100644 ruty/mails/program/include/rcmail_install.php create mode 100644 ruty/mails/program/include/rcmail_oauth.php create mode 100644 ruty/mails/program/include/rcmail_output.php create mode 100644 ruty/mails/program/include/rcmail_output_cli.php create mode 100644 ruty/mails/program/include/rcmail_output_html.php create mode 100644 ruty/mails/program/include/rcmail_output_json.php create mode 100644 ruty/mails/program/include/rcmail_resend_mail.php create mode 100644 ruty/mails/program/include/rcmail_sendmail.php create mode 100644 ruty/mails/program/include/rcmail_string_replacer.php create mode 100644 ruty/mails/program/include/rcmail_utils.php create mode 100644 ruty/mails/program/js/app.js create mode 100644 ruty/mails/program/js/app.min.js create mode 100644 ruty/mails/program/js/common.js create mode 100644 ruty/mails/program/js/common.min.js create mode 100644 ruty/mails/program/js/editor.js create mode 100644 ruty/mails/program/js/editor.min.js create mode 100644 ruty/mails/program/js/googiespell.js create mode 100644 ruty/mails/program/js/googiespell.min.js create mode 100644 ruty/mails/program/js/jquery.min.js create mode 100644 ruty/mails/program/js/jstz.min.js create mode 100644 ruty/mails/program/js/list.js create mode 100644 ruty/mails/program/js/list.min.js create mode 100644 ruty/mails/program/js/publickey.js create mode 100644 ruty/mails/program/js/publickey.min.js create mode 100644 ruty/mails/program/js/tinymce/icons/default/icons.min.js create mode 100644 ruty/mails/program/js/tinymce/langs/README.md create mode 100644 ruty/mails/program/js/tinymce/langs/ar.js create mode 100644 ruty/mails/program/js/tinymce/langs/az.js create mode 100644 ruty/mails/program/js/tinymce/langs/be.js create mode 100644 ruty/mails/program/js/tinymce/langs/bg_BG.js create mode 100644 ruty/mails/program/js/tinymce/langs/bs.js create mode 100644 ruty/mails/program/js/tinymce/langs/ca.js create mode 100644 ruty/mails/program/js/tinymce/langs/cs.js create mode 100644 ruty/mails/program/js/tinymce/langs/cs_CZ.js create mode 100644 ruty/mails/program/js/tinymce/langs/cy.js create mode 100644 ruty/mails/program/js/tinymce/langs/da.js create mode 100644 ruty/mails/program/js/tinymce/langs/de.js create mode 100644 ruty/mails/program/js/tinymce/langs/de_AT.js create mode 100644 ruty/mails/program/js/tinymce/langs/el.js create mode 100644 ruty/mails/program/js/tinymce/langs/en_CA.js create mode 100644 ruty/mails/program/js/tinymce/langs/en_GB.js create mode 100644 ruty/mails/program/js/tinymce/langs/eo.js create mode 100644 ruty/mails/program/js/tinymce/langs/es.js create mode 100644 ruty/mails/program/js/tinymce/langs/es_MX.js create mode 100644 ruty/mails/program/js/tinymce/langs/et.js create mode 100644 ruty/mails/program/js/tinymce/langs/eu.js create mode 100644 ruty/mails/program/js/tinymce/langs/fa.js create mode 100644 ruty/mails/program/js/tinymce/langs/fa_IR.js create mode 100644 ruty/mails/program/js/tinymce/langs/fi.js create mode 100644 ruty/mails/program/js/tinymce/langs/fo.js create mode 100644 ruty/mails/program/js/tinymce/langs/fr_CH.js create mode 100644 ruty/mails/program/js/tinymce/langs/fr_FR.js create mode 100644 ruty/mails/program/js/tinymce/langs/ga.js create mode 100644 ruty/mails/program/js/tinymce/langs/gd.js create mode 100644 ruty/mails/program/js/tinymce/langs/gl.js create mode 100644 ruty/mails/program/js/tinymce/langs/he_IL.js create mode 100644 ruty/mails/program/js/tinymce/langs/hi_IN.js create mode 100644 ruty/mails/program/js/tinymce/langs/hr.js create mode 100644 ruty/mails/program/js/tinymce/langs/hu_HU.js create mode 100644 ruty/mails/program/js/tinymce/langs/hy.js create mode 100644 ruty/mails/program/js/tinymce/langs/id.js create mode 100644 ruty/mails/program/js/tinymce/langs/is_IS.js create mode 100644 ruty/mails/program/js/tinymce/langs/it.js create mode 100644 ruty/mails/program/js/tinymce/langs/ja.js create mode 100644 ruty/mails/program/js/tinymce/langs/ka_GE.js create mode 100644 ruty/mails/program/js/tinymce/langs/kab.js create mode 100644 ruty/mails/program/js/tinymce/langs/km_KH.js create mode 100644 ruty/mails/program/js/tinymce/langs/ko_KR.js create mode 100644 ruty/mails/program/js/tinymce/langs/ku.js create mode 100644 ruty/mails/program/js/tinymce/langs/ku_IQ.js create mode 100644 ruty/mails/program/js/tinymce/langs/lb.js create mode 100644 ruty/mails/program/js/tinymce/langs/lt.js create mode 100644 ruty/mails/program/js/tinymce/langs/lv.js create mode 100644 ruty/mails/program/js/tinymce/langs/mk_MK.js create mode 100644 ruty/mails/program/js/tinymce/langs/ml_IN.js create mode 100644 ruty/mails/program/js/tinymce/langs/nb_NO.js create mode 100644 ruty/mails/program/js/tinymce/langs/nl.js create mode 100644 ruty/mails/program/js/tinymce/langs/oc.js create mode 100644 ruty/mails/program/js/tinymce/langs/pl.js create mode 100644 ruty/mails/program/js/tinymce/langs/pt_BR.js create mode 100644 ruty/mails/program/js/tinymce/langs/pt_PT.js create mode 100644 ruty/mails/program/js/tinymce/langs/ro.js create mode 100644 ruty/mails/program/js/tinymce/langs/ru.js create mode 100644 ruty/mails/program/js/tinymce/langs/sk.js create mode 100644 ruty/mails/program/js/tinymce/langs/sl_SI.js create mode 100644 ruty/mails/program/js/tinymce/langs/sv_SE.js create mode 100644 ruty/mails/program/js/tinymce/langs/ta.js create mode 100644 ruty/mails/program/js/tinymce/langs/ta_IN.js create mode 100644 ruty/mails/program/js/tinymce/langs/tg.js create mode 100644 ruty/mails/program/js/tinymce/langs/th_TH.js create mode 100644 ruty/mails/program/js/tinymce/langs/tr.js create mode 100644 ruty/mails/program/js/tinymce/langs/tr_TR.js create mode 100644 ruty/mails/program/js/tinymce/langs/tt.js create mode 100644 ruty/mails/program/js/tinymce/langs/ug.js create mode 100644 ruty/mails/program/js/tinymce/langs/uk.js create mode 100644 ruty/mails/program/js/tinymce/langs/uk_UA.js create mode 100644 ruty/mails/program/js/tinymce/langs/vi.js create mode 100644 ruty/mails/program/js/tinymce/langs/vi_VN.js create mode 100644 ruty/mails/program/js/tinymce/langs/zh_CN.js create mode 100644 ruty/mails/program/js/tinymce/langs/zh_TW.js create mode 100644 ruty/mails/program/js/tinymce/plugins/advlist/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/anchor/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/autolink/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/autoresize/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/autosave/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/bbcode/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/charmap/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/code/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/codesample/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/colorpicker/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/contextmenu/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/directionality/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/emoticons/js/emojiimages.js create mode 100644 ruty/mails/program/js/tinymce/plugins/emoticons/js/emojiimages.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/emoticons/js/emojis.js create mode 100644 ruty/mails/program/js/tinymce/plugins/emoticons/js/emojis.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/emoticons/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/fullpage/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/fullscreen/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/help/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/hr/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/image/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/imagetools/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/importcss/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/insertdatetime/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/legacyoutput/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/link/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/lists/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/media/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/nonbreaking/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/noneditable/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/pagebreak/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/paste/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/preview/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/print/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/quickbars/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/save/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/searchreplace/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/spellchecker/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/tabfocus/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/table/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/template/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/textcolor/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/textpattern/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/toc/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/visualblocks/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/visualchars/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/plugins/wordcount/plugin.min.js create mode 100644 ruty/mails/program/js/tinymce/skins/content/dark/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/content/default/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/content/document/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/content/writer/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/content.inline.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/content.mobile.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/skin.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/skin.mobile.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/content.inline.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/content.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/content.mobile.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/skin.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/skin.mobile.min.css create mode 100644 ruty/mails/program/js/tinymce/skins/ui/oxide/skin.shadowdom.min.css create mode 100644 ruty/mails/program/js/tinymce/themes/silver/theme.min.js create mode 100644 ruty/mails/program/js/tinymce/tinymce.min.js create mode 100644 ruty/mails/program/js/treelist.js create mode 100644 ruty/mails/program/js/treelist.min.js create mode 100644 ruty/mails/program/lib/Roundcube/README.md create mode 100644 ruty/mails/program/lib/Roundcube/bootstrap.php create mode 100644 ruty/mails/program/lib/Roundcube/cache/apc.php create mode 100644 ruty/mails/program/lib/Roundcube/cache/db.php create mode 100644 ruty/mails/program/lib/Roundcube/cache/memcache.php create mode 100644 ruty/mails/program/lib/Roundcube/cache/memcached.php create mode 100644 ruty/mails/program/lib/Roundcube/cache/redis.php create mode 100644 ruty/mails/program/lib/Roundcube/db/mssql.php create mode 100644 ruty/mails/program/lib/Roundcube/db/mysql.php create mode 100644 ruty/mails/program/lib/Roundcube/db/oracle.php create mode 100644 ruty/mails/program/lib/Roundcube/db/param.php create mode 100644 ruty/mails/program/lib/Roundcube/db/pgsql.php create mode 100644 ruty/mails/program/lib/Roundcube/db/sqlite.php create mode 100644 ruty/mails/program/lib/Roundcube/db/sqlsrv.php create mode 100644 ruty/mails/program/lib/Roundcube/html.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_addressbook.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_addresses.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_base_replacer.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_browser.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_cache.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_charset.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_config.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_contacts.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_content_filter.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_csv2vcard.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_db.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_enriched.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_html2text.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_image.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_imap.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_imap_cache.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_imap_generic.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_imap_search.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_ldap.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_ldap_generic.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_message.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_message_header.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_message_part.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_mime.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_mime_decode.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_output.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_plugin.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_plugin_api.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_result_index.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_result_multifolder.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_result_set.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_result_thread.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_session.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_smtp.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_spellchecker.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_spoofchecker.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_storage.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_string_replacer.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_text2html.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_tnef_decoder.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_user.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_utils.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_vcard.php create mode 100644 ruty/mails/program/lib/Roundcube/rcube_washtml.php create mode 100644 ruty/mails/program/lib/Roundcube/session/db.php create mode 100644 ruty/mails/program/lib/Roundcube/session/memcache.php create mode 100644 ruty/mails/program/lib/Roundcube/session/memcached.php create mode 100644 ruty/mails/program/lib/Roundcube/session/php.php create mode 100644 ruty/mails/program/lib/Roundcube/session/redis.php create mode 100644 ruty/mails/program/lib/Roundcube/spellchecker/atd.php create mode 100644 ruty/mails/program/lib/Roundcube/spellchecker/enchant.php create mode 100644 ruty/mails/program/lib/Roundcube/spellchecker/engine.php create mode 100644 ruty/mails/program/lib/Roundcube/spellchecker/googie.php create mode 100644 ruty/mails/program/lib/Roundcube/spellchecker/pspell.php create mode 100644 ruty/mails/program/localization/ar/labels.inc create mode 100644 ruty/mails/program/localization/ar/messages.inc create mode 100644 ruty/mails/program/localization/ar/timezones.inc create mode 100644 ruty/mails/program/localization/ar_SA/labels.inc create mode 100644 ruty/mails/program/localization/ar_SA/messages.inc create mode 100644 ruty/mails/program/localization/ar_SA/timezones.inc create mode 100644 ruty/mails/program/localization/ast/labels.inc create mode 100644 ruty/mails/program/localization/ast/messages.inc create mode 100644 ruty/mails/program/localization/az_AZ/labels.inc create mode 100644 ruty/mails/program/localization/az_AZ/messages.inc create mode 100644 ruty/mails/program/localization/be_BE/labels.inc create mode 100644 ruty/mails/program/localization/be_BE/messages.inc create mode 100644 ruty/mails/program/localization/ber/labels.inc create mode 100644 ruty/mails/program/localization/bg_BG/labels.inc create mode 100644 ruty/mails/program/localization/bg_BG/messages.inc create mode 100644 ruty/mails/program/localization/bg_BG/timezones.inc create mode 100644 ruty/mails/program/localization/bn_BD/labels.inc create mode 100644 ruty/mails/program/localization/bn_BD/messages.inc create mode 100644 ruty/mails/program/localization/br/labels.inc create mode 100644 ruty/mails/program/localization/br/messages.inc create mode 100644 ruty/mails/program/localization/bs_BA/labels.inc create mode 100644 ruty/mails/program/localization/bs_BA/messages.inc create mode 100644 ruty/mails/program/localization/ca_ES/labels.inc create mode 100644 ruty/mails/program/localization/ca_ES/messages.inc create mode 100644 ruty/mails/program/localization/ca_ES/timezones.inc create mode 100644 ruty/mails/program/localization/cs_CZ/labels.inc create mode 100644 ruty/mails/program/localization/cs_CZ/messages.inc create mode 100644 ruty/mails/program/localization/cs_CZ/timezones.inc create mode 100644 ruty/mails/program/localization/cy_GB/labels.inc create mode 100644 ruty/mails/program/localization/cy_GB/messages.inc create mode 100644 ruty/mails/program/localization/da_DK/labels.inc create mode 100644 ruty/mails/program/localization/da_DK/messages.inc create mode 100644 ruty/mails/program/localization/da_DK/timezones.inc create mode 100644 ruty/mails/program/localization/de_CH/csv2vcard.inc create mode 100644 ruty/mails/program/localization/de_CH/labels.inc create mode 100644 ruty/mails/program/localization/de_CH/messages.inc create mode 100644 ruty/mails/program/localization/de_CH/timezones.inc create mode 100644 ruty/mails/program/localization/de_DE/csv2vcard.inc create mode 100644 ruty/mails/program/localization/de_DE/labels.inc create mode 100644 ruty/mails/program/localization/de_DE/messages.inc create mode 100644 ruty/mails/program/localization/de_DE/timezones.inc create mode 100644 ruty/mails/program/localization/el_GR/labels.inc create mode 100644 ruty/mails/program/localization/el_GR/messages.inc create mode 100644 ruty/mails/program/localization/el_GR/timezones.inc create mode 100644 ruty/mails/program/localization/en_CA/labels.inc create mode 100644 ruty/mails/program/localization/en_CA/messages.inc create mode 100644 ruty/mails/program/localization/en_GB/labels.inc create mode 100644 ruty/mails/program/localization/en_GB/messages.inc create mode 100644 ruty/mails/program/localization/en_GB/timezones.inc create mode 100644 ruty/mails/program/localization/en_US/csv2vcard.inc create mode 100644 ruty/mails/program/localization/en_US/labels.inc create mode 100644 ruty/mails/program/localization/en_US/messages.inc create mode 100644 ruty/mails/program/localization/en_US/timezones.inc create mode 100644 ruty/mails/program/localization/eo/labels.inc create mode 100644 ruty/mails/program/localization/eo/messages.inc create mode 100644 ruty/mails/program/localization/es_419/labels.inc create mode 100644 ruty/mails/program/localization/es_419/messages.inc create mode 100644 ruty/mails/program/localization/es_419/timezones.inc create mode 100644 ruty/mails/program/localization/es_AR/labels.inc create mode 100644 ruty/mails/program/localization/es_AR/messages.inc create mode 100644 ruty/mails/program/localization/es_AR/timezones.inc create mode 100644 ruty/mails/program/localization/es_ES/csv2vcard.inc create mode 100644 ruty/mails/program/localization/es_ES/labels.inc create mode 100644 ruty/mails/program/localization/es_ES/messages.inc create mode 100644 ruty/mails/program/localization/es_ES/timezones.inc create mode 100644 ruty/mails/program/localization/et_EE/labels.inc create mode 100644 ruty/mails/program/localization/et_EE/messages.inc create mode 100644 ruty/mails/program/localization/et_EE/timezones.inc create mode 100644 ruty/mails/program/localization/eu_ES/labels.inc create mode 100644 ruty/mails/program/localization/eu_ES/messages.inc create mode 100644 ruty/mails/program/localization/eu_ES/timezones.inc create mode 100644 ruty/mails/program/localization/fa_AF/labels.inc create mode 100644 ruty/mails/program/localization/fa_AF/messages.inc create mode 100644 ruty/mails/program/localization/fa_IR/labels.inc create mode 100644 ruty/mails/program/localization/fa_IR/messages.inc create mode 100644 ruty/mails/program/localization/fa_IR/timezones.inc create mode 100644 ruty/mails/program/localization/fi_FI/csv2vcard.inc create mode 100644 ruty/mails/program/localization/fi_FI/labels.inc create mode 100644 ruty/mails/program/localization/fi_FI/messages.inc create mode 100644 ruty/mails/program/localization/fo_FO/labels.inc create mode 100644 ruty/mails/program/localization/fo_FO/messages.inc create mode 100644 ruty/mails/program/localization/fr_FR/csv2vcard.inc create mode 100644 ruty/mails/program/localization/fr_FR/labels.inc create mode 100644 ruty/mails/program/localization/fr_FR/messages.inc create mode 100644 ruty/mails/program/localization/fr_FR/timezones.inc create mode 100644 ruty/mails/program/localization/fy_NL/labels.inc create mode 100644 ruty/mails/program/localization/fy_NL/messages.inc create mode 100644 ruty/mails/program/localization/ga_IE/labels.inc create mode 100644 ruty/mails/program/localization/ga_IE/messages.inc create mode 100644 ruty/mails/program/localization/ga_IE/timezones.inc create mode 100644 ruty/mails/program/localization/gl_ES/labels.inc create mode 100644 ruty/mails/program/localization/gl_ES/messages.inc create mode 100644 ruty/mails/program/localization/he_IL/labels.inc create mode 100644 ruty/mails/program/localization/he_IL/messages.inc create mode 100644 ruty/mails/program/localization/he_IL/timezones.inc create mode 100644 ruty/mails/program/localization/hi_IN/labels.inc create mode 100644 ruty/mails/program/localization/hi_IN/messages.inc create mode 100644 ruty/mails/program/localization/hr_HR/labels.inc create mode 100644 ruty/mails/program/localization/hr_HR/messages.inc create mode 100644 ruty/mails/program/localization/hu_HU/labels.inc create mode 100644 ruty/mails/program/localization/hu_HU/messages.inc create mode 100644 ruty/mails/program/localization/hu_HU/timezones.inc create mode 100644 ruty/mails/program/localization/hy_AM/labels.inc create mode 100644 ruty/mails/program/localization/hy_AM/messages.inc create mode 100644 ruty/mails/program/localization/ia/labels.inc create mode 100644 ruty/mails/program/localization/ia/messages.inc create mode 100644 ruty/mails/program/localization/id_ID/labels.inc create mode 100644 ruty/mails/program/localization/id_ID/messages.inc create mode 100644 ruty/mails/program/localization/id_ID/timezones.inc create mode 100644 ruty/mails/program/localization/index.inc create mode 100644 ruty/mails/program/localization/is_IS/labels.inc create mode 100644 ruty/mails/program/localization/is_IS/messages.inc create mode 100644 ruty/mails/program/localization/is_IS/timezones.inc create mode 100644 ruty/mails/program/localization/it_IT/csv2vcard.inc create mode 100644 ruty/mails/program/localization/it_IT/labels.inc create mode 100644 ruty/mails/program/localization/it_IT/messages.inc create mode 100644 ruty/mails/program/localization/ja_JP/labels.inc create mode 100644 ruty/mails/program/localization/ja_JP/messages.inc create mode 100644 ruty/mails/program/localization/ja_JP/timezones.inc create mode 100644 ruty/mails/program/localization/ka_GE/labels.inc create mode 100644 ruty/mails/program/localization/ka_GE/messages.inc create mode 100644 ruty/mails/program/localization/kab/labels.inc create mode 100644 ruty/mails/program/localization/kab/messages.inc create mode 100644 ruty/mails/program/localization/km_KH/labels.inc create mode 100644 ruty/mails/program/localization/km_KH/messages.inc create mode 100644 ruty/mails/program/localization/kn_IN/labels.inc create mode 100644 ruty/mails/program/localization/kn_IN/messages.inc create mode 100644 ruty/mails/program/localization/ko_KR/labels.inc create mode 100644 ruty/mails/program/localization/ko_KR/messages.inc create mode 100644 ruty/mails/program/localization/ko_KR/timezones.inc create mode 100644 ruty/mails/program/localization/ku/labels.inc create mode 100644 ruty/mails/program/localization/ku/messages.inc create mode 100644 ruty/mails/program/localization/ku_IQ/labels.inc create mode 100644 ruty/mails/program/localization/ku_IQ/messages.inc create mode 100644 ruty/mails/program/localization/ku_IQ/timezones.inc create mode 100644 ruty/mails/program/localization/lb_LU/labels.inc create mode 100644 ruty/mails/program/localization/lb_LU/messages.inc create mode 100644 ruty/mails/program/localization/lb_LU/timezones.inc create mode 100644 ruty/mails/program/localization/lt_LT/labels.inc create mode 100644 ruty/mails/program/localization/lt_LT/messages.inc create mode 100644 ruty/mails/program/localization/lt_LT/timezones.inc create mode 100644 ruty/mails/program/localization/lv_LV/labels.inc create mode 100644 ruty/mails/program/localization/lv_LV/messages.inc create mode 100644 ruty/mails/program/localization/lv_LV/timezones.inc create mode 100644 ruty/mails/program/localization/mk_MK/labels.inc create mode 100644 ruty/mails/program/localization/mk_MK/messages.inc create mode 100644 ruty/mails/program/localization/mk_MK/timezones.inc create mode 100644 ruty/mails/program/localization/ml_IN/labels.inc create mode 100644 ruty/mails/program/localization/ml_IN/messages.inc create mode 100644 ruty/mails/program/localization/mn_MN/labels.inc create mode 100644 ruty/mails/program/localization/mn_MN/messages.inc create mode 100644 ruty/mails/program/localization/mr_IN/labels.inc create mode 100644 ruty/mails/program/localization/mr_IN/messages.inc create mode 100644 ruty/mails/program/localization/ms_MY/labels.inc create mode 100644 ruty/mails/program/localization/ms_MY/messages.inc create mode 100644 ruty/mails/program/localization/nb_NO/labels.inc create mode 100644 ruty/mails/program/localization/nb_NO/messages.inc create mode 100644 ruty/mails/program/localization/nb_NO/timezones.inc create mode 100644 ruty/mails/program/localization/ne_NP/labels.inc create mode 100644 ruty/mails/program/localization/ne_NP/messages.inc create mode 100644 ruty/mails/program/localization/nl_BE/labels.inc create mode 100644 ruty/mails/program/localization/nl_BE/messages.inc create mode 100644 ruty/mails/program/localization/nl_NL/labels.inc create mode 100644 ruty/mails/program/localization/nl_NL/messages.inc create mode 100644 ruty/mails/program/localization/nl_NL/timezones.inc create mode 100644 ruty/mails/program/localization/nn_NO/labels.inc create mode 100644 ruty/mails/program/localization/nn_NO/messages.inc create mode 100644 ruty/mails/program/localization/pl_PL/csv2vcard.inc create mode 100644 ruty/mails/program/localization/pl_PL/labels.inc create mode 100644 ruty/mails/program/localization/pl_PL/messages.inc create mode 100644 ruty/mails/program/localization/pl_PL/timezones.inc create mode 100644 ruty/mails/program/localization/ps/labels.inc create mode 100644 ruty/mails/program/localization/ps/messages.inc create mode 100644 ruty/mails/program/localization/pt_BR/csv2vcard.inc create mode 100644 ruty/mails/program/localization/pt_BR/labels.inc create mode 100644 ruty/mails/program/localization/pt_BR/messages.inc create mode 100644 ruty/mails/program/localization/pt_BR/timezones.inc create mode 100644 ruty/mails/program/localization/pt_PT/labels.inc create mode 100644 ruty/mails/program/localization/pt_PT/messages.inc create mode 100644 ruty/mails/program/localization/pt_PT/timezones.inc create mode 100644 ruty/mails/program/localization/ro_RO/labels.inc create mode 100644 ruty/mails/program/localization/ro_RO/messages.inc create mode 100644 ruty/mails/program/localization/ro_RO/timezones.inc create mode 100644 ruty/mails/program/localization/ru_RU/csv2vcard.inc create mode 100644 ruty/mails/program/localization/ru_RU/labels.inc create mode 100644 ruty/mails/program/localization/ru_RU/messages.inc create mode 100644 ruty/mails/program/localization/ru_RU/timezones.inc create mode 100644 ruty/mails/program/localization/si_LK/labels.inc create mode 100644 ruty/mails/program/localization/si_LK/messages.inc create mode 100644 ruty/mails/program/localization/sk_SK/csv2vcard.inc create mode 100644 ruty/mails/program/localization/sk_SK/labels.inc create mode 100644 ruty/mails/program/localization/sk_SK/messages.inc create mode 100644 ruty/mails/program/localization/sk_SK/timezones.inc create mode 100644 ruty/mails/program/localization/sl_SI/labels.inc create mode 100644 ruty/mails/program/localization/sl_SI/messages.inc create mode 100644 ruty/mails/program/localization/sl_SI/timezones.inc create mode 100644 ruty/mails/program/localization/sq_AL/labels.inc create mode 100644 ruty/mails/program/localization/sq_AL/messages.inc create mode 100644 ruty/mails/program/localization/sq_AL/timezones.inc create mode 100644 ruty/mails/program/localization/sr_CS/labels.inc create mode 100644 ruty/mails/program/localization/sr_CS/messages.inc create mode 100644 ruty/mails/program/localization/sv_SE/labels.inc create mode 100644 ruty/mails/program/localization/sv_SE/messages.inc create mode 100644 ruty/mails/program/localization/sv_SE/timezones.inc create mode 100644 ruty/mails/program/localization/ta_IN/labels.inc create mode 100644 ruty/mails/program/localization/ta_IN/messages.inc create mode 100644 ruty/mails/program/localization/th_TH/labels.inc create mode 100644 ruty/mails/program/localization/th_TH/messages.inc create mode 100644 ruty/mails/program/localization/ti/labels.inc create mode 100644 ruty/mails/program/localization/ti/messages.inc create mode 100644 ruty/mails/program/localization/tr_TR/labels.inc create mode 100644 ruty/mails/program/localization/tr_TR/messages.inc create mode 100644 ruty/mails/program/localization/tr_TR/timezones.inc create mode 100644 ruty/mails/program/localization/tzl/labels.inc create mode 100644 ruty/mails/program/localization/tzl/messages.inc create mode 100644 ruty/mails/program/localization/ug/labels.inc create mode 100644 ruty/mails/program/localization/ug/messages.inc create mode 100644 ruty/mails/program/localization/ug/timezones.inc create mode 100644 ruty/mails/program/localization/uk_UA/labels.inc create mode 100644 ruty/mails/program/localization/uk_UA/messages.inc create mode 100644 ruty/mails/program/localization/ur_PK/labels.inc create mode 100644 ruty/mails/program/localization/uz/labels.inc create mode 100644 ruty/mails/program/localization/uz/messages.inc create mode 100644 ruty/mails/program/localization/uz/timezones.inc create mode 100644 ruty/mails/program/localization/vi_VN/labels.inc create mode 100644 ruty/mails/program/localization/vi_VN/messages.inc create mode 100644 ruty/mails/program/localization/zh_CN/labels.inc create mode 100644 ruty/mails/program/localization/zh_CN/messages.inc create mode 100644 ruty/mails/program/localization/zh_TW/csv2vcard.inc create mode 100644 ruty/mails/program/localization/zh_TW/labels.inc create mode 100644 ruty/mails/program/localization/zh_TW/messages.inc create mode 100644 ruty/mails/program/localization/zh_TW/timezones.inc create mode 100644 ruty/mails/program/resources/blank.gif create mode 100644 ruty/mails/program/resources/blank.tiff create mode 100644 ruty/mails/program/resources/blank.webp create mode 100644 ruty/mails/program/resources/blocked.gif create mode 100644 ruty/mails/program/resources/dummy.pdf create mode 100644 ruty/mails/program/resources/error.html create mode 100644 ruty/mails/program/resources/tinymce/browser.css create mode 100644 ruty/mails/program/resources/tinymce/content.css create mode 100644 ruty/mails/program/resources/tinymce/video.png create mode 120000 ruty/mails/public_html/.htaccess create mode 100644 ruty/mails/public_html/index.php create mode 120000 ruty/mails/public_html/plugins create mode 120000 ruty/mails/public_html/program/js create mode 120000 ruty/mails/public_html/program/resources create mode 120000 ruty/mails/public_html/skins diff --git a/ruty/mails/bin/cleandb.sh b/ruty/mails/bin/cleandb.sh new file mode 100644 index 0000000..711f32b --- /dev/null +++ b/ruty/mails/bin/cleandb.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require INSTALL_PATH.'program/include/clisetup.php'; + +if (!empty($_SERVER['argv'][1])) { + $days = intval($_SERVER['argv'][1]); +} +else { + $days = 7; +} + +rcmail_utils::db_clean($days); diff --git a/ruty/mails/bin/cssshrink.sh b/ruty/mails/bin/cssshrink.sh new file mode 100644 index 0000000..4fa5dd4 --- /dev/null +++ b/ruty/mails/bin/cssshrink.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +PWD=`dirname "$0"` + +do_shrink() { + rm -f "$2" + csso $1 -o $2 --no-restructure +} + +if which csso > /dev/null 2>&1; then + : +else + echo "csso not found. Please install e.g. 'npm install -g csso-cli'." + exit 1 +fi + +# compress single file from argument +if [ $# -gt 0 ]; then + CSS_FILE="$1" + + echo "Shrinking $CSS_FILE" + minfile=`echo $CSS_FILE | sed -e 's/\.css$/\.min\.css/'` + do_shrink "$CSS_FILE" "$minfile" + exit +fi + +DIRS="$PWD/../skins/* $PWD/../plugins/* $PWD/../plugins/*/skins/* $PWD/../plugins/*/themes/*" +# default: compress application scripts +for dir in $DIRS; do + for file in $dir/*.css; do + if echo "$file" | grep -q -e '.min.css$'; then + continue + fi + if [ ! -f "$file" ]; then + continue + fi + + echo "Shrinking $file" + minfile=`echo $file | sed -e 's/\.css$/\.min\.css/'` + do_shrink "$file" "$minfile" + done +done diff --git a/ruty/mails/bin/decrypt.sh b/ruty/mails/bin/decrypt.sh new file mode 100644 index 0000000..b684cd6 --- /dev/null +++ b/ruty/mails/bin/decrypt.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +/** + * If http_received_header_encrypt is configured, the IP address and the + * host name of the added Received: header is encrypted with 3DES, to + * protect information that some could consider sensitive, yet their + * availability is a must in some circumstances. + * + * Such an encrypted Received: header might look like: + * + * Received: from DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ== + * [my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4] + * with HTTP/1.1 (POST); Thu, 14 May 2009 19:17:28 +0200 + * + * In this example, the two encrypted components are the sender host name + * (DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==) and the IP + * address (my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4). + * + * Using this tool, they can be decrypted into plain text: + * + * $ bin/decrypt.sh 'my0nUbjZXKtl7KVBZcsvWOxxtyVFxza4' \ + * > 'DzgkvJBO5+bw+oje5JACeNIa/uSI4mRw2cy5YoPBba73eyBmjtyHnQ==' + * 84.3.187.208 + * 5403BBD0.catv.pool.telekom.hu + * $ + * + * Thus it is known that this particular message was sent by 84.3.187.208, + * having, at the time of sending, the name of 5403BBD0.catv.pool.telekom.hu. + * + * If (most likely binary) junk is shown, then + * - either the encryption password has, between the time the mail was sent + * and 'now', changed, or + * - you are dealing with counterfeit header data. + */ + +define('INSTALL_PATH', realpath(__DIR__ .'/..') . '/'); + +require INSTALL_PATH . 'program/include/clisetup.php'; + +if ($argc < 2) { + die("Usage: " . basename($argv[0]) . " encrypted-hdr-part [encrypted-hdr-part ...]\n"); +} + +$RCMAIL = rcube::get_instance(); + +for ($i = 1; $i < $argc; $i++) { + printf("%s\n", $RCMAIL->decrypt($argv[$i])); +}; diff --git a/ruty/mails/bin/deluser.sh b/ruty/mails/bin/deluser.sh new file mode 100644 index 0000000..7dcf944 --- /dev/null +++ b/ruty/mails/bin/deluser.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +function print_usage() +{ + print "Usage: deluser.sh [--host=HOST][--age=DAYS][--dry-run] [username]\n"; + print "--host=HOST The IMAP hostname or IP the given user is related to\n"; + print "--age=DAYS Delete all users who have not logged in for more than X days\n"; + print "--dry-run List users but do not delete them (for use with --age)\n"; +} + +function _die($msg, $usage=false) +{ + fwrite(STDERR, $msg . "\n"); + if ($usage) print_usage(); + exit(1); +} + +$rcmail = rcube::get_instance(); + +// get arguments +$args = rcube_utils::get_opt(['h' => 'host', 'a' => 'age', 'd' => 'dry-run:bool']); + +if (!empty($args['age']) && ($age = intval($args['age']))) { + $db = $rcmail->get_dbh(); + $db->db_connect('r'); + + $query = $db->query("SELECT `username`, `mail_host` FROM " . $db->table_name('users', true) + . " WHERE `last_login` < " . $db->now($age * -1 * 86400) + . ($args['host'] ? " AND `mail_host` = " . $db->quote($args['host']) : '') + ); + + while ($user = $db->fetch_assoc($query)) { + if (!empty($args['dry-run'])) { + printf("%s (%s)\n", $user['username'], $user['mail_host']); + continue; + } + system(sprintf("php %s/deluser.sh --host=%s %s", INSTALL_PATH . 'bin', escapeshellarg($user['mail_host']), escapeshellarg($user['username']))); + } + exit(0); +} + +$username = isset($args[0]) ? trim($args[0]) : null; +if (empty($username)) { + _die("Missing required parameters", true); +} + +if (empty($args['host'])) { + $hosts = $rcmail->config->get('imap_host', ''); + if (is_string($hosts)) { + $args['host'] = $hosts; + } + else if (is_array($hosts) && count($hosts) == 1) { + $args['host'] = reset($hosts); + } + else { + _die("Specify a host name", true); + } + + // host can be a URL like tls://192.168.12.44 + $host_url = parse_url($args['host']); + if ($host_url['host']) { + $args['host'] = $host_url['host']; + } +} + +// connect to DB +$db = $rcmail->get_dbh(); +$db->db_connect('w'); +$transaction = false; + +if (!$db->is_connected() || $db->is_error()) { + _die("No DB connection\n" . $db->is_error()); +} + +// find user in local database +$user = rcube_user::query($username, $args['host']); + +if (!$user) { + die("User not found.\n"); +} + +// inform plugins about approaching user deletion +$plugin = $rcmail->plugins->exec_hook('user_delete_prepare', ['user' => $user, 'username' => $username, 'host' => $args['host']]); + +// let plugins cleanup their own user-related data +if (!$plugin['abort']) { + $transaction = $db->startTransaction(); + $plugin = $rcmail->plugins->exec_hook('user_delete', $plugin); +} + +if ($plugin['abort']) { + unset($plugin['abort']); + if ($transaction) { + $db->rollbackTransaction(); + } + _die("User deletion aborted by plugin"); +} + +$db->query('DELETE FROM ' . $db->table_name('users', true) . ' WHERE `user_id` = ?', $user->ID); + +if ($db->is_error()) { + $rcmail->plugins->exec_hook('user_delete_rollback', $plugin); + _die("DB error occurred: " . $db->is_error()); +} +else { + // inform plugins about executed user deletion + $plugin = $rcmail->plugins->exec_hook('user_delete_commit', $plugin); + + if ($plugin['abort']) { + unset($plugin['abort']); + $db->rollbackTransaction(); + $rcmail->plugins->exec_hook('user_delete_rollback', $plugin); + } + else { + $db->endTransaction(); + echo "Successfully deleted user $user->ID\n"; + } +} diff --git a/ruty/mails/bin/gc.sh b/ruty/mails/bin/gc.sh new file mode 100644 index 0000000..e83d18e --- /dev/null +++ b/ruty/mails/bin/gc.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require INSTALL_PATH.'program/include/clisetup.php'; + +$rcmail = rcube::get_instance(); + +$session_driver = $rcmail->config->get('session_storage', 'db'); +$session_lifetime = $rcmail->config->get('session_lifetime', 0) * 60 * 2; + +// Clean expired SQL sessions +if ($session_driver == 'db' && $session_lifetime) { + $db = $rcmail->get_dbh(); + $db->query("DELETE FROM " . $db->table_name('session') + . " WHERE changed < " . $db->now(-$session_lifetime)); +} + +// Clean caches and temp directory +$rcmail->gc(); diff --git a/ruty/mails/bin/indexcontacts.sh b/ruty/mails/bin/indexcontacts.sh new file mode 100644 index 0000000..3fcff80 --- /dev/null +++ b/ruty/mails/bin/indexcontacts.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH.'program/include/clisetup.php'; +ini_set('memory_limit', -1); + +rcmail_utils::indexcontacts(); diff --git a/ruty/mails/bin/initdb.sh b/ruty/mails/bin/initdb.sh new file mode 100644 index 0000000..a98bbf5 --- /dev/null +++ b/ruty/mails/bin/initdb.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +// get arguments +$opts = rcube_utils::get_opt([ + 'd' => 'dir', + 'u' => 'update' +]); + +if (empty($opts['dir'])) { + rcube::raise_error("Database schema directory not specified (--dir).", false, true); +} + +// Check if directory exists +if (!file_exists($opts['dir'])) { + rcube::raise_error("Specified database schema directory doesn't exist.", false, true); +} + +$db = rcmail_utils::db(); + +if (!empty($opts['update']) && in_array($db->table_name('system'), (array)$db->list_tables())) { + echo "Checking for database schema updates..." . PHP_EOL; + rcmail_utils::db_update($opts['dir'], 'roundcube', null, ['errors' => true]); +} else { + rcmail_utils::db_init($opts['dir']); +} diff --git a/ruty/mails/bin/installto.sh b/ruty/mails/bin/installto.sh new file mode 100644 index 0000000..30705d3 --- /dev/null +++ b/ruty/mails/bin/installto.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +if (!function_exists('system')) { + rcube::raise_error("PHP system() function is required. Check disable_functions in php.ini.", false, true); +} + +$target_dir = unslashify(end($_SERVER['argv'])); +$accept = in_array('-y', $_SERVER['argv']) ? 'y' : null; + +if (empty($target_dir) || !is_dir(realpath($target_dir))) { + rcube::raise_error("Invalid target: not a directory\nUsage: installto.sh [-y] ", false, true); +} + +// read version from iniset.php +$iniset = @file_get_contents($target_dir . '/program/include/iniset.php'); +if (!preg_match('/define\(.RCMAIL_VERSION.,\s*.([0-9.]+[a-z0-9-]*)/', $iniset, $m)) { + rcube::raise_error("No valid Roundcube installation found at $target_dir", false, true); +} + +$oldversion = $m[1]; + +if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '>')) { + rcube::raise_error("Target installation already in version $oldversion.", false, true); +} + +if (version_compare(version_parse($oldversion), version_parse(RCMAIL_VERSION), '==')) { + echo "Target installation already in version $oldversion. Do you want to update again? (y/N)\n"; +} +else { + echo "Upgrading from $oldversion. Do you want to continue? (y/N)\n"; +} + +$input = $accept ?: trim(fgets(STDIN)); + +if (strtolower($input) == 'y') { + echo "Copying files to target location..."; + + $adds = []; + $dirs = ['bin','SQL','plugins','skins','program']; + + if (is_dir(INSTALL_PATH . 'vendor') && (!is_file("$target_dir/composer.json") || rcmail_install::vendor_dir_untouched($target_dir))) { + $dirs[] = 'vendor'; + } + if (file_exists("$target_dir/installer")) { + $dirs[] = 'installer'; + } + + foreach ($dirs as $dir) { + // @FIXME: should we use --delete for all directories? + $delete = in_array($dir, ['program', 'vendor', 'installer']) ? '--delete ' : ''; + $command = "rsync -aC --out-format=%n " . $delete . INSTALL_PATH . "$dir/ $target_dir/$dir/"; + + if (system($command, $ret) === false || $ret > 0) { + rcube::raise_error("Failed to execute command: $command", false, true); + } + } + + foreach (['index.php','config/defaults.inc.php','composer.json-dist','jsdeps.json','CHANGELOG.md','README.md','UPGRADING','LICENSE','INSTALL'] as $file) { + $command = "rsync -a --out-format=%n " . INSTALL_PATH . "$file $target_dir/$file"; + + if (file_exists(INSTALL_PATH . $file) && (system($command, $ret) === false || $ret > 0)) { + rcube::raise_error("Failed to execute command: $command", false, true); + } + } + + // Copy .htaccess or .user.ini if needed + foreach (['.htaccess','.user.ini'] as $file) { + if (file_exists(INSTALL_PATH . $file)) { + if (!file_exists("$target_dir/$file") || file_get_contents(INSTALL_PATH . $file) != file_get_contents("$target_dir/$file")) { + if (copy(INSTALL_PATH . $file, "$target_dir/$file.new")) { + echo "$file.new\n"; + $adds[] = "NOTICE: New $file file saved as $file.new."; + } + } + } + } + + // remove old (<1.0) .htaccess file + @unlink("$target_dir/program/.htaccess"); + echo "done.\n\n"; + + if (is_dir("$target_dir/skins/default")) { + echo "Removing old default skin..."; + system("rm -rf $target_dir/skins/default $target_dir/plugins/jqueryui/themes/default"); + foreach (glob(INSTALL_PATH . "plugins/*/skins") as $plugin_skin_dir) { + $plugin_skin_dir = preg_replace('!^.*' . INSTALL_PATH . '!', '', $plugin_skin_dir); + if (is_dir("$target_dir/$plugin_skin_dir/classic")) { + system("rm -rf $target_dir/$plugin_skin_dir/default"); + } + } + echo "done.\n\n"; + } + + // Warn about situation when using "complete" package to update "custom" installation (#7087) + // Note: "Complete" package do not include jsdeps.json nor install-jsdeps.sh + if (file_exists("$target_dir/jsdeps.json") && !file_exists(INSTALL_PATH . "jsdeps.json")) { + $adds[] = "WARNING: JavaScript dependencies update skipped. New jsdeps.json file not found."; + } + // check if js-deps are up-to-date + else if (file_exists("$target_dir/jsdeps.json") && file_exists("$target_dir/bin/install-jsdeps.sh")) { + $jsdeps = json_decode(file_get_contents("$target_dir/jsdeps.json")); + $package = $jsdeps->dependencies[0]; + $dest_file = $target_dir . '/' . $package->dest; + + if (!file_exists($dest_file) || sha1_file($dest_file) !== $package->sha1) { + echo "Installing JavaScript dependencies..."; + system("cd $target_dir && bin/install-jsdeps.sh"); + echo "done.\n\n"; + } + } + + if (file_exists("$target_dir/installer")) { + $adds[] = "NOTICE: The 'installer' directory still exists. You should remove it after the upgrade."; + } + + if (!empty($adds)) { + echo implode("\n", $adds) . "\n\n"; + } + + echo "Running update script at target...\n"; + system("cd $target_dir && php bin/update.sh --version=$oldversion" . ($accept ? ' -y' : '')); + echo "All done.\n"; +} +else { + echo "Update cancelled. See ya!\n"; +} diff --git a/ruty/mails/bin/jsshrink.sh b/ruty/mails/bin/jsshrink.sh new file mode 100644 index 0000000..ee75399 --- /dev/null +++ b/ruty/mails/bin/jsshrink.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +set -e + +PWD=`dirname "$0"` +LANG_IN='ECMASCRIPT5' + +do_shrink() { + rm -f "$2" + # copy the first comment block with license information for LibreJS + grep -q '@lic' $1 && sed -n '/\/\*/,/\*\// { p; /\*\//q; }' $1 > $2 + uglifyjs --compress --mangle -- $1 >> $2 +} + +if which uglifyjs > /dev/null 2>&1; then + : +else + echo "uglifyjs not found. Please install e.g. 'npm install -g uglify-js'." + exit 1 +fi + +# compress single file from argument +if [ $# -gt 0 ]; then + JS_FILE="$1" + + if [ $# -gt 1 ]; then + LANG_IN="$2" + fi + + echo "Shrinking $JS_FILE" + minfile=`echo $JS_FILE | sed -e 's/\.js$/\.min\.js/'` + do_shrink "$JS_FILE" "$minfile" "$LANG_IN" + exit +fi + +DIRS="$PWD/../program/js $PWD/../skins/* $PWD/../plugins/* $PWD/../plugins/*/skins/* $PWD/../plugins/managesieve/codemirror/lib" +# default: compress application scripts +for dir in $DIRS; do + for file in $dir/*.js; do + if echo "$file" | grep -q -e '.min.js$'; then + continue + fi + if [ ! -f "$file" ]; then + continue + fi + + echo "Shrinking $file" + minfile=`echo $file | sed -e 's/\.js$/\.min\.js/'` + do_shrink "$file" "$minfile" "$LANG_IN" + done +done diff --git a/ruty/mails/bin/makedoc.sh b/ruty/mails/bin/makedoc.sh new file mode 100644 index 0000000..2d6ac8a --- /dev/null +++ b/ruty/mails/bin/makedoc.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +set -x + +BIN_PHPDOC=`/usr/bin/which phpdoc` + +if [ ! -x "$BIN_PHPDOC" ] +then + echo "phpdoc not found" + exit 1 +fi + +INSTALL_PATH="`dirname $0`/.." +PATH_PROJECT=$INSTALL_PATH/program/include +PATH_FRAMEWORK=$INSTALL_PATH/program/lib/Roundcube +PATH_DOCS=$INSTALL_PATH/doc/phpdoc +TITLE="Roundcube Webmail" +PACKAGES="Webmail" +OUTPUTFORMAT=HTML +TEMPLATE=responsive-twig + +# make documentation +$BIN_PHPDOC -d $PATH_PROJECT,$PATH_FRAMEWORK -t $PATH_DOCS --title "$TITLE" \ + --defaultpackagename $PACKAGES --template=$TEMPLATE diff --git a/ruty/mails/bin/moduserprefs.sh b/ruty/mails/bin/moduserprefs.sh new file mode 100644 index 0000000..b3402df --- /dev/null +++ b/ruty/mails/bin/moduserprefs.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH.'program/include/clisetup.php'; + +function print_usage() +{ + print "Usage: moduserprefs.sh [options] pref-name [pref-value]\n"; + print "Options:\n"; + print " --user=user-id User ID in local database\n"; + print " --config=path Location of additional configuration file\n"; + print " --delete Unset the given preference\n"; + print " --type=type Pref-value type: int, bool, string\n"; +} + + +// get arguments +$args = rcube_utils::get_opt([ + 'u' => 'user', + 'd' => 'delete:bool', + 't' => 'type', + 'c' => 'config', +]); + +if (empty($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') { + print_usage(); + exit; +} +else if (empty($args[0]) || (empty($args[1]) && empty($args['delete']))) { + print "Missing required parameters.\n"; + print_usage(); + exit; +} + +$pref_name = trim($args[0]); +$pref_value = !empty($args['delete']) ? null : trim($args[1]); + +if ($pref_value === null) { + $args['type'] = null; +} + +if (!empty($args['config'])) { + $rcube = rcube::get_instance(); + $rcube->config->load_from_file($args['config']); +} + +$type = isset($args['type']) ? $args['type'] : null; +$user = isset($args['user']) ? $args['user'] : null; + +rcmail_utils::mod_pref($pref_name, $pref_value, $user, $type); diff --git a/ruty/mails/bin/msgexport.sh b/ruty/mails/bin/msgexport.sh new file mode 100644 index 0000000..42624e5 --- /dev/null +++ b/ruty/mails/bin/msgexport.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); +ini_set('memory_limit', -1); + +require_once INSTALL_PATH.'program/include/clisetup.php'; + +function print_usage() +{ + print "Usage: msgexport.sh -h imap-host -u user-name -m mailbox name\n"; + print "--host IMAP host\n"; + print "--user IMAP user name\n"; + print "--mbox Folder name, set to '*' for all\n"; + print "--file Output file\n"; +} + +function vputs($str) +{ + $out = !empty($GLOBALS['args']['file']) ? STDOUT : STDERR; + fwrite($out, $str); +} + +function progress_update($pos, $max) +{ + $percent = round(100 * $pos / $max); + vputs(sprintf("%3d%% [%-51s] %d/%d\033[K\r", $percent, @str_repeat('=', $percent / 2) . '>', $pos, $max)); +} + +function export_mailbox($mbox, $filename) +{ + global $IMAP; + + $IMAP->set_folder($mbox); + + vputs("Getting message list of {$mbox}..."); + + $index = $IMAP->index($mbox, null, 'ASC'); + $count = $index->count(); + $index = $index->get(); + + vputs("$count messages\n"); + + if ($filename) { + if (!($out = fopen($filename, 'w'))) { + vputs("Cannot write to output file\n"); + return; + } + vputs("Writing to $filename\n"); + } + else { + $out = STDOUT; + } + + for ($i = 0; $i < $count; $i++) { + $headers = $IMAP->get_message_headers($index[$i]); + $from = current(rcube_mime::decode_address_list($headers->from, 1, false)); + + fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid)); + $IMAP->get_raw_body($headers->uid, $out); + fwrite($out, "\n\n\n"); + + progress_update($i+1, $count); + } + vputs("\ncomplete.\n"); + + if ($filename) { + fclose($out); + } +} + +// get arguments +$opts = ['h' => 'host', 'u' => 'user', 'p' => 'pass', 'm' => 'mbox', 'f' => 'file']; +$args = rcube_utils::get_opt($opts) + ['host' => 'localhost', 'mbox' => 'INBOX']; + +if (!isset($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') { + print_usage(); + exit; +} +else if (!$args['host']) { + vputs("Missing required parameters.\n"); + print_usage(); + exit; +} + +// prompt for username if not set +if (empty($args['user'])) { + vputs("IMAP user: "); + $args['user'] = trim(fgets(STDIN)); +} + +// prompt for password +$args['pass'] = rcube_utils::prompt_silent("Password: "); + + +// parse $host URL +$a_host = parse_url($args['host']); +if (!empty($a_host['host'])) { + $host = $a_host['host']; + $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? TRUE : FALSE; + $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143); +} +else { + $host = $args['host']; + $imap_port = 143; + $imap_ssl = false; +} + +// instantiate IMAP class +$IMAP = new rcube_imap(null); + +// try to connect to IMAP server +if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl)) { + vputs("IMAP login successful.\n"); + + $filename = null; + $mailboxes = $args['mbox'] == '*' ? $IMAP->list_folders(null) : [$args['mbox']]; + + foreach ($mailboxes as $mbox) { + if (!empty($args['file'])) { + $filename = preg_replace('/\.[a-z0-9]{3,4}$/i', '', $args['file']) . asciiwords($mbox) . '.mbox'; + } + else if ($args['mbox'] == '*') { + $filename = asciiwords($mbox) . '.mbox'; + } + + if ($args['mbox'] == '*' && in_array(strtolower($mbox), ['junk','spam','trash'])) { + continue; + } + + export_mailbox($mbox, $filename); + } +} +else { + vputs("IMAP login failed.\n"); +} diff --git a/ruty/mails/bin/msgimport.sh b/ruty/mails/bin/msgimport.sh new file mode 100644 index 0000000..e7dd5cb --- /dev/null +++ b/ruty/mails/bin/msgimport.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); +ini_set('memory_limit', -1); + +require_once INSTALL_PATH.'program/include/clisetup.php'; + +function print_usage() +{ + print "Usage: msgimport.sh -h imap-host -u user-name -m mailbox -f message-file\n"; + print "--host IMAP host\n"; + print "--user IMAP user name\n"; + print "--mbox Target mailbox\n"; + print "--file Message file to upload\n"; +} + +// get arguments +$opts = ['h' => 'host', 'u' => 'user', 'p' => 'pass', 'm' => 'mbox', 'f' => 'file']; +$args = rcube_utils::get_opt($opts) + ['host' => 'localhost', 'mbox' => 'INBOX']; + +if (!isset($_SERVER['argv'][1]) || $_SERVER['argv'][1] == 'help') { + print_usage(); + exit; +} +else if (empty($args['host']) || empty($args['file'])) { + print "Missing required parameters.\n"; + print_usage(); + exit; +} +else if (!is_file($args['file'])) { + rcube::raise_error("Cannot read message file.", false, true); +} + +// prompt for username if not set +if (empty($args['user'])) { + //fwrite(STDOUT, "Please enter your name\n"); + echo "IMAP user: "; + $args['user'] = trim(fgets(STDIN)); +} + +// prompt for password +if (empty($args['pass'])) { + $args['pass'] = rcube_utils::prompt_silent("Password: "); +} + +// parse $host URL +$a_host = parse_url($args['host']); +if (!empty($a_host['host'])) { + $host = $a_host['host']; + $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? TRUE : FALSE; + $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143); +} +else { + $host = $args['host']; + $imap_port = 143; + $imap_ssl = false; +} + +// instantiate IMAP class +$IMAP = new rcube_imap(null); + +// try to connect to IMAP server +if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl)) { + print "IMAP login successful.\n"; + print "Uploading messages...\n"; + + $count = 0; + $message = $lastline = ''; + + $fp = fopen($args['file'], 'r'); + while (($line = fgets($fp)) !== false) { + if (preg_match('/^From\s+-/', $line) && $lastline == '') { + if (!empty($message)) { + if ($IMAP->save_message($args['mbox'], rtrim($message))) { + $count++; + } + else { + rcube::raise_error("Failed to save message to {$args['mbox']}", false, true); + } + $message = ''; + } + continue; + } + + $message .= $line; + $lastline = rtrim($line); + } + + if (!empty($message) && $IMAP->save_message($args['mbox'], rtrim($message))) { + $count++; + } + + // upload message from file + if ($count) { + print "$count messages successfully added to {$args['mbox']}.\n"; + } + else { + print "Adding messages failed!\n"; + } +} +else { + rcube::raise_error("IMAP login failed.", false, true); +} diff --git a/ruty/mails/bin/update.sh b/ruty/mails/bin/update.sh new file mode 100644 index 0000000..13f0846 --- /dev/null +++ b/ruty/mails/bin/update.sh @@ -0,0 +1,328 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +// get arguments +$opts = rcube_utils::get_opt(['v' => 'version', 'y' => 'accept:bool']); + +// ask user if no version is specified +if (empty($opts['version'])) { + echo "What version are you upgrading from? Type '?' if you don't know.\n"; + + if (($input = trim(fgets(STDIN))) && preg_match('/^[0-9.]+[a-z0-9-]*$/', $input)) { + $opts['version'] = $input; + } + else { + $opts['version'] = RCMAIL_VERSION; + } +} + +$RCI = rcmail_install::get_instance(); +$RCI->load_config(); + +if ($RCI->configured) { + $success = true; + + if (($messages = $RCI->check_config($opts['version'])) || $RCI->legacy_config) { + $success = false; + $err = 0; + + // list old/replaced config options + if (!empty($messages['replaced'])) { + echo "WARNING: Replaced config options:\n"; + echo "(These config options have been replaced or renamed)\n"; + + foreach ($messages['replaced'] as $msg) { + echo "- '" . $msg['prop'] . "' was replaced by '" . $msg['replacement'] . "'\n"; + $err++; + } + } + + // list obsolete config options (just a notice) + if (!empty($messages['obsolete'])) { + echo "NOTICE: Obsolete config options:\n"; + echo "(You still have some obsolete or inexistent properties set." + . " This isn't a problem but should be noticed)\n"; + + foreach ($messages['obsolete'] as $msg) { + echo "- '" . $msg['prop'] . (!empty($msg['explain']) ? "': " . $msg['explain'] : "'") . "\n"; + $err++; + } + } + + if (!$err && $RCI->legacy_config) { + echo "WARNING: Your configuration needs to be migrated!\n"; + echo "We changed the configuration files structure and your two config files " + . "main.inc.php and db.inc.php have to be merged into one single file.\n"; + $err++; + } + + // ask user to update config files + if ($err) { + if (empty($opts['accept'])) { + echo "Do you want me to fix your local configuration? (y/N)\n"; + $input = trim(fgets(STDIN)); + } + + // positive: merge the local config with the defaults + if (!empty($opts['accept']) || strtolower($input) == 'y') { + $error = $written = false; + + echo "- backing up the current config file(s)...\n"; + + foreach (['config', 'main', 'db'] as $file) { + if (file_exists(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php')) { + if (!copy(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php', RCMAIL_CONFIG_DIR . '/' . $file . '.old.php')) { + $error = true; + } + } + } + + if (!$error) { + $RCI->merge_config(); + echo "- writing " . RCMAIL_CONFIG_DIR . "/config.inc.php...\n"; + $written = $RCI->save_configfile($RCI->create_config(false)); + } + + // Success! + if ($written) { + echo "Done.\n"; + echo "Your configuration files are now up-to-date!\n"; + + if (!empty($messages['missing'])) { + echo "But you still need to add the following missing options:\n"; + foreach ($messages['missing'] as $msg) { + echo "- '" . $msg['prop'] . ($msg['name'] ? "': " . $msg['name'] : "'") . "\n"; + } + } + + if ($RCI->legacy_config) { + foreach (['main', 'db'] as $file) { + @unlink(RCMAIL_CONFIG_DIR . '/' . $file . '.inc.php'); + } + } + } + else { + echo "Failed to write config file(s)!\n"; + echo "Grant write privileges to the current user or update the files manually " + . "according to the above messages.\n"; + } + } + else { + echo "Please update your config files manually according to the above messages.\n"; + } + } + + // list of config options with changed default (just a notice) + if (!empty($messages['defaults'])) { + echo "WARNING: Changed defaults (These config options have new default values):\n"; + + foreach ($messages['defaults'] as $opt) { + echo "- '{$opt}'\n"; + } + } + + // check dependencies based on the current configuration + if (!empty($messages['dependencies'])) { + echo "WARNING: Dependency check failed!\n"; + echo "(Some of your configuration settings require other options to be configured " + . "or additional PHP modules to be installed)\n"; + + foreach ($messages['dependencies'] as $msg) { + echo "- " . $msg['prop'] . ': ' . $msg['explain'] . "\n"; + } + + echo "Please fix your config files and run this script again!\n"; + echo "See ya.\n"; + } + } + + // check file type detection + if ($RCI->check_mime_detection()) { + echo "WARNING: File type detection doesn't work properly!\n"; + echo "Please check the 'mime_magic' config option or the finfo functions of PHP and run this script again.\n"; + } + if ($RCI->check_mime_extensions()) { + echo "WARNING: Mimetype to file extension mapping doesn't work properly!\n"; + echo "Please check the 'mime_types' config option and run this script again.\n"; + } + + // check database schema + if (!empty($RCI->config['db_dsnw'])) { + echo "Executing database schema update.\n"; + $success = rcmail_utils::db_update(INSTALL_PATH . 'SQL', 'roundcube', $opts['version'], ['errors' => true]); + } + + // update composer dependencies + if (is_file(INSTALL_PATH . 'composer.json') && is_readable(INSTALL_PATH . 'composer.json-dist')) { + $composer_data = json_decode(file_get_contents(INSTALL_PATH . 'composer.json'), true); + $composer_template = json_decode(file_get_contents(INSTALL_PATH . 'composer.json-dist'), true); + $composer_json = null; + + // update the require section with the new dependencies + if (!empty($composer_data['require']) && !empty($composer_template['require'])) { + $composer_data['require'] = array_merge($composer_data['require'], $composer_template['require']); + + // remove obsolete packages + $old_packages = [ + 'pear-pear.php.net/net_socket', + 'pear-pear.php.net/auth_sasl', + 'pear-pear.php.net/net_idna2', + 'pear-pear.php.net/mail_mime', + 'pear-pear.php.net/net_smtp', + 'pear-pear.php.net/crypt_gpg', + 'pear-pear.php.net/net_sieve', + 'pear/mail_mime-decode', + 'roundcube/net_sieve', + 'endroid/qrcode', + 'endroid/qr-code', + ]; + + foreach ($old_packages as $pkg) { + if (array_key_exists($pkg, $composer_data['require'])) { + unset($composer_data['require'][$pkg]); + } + } + } + + // update the repositories section with the new dependencies + if (!empty($composer_template['repositories'])) { + if (empty($composer_data['repositories'])) { + $composer_data['repositories'] = []; + } + + foreach ($composer_template['repositories'] as $repo) { + $rkey = repo_key($repo); + $existing = false; + + foreach ($composer_data['repositories'] as $k => $_repo) { + if ($rkey == repo_key($_repo)) { + // switch to https:// + if (isset($_repo['url']) && strpos($_repo['url'], 'http://') === 0) { + $composer_data['repositories'][$k]['url'] = 'https:' . substr($_repo['url'], 5); + } + + $existing = true; + break; + } + + // remove old repos + if (isset($_repo['url']) && strpos($_repo['url'], 'git://git.kolab.org') === 0) { + unset($composer_data['repositories'][$k]); + } + else if ( + $_repo['type'] == 'package' + && !empty($_repo['package']['name']) + && $_repo['package']['name'] == 'Net_SMTP' + ) { + unset($composer_data['repositories'][$k]); + } + } + + if (!$existing) { + $composer_data['repositories'][] = $repo; + } + } + + $composer_data['repositories'] = array_values($composer_data['repositories']); + } + + $composer_json = json_encode($composer_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + // write updated composer.json back to disk + if ($composer_json && is_writeable(INSTALL_PATH . 'composer.json')) { + $success &= (bool)file_put_contents(INSTALL_PATH . 'composer.json', $composer_json); + } + else { + echo "WARNING: unable to update composer.json!\n"; + echo "Please replace the 'require' section in your composer.json with the following:\n"; + + $require_json = ''; + foreach ($composer_data['require'] as $pkg => $ver) { + $require_json .= sprintf(' "%s": "%s",'."\n", $pkg, $ver); + } + + echo ' "require": {'."\n"; + echo rtrim($require_json, ",\n"); + echo "\n }\n\n"; + } + + if (!rcmail_install::vendor_dir_untouched(INSTALL_PATH)) { + $exit_code = 1; + if ($composer_bin = find_composer()) { + echo "Executing " . $composer_bin . " to update dependencies...\n"; + echo system("$composer_bin update -d " . escapeshellarg(INSTALL_PATH) . " --no-dev", $exit_code); + } + if ($exit_code != 0) { + echo "-----------------------------------------------------------------------------\n"; + echo "ATTENTION: Update dependencies by running `php composer.phar update --no-dev`\n"; + echo "-----------------------------------------------------------------------------\n"; + } + } + } + + // index contacts for fulltext searching + if ($opts['version'] && version_compare(version_parse($opts['version']), '0.6.0', '<')) { + rcmail_utils::indexcontacts(); + } + + if ($success) { + echo "This instance of Roundcube is up-to-date.\n"; + echo "Have fun!\n"; + } +} +else { + echo "This instance of Roundcube is not yet configured!\n"; + echo "Open http://url-to-roundcube/installer/ in your browser and follow the instructions.\n"; +} + +function repo_key($repo) +{ + $key = $repo['type']; + + if (!empty($repo['url'])) { + $key .= preg_replace('/^https?:/', '', $repo['url']); + } + + if (!empty($repo['package']['name'])) { + $key .= $repo['package']['name']; + } + + return $key; +} + +function find_composer() +{ + if (is_file(INSTALL_PATH . 'composer.phar')) { + return 'php composer.phar'; + } + + foreach (['composer', 'composer.phar'] as $check_file) { + $which = trim(system("which $check_file")); + if (!empty($which)) { + return $which; + } + } + + return null; +} diff --git a/ruty/mails/bin/updatecss.sh b/ruty/mails/bin/updatecss.sh new file mode 100644 index 0000000..cac7201 --- /dev/null +++ b/ruty/mails/bin/updatecss.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +// get arguments +$opts = rcube_utils::get_opt(['d' => 'dir']); + +if (empty($opts['dir'])) { + print "Skin directory not specified (--dir). Using skins/ and plugins/*/skins/.\n"; + + $dir = INSTALL_PATH . 'skins'; + $dir_p = INSTALL_PATH . 'plugins'; + $skins = glob("$dir/*", GLOB_ONLYDIR); + $skins_p = glob("$dir_p/*/skins/*", GLOB_ONLYDIR); + + $dirs = array_merge($skins, $skins_p); +} +// Check if directory exists +else if (!file_exists($opts['dir'])) { + rcube::raise_error("Specified directory doesn't exist.", false, true); +} +else { + $dirs = [$opts['dir']]; +} + +foreach ($dirs as $dir) { + $img_dir = $dir . '/images'; + if (!file_exists($img_dir)) { + continue; + } + + $files = get_files($dir); + $images = get_images($img_dir); + $find = []; + $replace = []; + + // build regexps array + foreach ($images as $path => $sum) { + $path_ex = str_replace('.', '\\.', $path); + $find[] = "#url\(['\"]?images/$path_ex(\?v=[a-f0-9-\.]+)?['\"]?\)#"; + $replace[] = "url(images/$path?v=$sum)"; + } + + foreach ($files as $file) { + $file = $dir . '/' . $file; + print "File: $file\n"; + $content = file_get_contents($file); + $content = preg_replace($find, $replace, $content, -1, $count); + if ($count) { + file_put_contents($file, $content); + } + } +} + + +function get_images($dir) +{ + $images = []; + $dh = opendir($dir); + + while ($file = readdir($dh)) { + if (preg_match('/^(.+)\.(gif|ico|png|jpg|jpeg)$/', $file, $m)) { + $filepath = "$dir/$file"; + $images[$file] = substr(md5_file($filepath), 0, 4) . '.' . filesize($filepath); + print "Image: $filepath ({$images[$file]})\n"; + } + else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) { + foreach (get_images($dir . '/' . $file) as $img => $sum) { + $images[$file . '/' . $img] = $sum; + } + } + } + + closedir($dh); + + return $images; +} + +function get_files($dir) +{ + $files = []; + $dh = opendir($dir); + + while ($file = readdir($dh)) { + if (preg_match('/^(.+)\.(css|html)$/', $file, $m)) { + $files[] = $file; + } + else if ($file != '.' && $file != '..' && is_dir($dir . '/' . $file)) { + foreach (get_files($dir . '/' . $file) as $f) { + $files[] = $file . '/' . $f; + } + } + } + + closedir($dh); + + return $files; +} diff --git a/ruty/mails/bin/updatedb.sh b/ruty/mails/bin/updatedb.sh new file mode 100644 index 0000000..133d4a4 --- /dev/null +++ b/ruty/mails/bin/updatedb.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env php + | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(__DIR__ . '/..') . '/' ); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +// get arguments +$opts = rcube_utils::get_opt([ + 'v' => 'version', + 'd' => 'dir', + 'p' => 'package', +]); + +if (empty($opts['dir'])) { + rcube::raise_error("Database schema directory not specified (--dir).", false, true); +} +if (empty($opts['package'])) { + rcube::raise_error("Database schema package name not specified (--package).", false, true); +} + +rcmail_utils::db_update($opts['dir'], $opts['package'], $opts['version'] ?? null, ['errors' => true]); diff --git a/ruty/mails/config/.htaccess b/ruty/mails/config/.htaccess new file mode 100644 index 0000000..43e24ed --- /dev/null +++ b/ruty/mails/config/.htaccess @@ -0,0 +1,7 @@ +# deny webserver access to this directory + + Require all denied + + + Deny from all + diff --git a/ruty/mails/config/config.inc.php b/ruty/mails/config/config.inc.php new file mode 100644 index 0000000..bf6db10 --- /dev/null +++ b/ruty/mails/config/config.inc.php @@ -0,0 +1,89 @@ + 'r', +// 'cache_index' => 'r', +// 'cache_thread' => 'r', +// 'cache_messages' => 'r', +]; + +// It is possible to specify database variable values e.g. some limits here. +// Use them if your server is not MySQL or for better performance. +// For example Roundcube uses max_allowed_packet value (in bytes) +// which limits query size for database cache operations. +$config['db_max_allowed_packet'] = null; + + +// ---------------------------------- +// LOGGING/DEBUGGING +// ---------------------------------- + +// log driver: 'syslog', 'stdout' or 'file'. +$config['log_driver'] = 'file'; + +// date format for log entries +// (read http://php.net/manual/en/function.date.php for all format characters) +$config['log_date_format'] = 'd-M-Y H:i:s O'; + +// length of the session ID to prepend each log line with +// set to 0 to avoid session IDs being logged. +$config['log_session_id'] = 8; + +// Default extension used for log file name +$config['log_file_ext'] = '.log'; + +// Syslog ident string to use, if using the 'syslog' log driver. +$config['syslog_id'] = 'roundcube'; + +// Syslog facility to use, if using the 'syslog' log driver. +// For possible values see installer or http://php.net/manual/en/function.openlog.php +$config['syslog_facility'] = LOG_USER; + +// Activate this option if logs should be written to per-user directories. +// Data will only be logged if a directory // exists and is writable. +$config['per_user_logging'] = false; + +// Log sent messages to /sendmail.log or to syslog +$config['smtp_log'] = true; + +// Log successful/failed logins to /userlogins.log or to syslog +$config['log_logins'] = false; + +// Log session debug information/authentication errors to /session.log or to syslog +$config['session_debug'] = false; + +// Log SQL queries to /sql.log or to syslog +$config['sql_debug'] = false; + +// Log IMAP conversation to /imap.log or to syslog +$config['imap_debug'] = false; + +// Log LDAP conversation to /ldap.log or to syslog +$config['ldap_debug'] = false; + +// Log SMTP conversation to /smtp.log or to syslog +$config['smtp_debug'] = false; + +// Log Memcache conversation to /memcache.log or to syslog +$config['memcache_debug'] = false; + +// Log APC conversation to /apc.log or to syslog +$config['apc_debug'] = false; + +// Log Redis conversation to /redis.log or to syslog +$config['redis_debug'] = false; + + +// ---------------------------------- +// IMAP +// ---------------------------------- + +// The IMAP host (and optionally port number) chosen to perform the log-in. +// Leave blank to show a textbox at login, give a list of hosts +// to display a pulldown menu or set one host as string. +// Enter hostname with prefix ssl:// to use Implicit TLS, or use +// prefix tls:// to use STARTTLS. +// If port number is omitted it will be set to 993 (for ssl://) or 143 otherwise. +// Supported replacement variables: +// %n - hostname ($_SERVER['SERVER_NAME']) +// %t - hostname without the first part +// %d - domain (http hostname $_SERVER['HTTP_HOST'] without the first part) +// %s - domain name after the '@' from e-mail address provided at login screen +// For example %n = mail.domain.tld, %t = domain.tld +// WARNING: After hostname change update of mail_host column in users table is +// required to match old user data records with the new host. +$config['imap_host'] = 'localhost:143'; + +// IMAP authentication method (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or null). +// Use 'IMAP' to authenticate with IMAP LOGIN command. +// By default the most secure method (from supported) will be selected. +$config['imap_auth_type'] = null; + +// IMAP socket context options +// See http://php.net/manual/en/context.ssl.php +// The example below enables server certificate validation +//$config['imap_conn_options'] = [ +// 'ssl' => [ +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', +// ], +// ]; +// Note: These can be also specified as an array of options indexed by hostname +$config['imap_conn_options'] = null; + +// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout) +$config['imap_timeout'] = 0; + +// Optional IMAP authentication identifier to be used as authorization proxy +$config['imap_auth_cid'] = null; + +// Optional IMAP authentication password to be used for imap_auth_cid +$config['imap_auth_pw'] = null; + +// If you know your imap's folder delimiter, you can specify it here. +// Otherwise it will be determined automatically +$config['imap_delimiter'] = null; + +// If you know your imap's folder vendor, you can specify it here. +// Otherwise it will be determined automatically. Use lower-case +// identifiers, e.g. 'dovecot', 'cyrus', 'gimap', 'hmail', 'uw-imap'. +$config['imap_vendor'] = null; + +// If IMAP server doesn't support NAMESPACE extension, but you're +// using shared folders or personal root folder is non-empty, you'll need to +// set these options. All can be strings or arrays of strings. +// Note: Folders need to be ended with directory separator, e.g. "INBOX." +// (special directory "~" is an exception to this rule) +// Note: These can be used also to overwrite server's namespaces +// Note: Set these to FALSE to disable access to specified namespace +$config['imap_ns_personal'] = null; +$config['imap_ns_other'] = null; +$config['imap_ns_shared'] = null; + +// By default IMAP capabilities are read after connection to IMAP server +// In some cases, e.g. when using IMAP proxy, there's a need to refresh the list +// after login. Set to True if you've got this case. +$config['imap_force_caps'] = false; + +// By default list of subscribed folders is determined using LIST-EXTENDED +// extension if available. Some servers (dovecot 1.x) returns wrong results +// for shared namespaces in this case. https://github.com/roundcube/roundcubemail/issues/2474 +// Enable this option to force LSUB command usage instead. +// Deprecated: Use imap_disabled_caps = ['LIST-EXTENDED'] +$config['imap_force_lsub'] = false; + +// Some server configurations (e.g. Courier) doesn't list folders in all namespaces +// Enable this option to force listing of folders in all namespaces +$config['imap_force_ns'] = false; + +// Some servers return hidden folders (name starting with a dot) +// from user home directory. IMAP RFC does not forbid that. +// Enable this option to hide them and disable possibility to create such. +$config['imap_skip_hidden_folders'] = false; + +// Some servers do not support folders with both folders and messages inside +// If your server supports that use true, if it does not, use false. +// By default it will be determined automatically (once per user session). +$config['imap_dual_use_folders'] = null; + +// List of disabled imap extensions. +// Use if your IMAP server has broken implementation of some feature +// and you can't remove it from CAPABILITY string on server-side. +// For example UW-IMAP server has broken ESEARCH. +// Note: Because the list is cached, re-login is required after change. +$config['imap_disabled_caps'] = []; + +// Log IMAP session identifiers after each IMAP login. +// This is used to relate IMAP session with Roundcube user sessions +$config['imap_log_session'] = false; + +// Type of IMAP indexes cache. Supported values: 'db', 'apc' and 'memcache' or 'memcached'. +$config['imap_cache'] = null; + +// Enables messages cache. Only 'db' cache is supported. +// This requires an IMAP server that supports QRESYNC and CONDSTORE +// extensions (RFC7162). See synchronize() in program/lib/Roundcube/rcube_imap_cache.php +// for further info, or if you experience syncing problems. +$config['messages_cache'] = false; + +// Lifetime of IMAP indexes cache. Possible units: s, m, h, d, w +$config['imap_cache_ttl'] = '10d'; + +// Lifetime of messages cache. Possible units: s, m, h, d, w +$config['messages_cache_ttl'] = '10d'; + +// Maximum cached message size in kilobytes. +// Note: On MySQL this should be less than (max_allowed_packet - 30%) +$config['messages_cache_threshold'] = 50; + + +// ---------------------------------- +// SMTP +// ---------------------------------- + +// SMTP server host (and optional port number) for sending mails. +// Enter hostname with prefix ssl:// to use Implicit TLS, or use +// prefix tls:// to use STARTTLS. +// If port number is omitted it will be set to 465 (for ssl://) or 587 otherwise. +// Supported replacement variables: +// %h - user's IMAP hostname +// %n - hostname ($_SERVER['SERVER_NAME']) +// %t - hostname without the first part +// %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 different SMTP servers for different IMAP hosts provide an array +// of IMAP host (no prefix or port) and SMTP server e.g. ['imap.example.com' => 'smtp.example.net'] +$config['smtp_host'] = 'localhost:587'; + +// SMTP username (if required) if you use %u as the username Roundcube +// will use the current username for login +$config['smtp_user'] = '%u'; + +// SMTP password (if required) if you use %p as the password Roundcube +// will use the current user's password for login +$config['smtp_pass'] = '%p'; + +// SMTP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use +// best server supported one) +$config['smtp_auth_type'] = null; + +// Optional SMTP authentication identifier to be used as authorization proxy +$config['smtp_auth_cid'] = null; + +// Optional SMTP authentication password to be used for smtp_auth_cid +$config['smtp_auth_pw'] = null; + +// Pass the username (XCLIENT LOGIN) to the server +$config['smtp_xclient_login'] = false; + +// Pass the remote IP (XCLIENT ADDR) to the server +$config['smtp_xclient_addr'] = false; + + +// SMTP HELO host +// Hostname to give to the remote server for SMTP 'HELO' or 'EHLO' messages +// Leave this blank and you will get the server variable 'server_name' or +// localhost if that isn't defined. +$config['smtp_helo_host'] = ''; + +// SMTP connection timeout, in seconds. Default: 0 (use default_socket_timeout) +// Note: There's a known issue where using ssl connection with +// timeout > 0 causes connection errors (https://bugs.php.net/bug.php?id=54511) +$config['smtp_timeout'] = 0; + +// SMTP socket context options +// See http://php.net/manual/en/context.ssl.php +// The example below enables server certificate validation, and +// requires 'smtp_timeout' to be non zero. +// $config['smtp_conn_options'] = [ +// 'ssl' => [ +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', +// ], +// ]; +// Note: These can be also specified as an array of options indexed by hostname +$config['smtp_conn_options'] = null; + + +// ---------------------------------- +// OAuth +// ---------------------------------- + +// Enable OAuth2 by defining a provider. Use 'generic' here +$config['oauth_provider'] = null; + +// Provider name to be displayed on the login button +$config['oauth_provider_name'] = 'Google'; + +// Mandatory: OAuth client ID for your Roundcube installation +$config['oauth_client_id'] = null; + +// Mandatory: OAuth client secret +$config['oauth_client_secret'] = null; + +// Mandatory: URI for OAuth user authentication (redirect) +$config['oauth_auth_uri'] = null; + +// Mandatory: Endpoint for OAuth authentication requests (server-to-server) +$config['oauth_token_uri'] = null; + +// Optional: Endpoint to query user identity if not provided in auth response +$config['oauth_identity_uri'] = null; + +// Optional: disable SSL certificate check on HTTP requests to OAuth server +// See http://docs.guzzlephp.org/en/stable/request-options.html#verify for possible values +$config['oauth_verify_peer'] = true; + +// Mandatory: OAuth scopes to request (space-separated string) +$config['oauth_scope'] = null; + +// Optional: additional query parameters to send with login request (hash array) +$config['oauth_auth_parameters'] = []; + +// Optional: array of field names used to resolve the username within the identity information +$config['oauth_identity_fields'] = null; + +// Boolean: automatically redirect to OAuth login when opening Roundcube without a valid session +$config['oauth_login_redirect'] = false; + +///// Example config for Gmail + +// Register your service at https://console.developers.google.com/ +// - use https:///index.php/login/oauth as redirect URL + +// $config['default_host'] = 'ssl://imap.gmail.com'; +// $config['oauth_provider'] = 'google'; +// $config['oauth_provider_name'] = 'Google'; +// $config['oauth_client_id'] = ""; +// $config['oauth_client_secret'] = ""; +// $config['oauth_auth_uri'] = "https://accounts.google.com/o/oauth2/auth"; +// $config['oauth_token_uri'] = "https://oauth2.googleapis.com/token"; +// $config['oauth_identity_uri'] = 'https://www.googleapis.com/oauth2/v1/userinfo'; +// $config['oauth_scope'] = "email profile openid https://mail.google.com/"; +// $config['oauth_auth_parameters'] = ['access_type' => 'offline', 'prompt' => 'consent']; + +///// Example config for Outlook.com (Office 365) + +// Register your OAuth client at https://portal.azure.com +// - use https:///index.php/login/oauth as redirect URL +// - grant permissions to Microsoft Graph API "IMAP.AccessAsUser.All", "SMTP.Send", "User.Read" and "offline_access" + +// $config['imap_host'] = 'ssl://outlook.office365.com'; +// $config['smtp_host'] = 'ssl://smtp.office365.com'; + +// $config['oauth_provider'] = 'outlook'; +// $config['oauth_provider_name'] = 'Outlook.com'; +// $config['oauth_client_id'] = ""; +// $config['oauth_client_secret'] = ""; +// $config['oauth_auth_uri'] = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; +// $config['oauth_token_uri'] = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; +// $config['oauth_identity_uri'] = "https://graph.microsoft.com/v1.0/me"; +// $config['oauth_identity_fields'] = ['email', 'userPrincipalName']; +// $config['oauth_scope'] = "https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/SMTP.Send User.Read offline_access"; +// $config['oauth_auth_parameters'] = ['nonce' => mt_rand()]; + +// ---------------------------------- +// LDAP +// ---------------------------------- + +// Type of LDAP cache. Supported values: 'db', 'apc' and 'memcache' or 'memcached'. +$config['ldap_cache'] = 'db'; + +// Lifetime of LDAP cache. Possible units: s, m, h, d, w +$config['ldap_cache_ttl'] = '10m'; + + +// ---------------------------------- +// CACHE(S) +// ---------------------------------- + +// Use these hosts for accessing memcached +// Define any number of hosts in the form of hostname:port or unix:///path/to/socket.file +// Example: ['localhost:11211', '192.168.1.12:11211', 'unix:///var/tmp/memcached.sock']; +$config['memcache_hosts'] = null; + +// Controls the use of a persistent connections to memcache servers +// See http://php.net/manual/en/memcache.addserver.php +$config['memcache_pconnect'] = true; + +// Value in seconds which will be used for connecting to the daemon +// See http://php.net/manual/en/memcache.addserver.php +$config['memcache_timeout'] = 1; + +// Controls how often a failed server will be retried (value in seconds). +// Setting this parameter to -1 disables automatic retry. +// See http://php.net/manual/en/memcache.addserver.php +$config['memcache_retry_interval'] = 15; + +// Use these hosts for accessing Redis. +// Currently only one host is supported. Cluster support may come in a future release. +// You can pass 4 fields, host, port (optional), database (optional) and password (optional). +// Unset fields will be set to the default values host=127.0.0.1, port=6379. +// Examples: +// ['localhost:6379']; +// ['192.168.1.1:6379:1:secret']; +// ['unix:///var/run/redis/redis-server.sock:1:secret']; +$config['redis_hosts'] = null; + +// Maximum size of an object in memcache (in bytes). Default: 2MB +$config['memcache_max_allowed_packet'] = '2M'; + +// Maximum size of an object in APC cache (in bytes). Default: 2MB +$config['apc_max_allowed_packet'] = '2M'; + +// Maximum size of an object in Redis cache (in bytes). Default: 2MB +$config['redis_max_allowed_packet'] = '2M'; + + +// ---------------------------------- +// SYSTEM +// ---------------------------------- + +// THIS OPTION WILL ALLOW THE INSTALLER TO RUN AND CAN EXPOSE SENSITIVE CONFIG DATA. +// ONLY ENABLE IT IF YOU'RE REALLY SURE WHAT YOU'RE DOING! +$config['enable_installer'] = false; + +// don't allow these settings to be overridden by the user +$config['dont_override'] = []; + +// List of disabled UI elements/actions +$config['disabled_actions'] = []; + +// define which settings should be listed under the 'advanced' block +// which is hidden by default +$config['advanced_prefs'] = []; + +// provide an URL where a user can get support for this Roundcube installation +// PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE! +$config['support_url'] = ''; + +// Location of the blank (watermark) frame page. By default it is the watermark.html +// file from the currently selected skin. Prepend name/path with a slash to use +// current skin folder. Remove the slash to point to a file in the Roundcube +// root directory. It can be also a full URL. +$config['blankpage_url'] = '/watermark.html'; + +// Logo image replacement. Specifies location of the image as: +// - URL relative to the document root of this Roundcube installation +// - full URL with http:// or https:// prefix +// - URL relative to the current skin folder (when starts with a '/') +// +// An array can be used to specify different logos for specific template files +// The array key specifies the place(s) the logo should be applied to and +// is made up of (up to) 3 parts: +// - skin name prefix (always with colon, can be replaced with *) +// - template name (or * for all templates) +// - logo type - it is used for logos used on multiple templates and the available types include: +// '[favicon]' for favicon +// '[print]' for logo on all print templates (e.g. messageprint, contactprint) +// '[small]' for small screen logo in supported skins +// '[dark]' and '[small-dark]' for dark mode logo in supported skins +// '[link]' for adding a URL link to the logo image +// +// Example config for skin_logo +/* + [ + // show the image /images/logo_login_small.png for the Login screen in the Elastic skin on small screens + "elastic:login[small]" => "/images/logo_login_small.png", + // show the image /images/logo_login.png for the Login screen in the Elastic skin + "elastic:login" => "/images/logo_login.png", + // add a link to the logo on the Login screen in the Elastic skin + "elastic:login[link]" => "https://www.example.com", + // add a link to the logo on all screens in the Elastic skin + "elastic:*[link]" => "https://www.example.com", + // add a link to the logo on all screens for all skins + "[link]" => "https://www.example.com", + // show the image /images/logo_small.png in the Elastic skin + "elastic:*[small]" => "/images/logo_small.png", + // show the image /images/larry.png in the Larry skin + "larry:*" => "/images/larry.png", + // show the image /images/logo_login.png on the login template in all skins + "login" => "/images/logo_login.png", + // show the image /images/logo_print.png for all print type logos in all skins + "[print]" => "/images/logo_print.png", + ]; +*/ +$config['skin_logo'] = null; + +// Automatically register user in Roundcube database on successful (IMAP) logon. +// Set to false if only registered users should be allowed to the webmail. +// Note: If disabled you have to create records in Roundcube users table by yourself. +// Note: Roundcube does not manage/create users on a mail server. +$config['auto_create_user'] = true; + +// Enables possibility to log in using email address from user identities +$config['user_aliases'] = false; + +// use this folder to store log files +// must be writeable for the user who runs PHP process (Apache user if mod_php is being used) +// This is used by the 'file' log driver. +$config['log_dir'] = RCUBE_INSTALL_PATH . 'logs/'; + +// Location of temporary saved files such as attachments and cache files +// must be writeable for the user who runs PHP process (Apache user if mod_php is being used) +$config['temp_dir'] = RCUBE_INSTALL_PATH . 'temp/'; + +// expire files in temp_dir after 48 hours +// possible units: s, m, h, d, w +$config['temp_dir_ttl'] = '48h'; + +// Enforce connections over https +// With this option enabled, all non-secure connections will be redirected. +// It can be also a port number, hostname or hostname:port if they are +// different than default HTTP_HOST:443 +$config['force_https'] = false; + +// tell PHP that it should work as under secure connection +// even if it doesn't recognize it as secure ($_SERVER['HTTPS'] is not set) +// e.g. when you're running Roundcube behind a https proxy +// this option is mutually exclusive to 'force_https' and only either one of them should be set to true. +$config['use_https'] = false; + +// Allow browser-autocompletion on login form. +// 0 - disabled, 1 - username and host only, 2 - username, host, password +$config['login_autocomplete'] = 0; + +// Forces conversion of logins to lower case. +// 0 - disabled, 1 - only domain part, 2 - domain and local part. +// If users authentication is case-insensitive this must be enabled. +// Note: After enabling it all user records need to be updated, e.g. with query: +// UPDATE users SET username = LOWER(username); +$config['login_lc'] = 2; + +// Maximum length (in bytes) of logon username and password. +$config['login_username_maxlen'] = 1024; +$config['login_password_maxlen'] = 1024; + +// Logon username filter. Regular expression for use with preg_match(). +// Use special value 'email' if you accept only full email addresses as user logins. +// Example: '/^[a-z0-9_@.-]+$/' +$config['login_username_filter'] = null; + +// Brute-force attacks prevention. +// The value specifies maximum number of failed logon attempts per minute. +$config['login_rate_limit'] = 3; + +// Includes should be interpreted as PHP files +$config['skin_include_php'] = false; + +// display product name and software version on login screen +// 0 - hide product name and version number, 1 - show product name only, 2 - show product name and version number +$config['display_product_info'] = 1; + +// Session lifetime in minutes +$config['session_lifetime'] = 30; + +// Session domain: .example.org +$config['session_domain'] = ''; + +// Session name. Default: 'roundcube_sessid' +$config['session_name'] = null; + +// Session authentication cookie name. Default: 'roundcube_sessauth' +$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' +// Make sure the Memcache extension (https://pecl.php.net/package/memcache) version >= 2.0.0 +// or the Memcached extension (https://pecl.php.net/package/memcached) version >= 2.0.0 is installed. +// +// If set to 'redis', a server needs to be specified in 'redis_hosts' +// Make sure the Redis extension (https://pecl.php.net/package/redis) version >= 2.0.0 is installed. +// +// Setting this value to 'php' will use the default session save handler configured in PHP +$config['session_storage'] = 'db'; + +// List of trusted proxies +// X_FORWARDED_* and X_REAL_IP headers are only accepted from these IPs +$config['proxy_whitelist'] = []; + +// List of trusted host names +// Attackers can modify Host header of the HTTP request causing $_SERVER['SERVER_NAME'] +// or $_SERVER['HTTP_HOST'] variables pointing to a different host, that could be used +// to collect user names and passwords. Some server configurations prevent that, but not all. +// An empty list accepts any host name. The list can contain host names +// or PCRE patterns (without // delimiters, that will be added automatically). +$config['trusted_host_patterns'] = []; + +// check client IP in session authorization +$config['ip_check'] = false; + +// X-Frame-Options HTTP header value sent to prevent from Clickjacking. +// Possible values: sameorigin|deny|allow-from . +// Set to false in order to disable sending the header. +$config['x_frame_options'] = 'sameorigin'; + +// This key is used for encrypting purposes, like storing of imap password +// in the session. For historical reasons it's called DES_key, but it's used +// with any configured cipher_method (see below). +// For the default cipher_method a required key length is 24 characters. +$config['des_key'] = 'rcmail-!24ByteDESkey*Str'; + +// Encryption algorithm. You can use any method supported by OpenSSL. +// Default is set for backward compatibility to DES-EDE3-CBC, +// but you can choose e.g. AES-256-CBC which we consider a better choice. +$config['cipher_method'] = 'DES-EDE3-CBC'; + +// Automatically add this domain to user names for login +// Only for IMAP servers that require full e-mail addresses for login +// Specify an array with 'host' => 'domain' values to support multiple hosts +// Supported replacement variables: +// %h - user's IMAP hostname +// %n - hostname ($_SERVER['SERVER_NAME']) +// %t - hostname without the first part +// %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 +$config['username_domain'] = ''; + +// Force domain configured in username_domain to be used for login. +// Any domain in username will be replaced by username_domain. +$config['username_domain_forced'] = false; + +// This domain will be used to form e-mail addresses of new users +// Specify an array with 'host' => 'domain' values to support multiple hosts +// Supported replacement variables: +// %h - user's IMAP hostname +// %n - http hostname ($_SERVER['SERVER_NAME']) +// %d - domain (http hostname without the first part) +// %z - IMAP domain (IMAP hostname without the first part) +// For example %n = mail.domain.tld, %t = domain.tld +$config['mail_domain'] = ''; + +// Password character set, to change the password for user +// authentication or for password change operations +$config['password_charset'] = 'UTF-8'; + +// How many seconds must pass between emails sent by a user +$config['sendmail_delay'] = 0; + +// Message size limit. Note that SMTP server(s) may use a different value. +// This limit is verified when user attaches files to a composed message. +// Size in bytes (possible unit suffix: K, M, G) +$config['max_message_size'] = '100M'; + +// Maximum number of recipients per message (including To, Cc, Bcc). +// Default: 0 (no limit) +$config['max_recipients'] = 0; + +// Maximum number of recipients per message excluding Bcc header. +// This is a soft limit, which means we only display a warning to the user. +// Default: 5 +$config['max_disclosed_recipients'] = 5; + +// Maximum allowed number of members of an address group. Default: 0 (no limit) +// If 'max_recipients' is set this value should be less or equal +$config['max_group_members'] = 0; + +// Name your service. This is displayed on the login screen and in the window title +$config['product_name'] = 'Roundcube Webmail'; + +// Add this user-agent to message headers when sending. Default: not set. +$config['useragent'] = null; + +// try to load host-specific configuration +// see https://github.com/roundcube/roundcubemail/wiki/Configuration:-Multi-Domain-Setup +// for more details +$config['include_host_config'] = false; + +// path to a text file which will be added to each sent message +// paths are relative to the Roundcube root folder +$config['generic_message_footer'] = ''; + +// path to a text file which will be added to each sent HTML message +// paths are relative to the Roundcube root folder +$config['generic_message_footer_html'] = ''; + +// add a received header to outgoing mails containing the creators IP and hostname +$config['http_received_header'] = false; + +// Whether or not to encrypt the IP address and the host name +// these could, in some circles, be considered as sensitive information; +// however, for the administrator, these could be invaluable help +// when tracking down issues. +$config['http_received_header_encrypt'] = false; + +// number of chars allowed for line when wrapping text. +// text wrapping is done when composing/sending messages +$config['line_length'] = 72; + +// send plaintext messages as format=flowed +$config['send_format_flowed'] = true; + +// According to RFC2298, return receipt envelope sender address must be empty. +// If this option is true, Roundcube will use user's identity as envelope sender for MDN responses. +$config['mdn_use_from'] = false; + +// Set identities access level: +// 0 - many identities with possibility to edit all params +// 1 - many identities with possibility to edit all params but not email address +// 2 - one identity with possibility to edit all params +// 3 - one identity with possibility to edit all params but not email address +// 4 - one identity with possibility to edit only signature +$config['identities_level'] = 0; + +// Maximum size of uploaded image (in kilobytes) for HTML identities. +// Images (in html signatures) are stored in database as data URIs. +$config['identity_image_size'] = 64; + +// Maximum size of uploaded image (in kilobytes) for HTML responses. +// Images (in html responses) are stored in database as data URIs. +$config['response_image_size'] = 64; + +// Mimetypes supported by the browser. +// Attachments of these types will open in a preview window. +// Either a comma-separated list or an array. Default list includes: +// text/plain,text/html, +// image/jpeg,image/gif,image/png,image/bmp,image/tiff,image/webp, +// application/x-javascript,application/pdf,application/x-shockwave-flash +$config['client_mimetypes'] = null; + +// Path to a local mime magic database file for PHPs finfo extension. +// Set to null if the default path should be used. +$config['mime_magic'] = null; + +// Absolute path to a local mime.types mapping table file. +// This is used to derive mime-types from the filename extension or vice versa. +// Such a file is usually part of the apache webserver. If you don't find a file named mime.types on your system, +// download it from http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types +$config['mime_types'] = null; + +// path to imagemagick identify binary (if not set we'll use Imagick or GD extensions) +$config['im_identify_path'] = null; + +// path to imagemagick convert binary (if not set we'll use Imagick or GD extensions) +$config['im_convert_path'] = null; + +// Size of thumbnails from image attachments displayed below the message content. +// Note: whether images are displayed at all depends on the 'inline_images' option. +// Set to 0 to display images in full size. +$config['image_thumbnail_size'] = 240; + +// maximum size of uploaded contact photos in pixel +$config['contact_photo_size'] = 160; + +// Enable DNS checking for e-mail address validation +$config['email_dns_check'] = false; + +// Disables saving sent messages in Sent folder (like gmail) (Default: false) +// Note: useful when SMTP server stores sent mail in user mailbox +$config['no_save_sent_messages'] = false; + +// Improve system security by using special URL with security token. +// This can be set to a number defining token length. Default: 16. +// Warning: This requires http server configuration. Sample: +// RewriteRule ^/roundcubemail/[a-zA-Z0-9]{16}/(.*) /roundcubemail/$1 [PT] +// Alias /roundcubemail /var/www/roundcubemail/ +// Warning: This feature does NOT work with request_path = 'SCRIPT_NAME' +// Note: Use assets_path to not prevent the browser from caching assets +$config['use_secure_urls'] = false; + +// Specifies the full path of the original HTTP request, either as a real path or +// $_SERVER field name. This might be useful when Roundcube runs behind a reverse +// proxy using a subpath. This is a path part of the URL, not the full URL! +// The reverse proxy config can specify a custom header (e.g. X-Forwarded-Path) containing +// the path under which Roundcube is exposed to the outside world (e.g. /rcube/). +// This header value is then available in PHP with $_SERVER['HTTP_X_FORWARDED_PATH']. +// By default the path comes from 'REDIRECT_SCRIPT_URL', 'SCRIPT_NAME' or 'REQUEST_URI', +// whichever is set (in this order). +$config['request_path'] = null; + +// Allows to define separate server/path for image/js/css files +// Warning: If the domain is different cross-domain access to some +// resources need to be allowed +// Sample: +// +// Header set Access-Control-Allow-Origin "*" +// +$config['assets_path'] = ''; + +// While assets_path is for the browser, assets_dir informs +// PHP code about the location of asset files in filesystem +$config['assets_dir'] = ''; + +// Options passed when creating Guzzle HTTP client, used to fetch remote content +// For example: +// [ +// 'timeout' => 10, +// 'proxy' => 'tcp://localhost:8125', +// ] +$config['http_client'] = []; + +// List of supported subject prefixes for a message reply +// This list is used to clean the subject when replying or sorting messages +$config['subject_reply_prefixes'] = ['Re:']; + +// List of supported subject prefixes for a message forward +// This list is used to clean the subject when forwarding or sorting messages +$config['subject_forward_prefixes'] = ['Fwd:', 'Fw:']; + +// Prefix to use in subject when replying to a message +$config['response_prefix'] = 'Re:'; + +// Prefix to use in subject when forwarding a message +$config['forward_prefix'] = 'Fwd:'; + +// ---------------------------------- +// PLUGINS +// ---------------------------------- + +// List of active plugins (in plugins/ directory) +$config['plugins'] = []; + +// ---------------------------------- +// USER INTERFACE +// ---------------------------------- + +// default messages sort column. Use empty value for default server's sorting, +// or 'arrival', 'date', 'subject', 'from', 'to', 'fromto', 'size', 'cc' +$config['message_sort_col'] = ''; + +// default messages sort order +$config['message_sort_order'] = 'DESC'; + +// These cols are shown in the message list. Available cols are: +// subject, from, to, fromto, cc, replyto, date, size, status, flag, attachment, priority +$config['list_cols'] = ['subject', 'status', 'fromto', 'date', 'size', 'flag', 'attachment']; + +// the default locale setting (leave empty for auto-detection) +// RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR +$config['language'] = null; + +// use this format for date display (PHP DateTime format) +$config['date_format'] = 'Y-m-d'; + +// give this choice of date formats to the user to select from +// Note: do not use ambiguous formats like m/d/Y +$config['date_formats'] = ['Y-m-d', 'Y/m/d', 'Y.m.d', 'd-m-Y', 'd/m/Y', 'd.m.Y', 'j.n.Y']; + +// use this format for time display (PHP DateTime format) +$config['time_format'] = 'H:i'; + +// give this choice of time formats to the user to select from +$config['time_formats'] = ['G:i', 'H:i', 'g:i a', 'h:i A']; + +// use this format for short date display (derived from date_format and time_format) +$config['date_short'] = 'D H:i'; + +// use this format for detailed date/time formatting (derived from date_format and time_format) +$config['date_long'] = 'Y-m-d H:i'; + +// store draft message is this mailbox +// leave blank if draft messages should not be stored +// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP) +$config['drafts_mbox'] = 'Drafts'; + +// store spam messages in this mailbox +// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP) +$config['junk_mbox'] = 'Junk'; + +// store sent message is this mailbox +// leave blank if sent messages should not be stored +// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP) +$config['sent_mbox'] = 'Sent'; + +// move messages to this folder when deleting them +// leave blank if they should be deleted directly +// NOTE: Use folder names with namespace prefix (INBOX. on Courier-IMAP) +$config['trash_mbox'] = 'Trash'; + +// automatically create the above listed default folders on user login +$config['create_default_folders'] = false; + +// protect the default folders from renames, deletes, and subscription changes +$config['protect_default_folders'] = true; + +// Disable localization of the default folder names listed above +$config['show_real_foldernames'] = false; + +// if in your system 0 quota means no limit set this option to true +$config['quota_zero_as_unlimited'] = false; + +// Make use of the built-in spell checker. +$config['enable_spellcheck'] = false; + +// Enables spellchecker exceptions dictionary. +// Setting it to 'shared' will make the dictionary shared by all users. +$config['spellcheck_dictionary'] = false; + +// Set the spell checking engine. Possible values: +// - 'googie' - the default (also used for connecting to Nox Spell Server, see 'spellcheck_uri' setting) +// - 'pspell' - requires the PHP Pspell module and aspell installed +// - 'enchant' - requires the PHP Enchant module +// - 'atd' - install your own After the Deadline server or check with the people at http://www.afterthedeadline.com before using their API +// Since Google shut down their public spell checking service, the default settings +// connect to http://spell.roundcube.net which is a hosted service provided by Roundcube. +// You can connect to any other googie-compliant service by setting 'spellcheck_uri' accordingly. +$config['spellcheck_engine'] = 'googie'; + +// For locally installed Nox Spell Server or After the Deadline services, +// please specify the URI to call it. +// Get Nox Spell Server from http://orangoo.com/labs/?page_id=72 or +// the After the Deadline package from http://www.afterthedeadline.com. +// Leave empty to use the public API of service.afterthedeadline.com +$config['spellcheck_uri'] = ''; + +// These languages can be selected for spell checking. +// Configure as a PHP style hash array: ['en'=>'English', 'de'=>'Deutsch']; +// Leave empty for default set of available language. +$config['spellcheck_languages'] = null; + +// Makes that words with all letters capitalized will be ignored (e.g. GOOGLE) +$config['spellcheck_ignore_caps'] = false; + +// Makes that words with numbers will be ignored (e.g. g00gle) +$config['spellcheck_ignore_nums'] = false; + +// Makes that words with symbols will be ignored (e.g. g@@gle) +$config['spellcheck_ignore_syms'] = false; + +// Number of lines at the end of a message considered to contain the signature. +// Increase this value if signatures are not properly detected and colored +$config['sig_max_lines'] = 15; + +// don't let users set pagesize to more than this value if set +$config['max_pagesize'] = 200; + +// Minimal value of user's 'refresh_interval' setting (in seconds) +$config['min_refresh_interval'] = 60; + +// Specifies for how many seconds the Undo button will be available +// after object delete action. Currently used with supporting address book sources. +// Setting it to 0, disables the feature. +$config['undo_timeout'] = 0; + +// A static list of canned responses which are immutable for the user +$config['compose_responses_static'] = [ +// ['name' => 'Canned Response 1', 'text' => 'Static Response One'], +// ['name' => 'Canned Response 2', 'text' => 'Static Response Two'], +]; + +// List of HKP key servers for PGP public key lookups in Enigma/Mailvelope +// Note: Lookup is client-side, so the server must support Cross-Origin Resource Sharing +$config['keyservers'] = ['keys.openpgp.org']; + +// Enables use of the Main Keyring in Mailvelope? If disabled, a per-site keyring +// will be used. This is set to false for backwards compatibility. +$config['mailvelope_main_keyring'] = false; + +// Mailvelope RSA bit size for newly generated keys, either 2048 or 4096. +// It maybe desirable to use 2048 for sites with many mobile users. +$config['mailvelope_keysize'] = 4096; + +// Html2Text link handling options: +// 0 - links will be removed +// 1 - a list of link URLs should be listed at the end of the text (default) +// 2 - link should be displayed to the original point in the text they appeared +$config['html2text_links'] = 1; + +// Html2Text text width. Maximum width of the formatted text, in columns. Default: 75. +$config['html2text_width'] = 75; + +// ---------------------------------- +// ADDRESSBOOK SETTINGS +// ---------------------------------- + +// This indicates which type of address book to use. Possible choices: +// 'sql' - built-in sql addressbook enabled (default), +// '' - built-in sql addressbook disabled. +// Still LDAP or plugin-added addressbooks will be available. +// BC Note: The value can actually be anything except 'sql', it does not matter. +$config['address_book_type'] = 'sql'; + +// In order to enable public ldap search, configure an array like the Verisign +// example further below. if you would like to test, simply uncomment the example. +// Array key must contain only safe characters, ie. a-zA-Z0-9_ +$config['ldap_public'] = []; + +// If you are going to use LDAP for individual address books, you will need to +// set 'user_specific' to true and use the variables to generate the appropriate DNs to access it. +// +// The recommended directory structure for LDAP is to store all the address book entries +// under the users main entry, e.g.: +// +// o=root +// ou=people +// uid=user@domain +// mail=contact@contactdomain +// +// So the base_dn would be uid=%fu,ou=people,o=root +// The bind_dn would be the same as based_dn or some super user login. +/* + * example config for Verisign directory + * +$config['ldap_public']['Verisign'] = [ + 'name' => 'Verisign.com', + // Replacement variables supported in host names: + // %h - user's IMAP hostname + // %n - hostname ($_SERVER['SERVER_NAME']) + // %t - hostname without the first part + // %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) + // Note: If port number is omitted, it will be set to 636 (for ldaps://) or 389 otherwise. + // Note: To enable TLS use tls:// prefix + 'hosts' => array('directory.verisign.com:389'), + 'ldap_version' => 3, // using LDAPv3 + 'network_timeout' => 10, // The timeout (in seconds) for connect + bind attempts. This is only supported in PHP >= 5.3.0 with OpenLDAP 2.x + 'user_specific' => false, // If true the base_dn, bind_dn and bind_pass default to the user's IMAP login. + // When 'user_specific' is enabled following variables can be used in base_dn/bind_dn config: + // %fu - The full username provided, assumes the username is an email + // address, uses the username_domain value if not an email address. + // %u - The username prior to the '@'. + // %d - The domain name after the '@'. + // %dc - The domain name hierarchal string e.g. "dc=test,dc=domain,dc=com" + // %dn - DN found by ldap search when search_filter/search_base_dn are used + 'base_dn' => '', + 'bind_dn' => '', + 'bind_pass' => '', + // It's possible to bind for an individual address book + // The login name is used to search for the DN to bind with + 'search_base_dn' => '', + 'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))' + // DN and password to bind as before searching for bind DN, if anonymous search is not allowed + 'search_bind_dn' => '', + 'search_bind_pw' => '', + // Base DN and filter used for resolving the user's domain root DN which feeds the %dc variables + // Leave empty to skip this lookup and derive the root DN from the username domain + 'domain_base_dn' => '', + 'domain_filter' => '', + // Optional map of replacement strings => attributes used when binding for an individual address book + 'search_bind_attrib' => [], // e.g. ['%udc' => 'ou'] + // Default for %dn variable if search doesn't return DN value + 'search_dn_default' => '', + // Optional authentication identifier to be used as SASL authorization proxy + // bind_dn need to be empty + 'auth_cid' => '', + // SASL authentication method (for proxy auth), e.g. DIGEST-MD5 + 'auth_method' => '', + // Indicates if the addressbook shall be hidden from the list. + // With this option enabled you can still search/view contacts. + 'hidden' => false, + // Indicates if the addressbook shall not list contacts but only allows searching. + 'searchonly' => false, + // Indicates if we can write to the LDAP directory or not. + // If writable is true then these fields need to be populated: + // LDAP_Object_Classes, required_fields, LDAP_rdn + 'writable' => false, + // To create a new contact these are the object classes to specify + // (or any other classes you wish to use). + 'LDAP_Object_Classes' => ['top', 'inetOrgPerson'], + // The RDN field that is used for new entries, this field needs + // to be one of the search_fields, the base of base_dn is appended + // to the RDN to insert into the LDAP directory. + 'LDAP_rdn' => 'cn', + // The required attributes needed to build a new contact as required by + // the object classes (can include additional fields not required by the object classes). + 'required_fields' => ['cn', 'sn', 'mail'], + // The attributes used when searching with "All fields" option + // If empty, attributes for name, surname, firstname and email fields will be used + 'search_fields' => ['mail', 'cn'], + // mapping of contact fields to directory attributes + // 1. for every attribute one can specify the number of values (limit) allowed. + // default is 1, a wildcard * means unlimited + // 2. another possible parameter is separator character for composite fields + // 3. it's possible to define field format for write operations, e.g. for date fields + // example: 'birthday:date[YmdHis\\Z]' + 'fieldmap' => [ + // Roundcube => LDAP:limit + 'name' => 'cn', + 'surname' => 'sn', + 'firstname' => 'givenName', + 'jobtitle' => 'title', + 'email' => 'mail:*', + 'phone:home' => 'homePhone', + 'phone:work' => 'telephoneNumber', + 'phone:mobile' => 'mobile', + 'phone:pager' => 'pager', + 'phone:workfax' => 'facsimileTelephoneNumber', + 'street' => 'street', + 'zipcode' => 'postalCode', + 'region' => 'st', + 'locality' => 'l', + // if you country is a complex object, you need to configure 'sub_fields' below + 'country' => 'c', + 'organization' => 'o', + 'department' => 'ou', + 'jobtitle' => 'title', + 'notes' => 'description', + 'photo' => 'jpegPhoto', + // these currently don't work: + // 'manager' => 'manager', + // 'assistant' => 'secretary', + ], + // Map of contact sub-objects (attribute name => objectClass(es)), e.g. 'c' => 'country' + 'sub_fields' => [], + // Generate values for the following LDAP attributes automatically when creating a new record + 'autovalues' => [ + // 'uid' => 'md5(microtime())', // You may specify PHP code snippets which are then eval'ed + // 'mail' => '{givenname}.{sn}@mydomain.com', // or composite strings with placeholders for existing attributes + ], + 'sort' => 'cn', // The field to sort the listing by. + 'scope' => 'sub', // search mode: sub|base|list + 'filter' => '(objectClass=inetOrgPerson)', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act + 'fuzzy_search' => true, // server allows wildcard search + 'vlv' => false, // Enable Virtual List View to more efficiently fetch paginated data (if server supports it) + 'vlv_search' => false, // Use Virtual List View functions for autocompletion searches (if server supports it) + 'numsub_filter' => '(objectClass=organizationalUnit)', // with VLV, we also use numSubOrdinates to query the total number of records. Set this filter to get all numSubOrdinates attributes for counting + 'config_root_dn' => 'cn=config', // Root DN to search config entries (e.g. vlv indexes) + 'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit. + 'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit. + 'referrals' => false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups + 'dereference' => 0, // Sets the LDAP_OPT_DEREF option. One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS + // Used where addressbook contains aliases to objects elsewhere in the LDAP tree. + + // definition for contact groups (uncomment if no groups are supported) + // for the groups base_dn, the user replacements %fu, %u, %d and %dc work as for base_dn (see above) + // if the groups base_dn is empty, the contact base_dn is used for the groups as well + // -> in this case, assure that groups and contacts are separated due to the concerning filters! + 'groups' => [ + 'base_dn' => '', + 'scope' => 'sub', // Search mode: sub|base|list + 'filter' => '(objectClass=groupOfNames)', + 'object_classes' => ['top', 'groupOfNames'], // Object classes to be assigned to new groups + 'member_attr' => 'member', // Name of the default member attribute, e.g. uniqueMember + 'name_attr' => 'cn', // Attribute to be used as group name + 'email_attr' => 'mail', // Group email address attribute (e.g. for mailing lists) + 'member_filter' => '(objectclass=*)', // Optional filter to use when querying for group members + 'vlv' => false, // Use VLV controls to list groups + 'class_member_attr' => [ // Mapping of group object class to member attribute used in these objects + 'groupofnames' => 'member', + 'groupofuniquenames' => 'uniquemember' + ], + ], + // this configuration replaces the regular groups listing in the directory tree with + // a hard-coded list of groups, each listing entries with the configured base DN and filter. + // if the 'groups' option from above is set, it'll be shown as the first entry with the name 'Groups' + 'group_filters' => [ + 'departments' => [ + 'name' => 'Company Departments', + 'scope' => 'list', + 'base_dn' => 'ou=Groups,dc=mydomain,dc=com', + 'filter' => '(|(objectclass=groupofuniquenames)(objectclass=groupofurls))', + 'name_attr' => 'cn', + ], + 'customers' => [ + 'name' => 'Customers', + 'scope' => 'sub', + 'base_dn' => 'ou=Customers,dc=mydomain,dc=com', + 'filter' => '(objectClass=inetOrgPerson)', + 'name_attr' => 'sn', + ], + ], +]; +*/ + +// An ordered array of the ids of the addressbooks that should be searched +// when populating address autocomplete fields server-side. ex: ['sql','Verisign']; +$config['autocomplete_addressbooks'] = ['sql']; + +// The minimum number of characters required to be typed in an autocomplete field +// before address books will be searched. Most useful for LDAP directories that +// may need to do lengthy results building given overly-broad searches +$config['autocomplete_min_length'] = 1; + +// Number of parallel autocomplete requests. +// If there's more than one address book, n parallel (async) requests will be created, +// where each request will search in one address book. By default (0), all address +// books are searched in one request. +$config['autocomplete_threads'] = 0; + +// Max. number of entries in autocomplete popup. Default: 15. +$config['autocomplete_max'] = 15; + +// show address fields in this order +// available placeholders: {street}, {locality}, {zipcode}, {country}, {region} +$config['address_template'] = '{street}
{locality} {zipcode}
{country} {region}'; + +// Matching mode for addressbook search (including autocompletion) +// 0 - partial (*abc*), default +// 1 - strict (abc) +// 2 - prefix (abc*) +// Note: For LDAP sources fuzzy_search must be enabled to use 'partial' or 'prefix' mode +$config['addressbook_search_mode'] = 0; + +// List of fields used on contacts list and for autocompletion searches +// Warning: These are field names not LDAP attributes (see 'fieldmap' setting)! +$config['contactlist_fields'] = ['name', 'firstname', 'surname', 'email']; + +// Template of contact entry on the autocompletion list. +// You can use contact fields as: name, email, organization, department, etc. +// See program/actions/contacts/index.php for a list +$config['contact_search_name'] = '{name} <{email}>'; + +// Contact mode. If your contacts are mostly business, switch it to 'business'. +// This will prioritize form fields related to 'work' (instead of 'home'). +// Default: 'private'. +$config['contact_form_mode'] = 'private'; + +// The addressbook source to store automatically collected recipients in. +// Default: true (the built-in "Collected recipients" addressbook, source id = '1') +// Note: It can be set to any writeable addressbook, e.g. 'sql' +$config['collected_recipients'] = true; + +// The addressbook source to store trusted senders in. +// Default: true (the built-in "Trusted senders" addressbook, source id = '2') +// Note: It can be set to any writeable addressbook, e.g. 'sql' +$config['collected_senders'] = true; + + +// ---------------------------------- +// USER PREFERENCES +// ---------------------------------- + +// Use this charset as fallback for message decoding +$config['default_charset'] = 'ISO-8859-1'; + +// Skin name: folder from skins/ +$config['skin'] = 'elastic'; + +// Limit skins available for the user. +// Note: When not empty, it should include the default skin set in 'skin' option. +$config['skins_allowed'] = []; + +// Enables using standard browser windows (that can be handled as tabs) +// instead of popup windows +$config['standard_windows'] = false; + +// show up to X items in messages list view +$config['mail_pagesize'] = 50; + +// show up to X items in contacts list view +$config['addressbook_pagesize'] = 50; + +// sort contacts by this col (preferably either one of name, firstname, surname) +$config['addressbook_sort_col'] = 'surname'; + +// The way how contact names are displayed in the list. +// 0: prefix firstname middlename surname suffix (only if display name is not set) +// 1: firstname middlename surname +// 2: surname firstname middlename +// 3: surname, firstname middlename +$config['addressbook_name_listing'] = 0; + +// use this timezone to display date/time +// valid timezone identifiers are listed here: php.net/manual/en/timezones.php +// 'auto' will use the browser's timezone settings +$config['timezone'] = 'auto'; + +// prefer displaying HTML messages +$config['prefer_html'] = true; + +// Display remote resources (inline images, styles) in HTML messages. Default: 0. +// 0 - Never, always ask +// 1 - Allow from my contacts (all writeable addressbooks + collected senders and recipients) +// 2 - Always allow +// 3 - Allow from trusted senders only +$config['show_images'] = 0; + +// open messages in new window +$config['message_extwin'] = false; + +// open message compose form in new window +$config['compose_extwin'] = false; + +// compose html formatted messages by default +// 0 - never, +// 1 - always, +// 2 - on reply to HTML message, +// 3 - on forward or reply to HTML message +// 4 - always, except when replying to plain text message +$config['htmleditor'] = 0; + +// save copies of compose messages in the browser's local storage +// for recovery in case of browser crashes and session timeout. +$config['compose_save_localstorage'] = true; + +// show pretty dates as standard +$config['prettydate'] = true; + +// save compose message every 300 seconds (5min) +$config['draft_autosave'] = 300; + +// Interface layout. Default: 'widescreen'. +// 'widescreen' - three columns +// 'desktop' - two columns, preview on bottom +// 'list' - two columns, no preview +$config['layout'] = 'widescreen'; + +// Mark as read when viewing a message (delay in seconds) +// Set to -1 if messages should not be marked as read +$config['mail_read_time'] = 0; + +// Clear Trash on logout. Remove all messages or only older than N days. +// Supported values: false, true, 30, 60, 90. Default: false. +$config['logout_purge'] = false; + +// Compact INBOX on logout +$config['logout_expunge'] = false; + +// Display attached images below the message body +$config['inline_images'] = true; + +// Encoding of long/non-ascii attachment names: +// 0 - Full RFC 2231 compatible +// 1 - RFC 2047 for 'name' and RFC 2231 for 'filename' parameter (Thunderbird's default) +// 2 - Full 2047 compatible +$config['mime_param_folding'] = 1; + +// Set true if deleted messages should not be displayed +// This will make the application run slower +$config['skip_deleted'] = false; + +// Set true to Mark deleted messages as read as well as deleted +// False means that a message's read status is not affected by marking it as deleted +$config['read_when_deleted'] = true; + +// Set to true to never delete messages immediately +// Use 'Purge' to remove messages marked as deleted +$config['flag_for_deletion'] = false; + +// Default interval for auto-refresh requests (in seconds) +// These are requests for system state updates e.g. checking for new messages, etc. +// Setting it to 0 disables the feature. +$config['refresh_interval'] = 60; + +// If true all folders will be checked for recent messages +$config['check_all_folders'] = false; + +// If true, after message/contact delete/move, the next message/contact will be displayed +$config['display_next'] = true; + +// Default messages listing mode. One of 'threads' or 'list'. +$config['default_list_mode'] = 'list'; + +// 0 - Do not expand threads +// 1 - Expand all threads automatically +// 2 - Expand only threads with unread messages +$config['autoexpand_threads'] = 0; + +// When replying: +// -1 - don't cite the original message +// 0 - place cursor below the original message +// 1 - place cursor above original message (top posting) +// 2 - place cursor above original message (top posting), but do not indent the quote +$config['reply_mode'] = 0; + +// When replying strip original signature from message +$config['strip_existing_sig'] = true; + +// Show signature: +// 0 - Never +// 1 - Always +// 2 - New messages only +// 3 - Forwards and Replies only +$config['show_sig'] = 1; + +// By default the signature is placed depending on cursor position (reply_mode). +// Sometimes it might be convenient to start the reply on top but keep +// the signature below the quoted text (sig_below = true). +$config['sig_below'] = false; + +// Enables adding of standard separator to the signature +$config['sig_separator'] = true; + +// Use MIME encoding (quoted-printable) for 8bit characters in message body +$config['force_7bit'] = false; + +// Default fields configuration for mail search. +// The array can contain a per-folder list of header fields which should be considered when searching +// The entry with key '*' stands for all folders which do not have a specific list set. +// Supported fields: subject, from, to, cc, bcc, replyto, followupto, body, text. +// Please note that folder names should to be in sync with $config['*_mbox'] options +$config['search_mods'] = null; // Example: ['*' => ['subject'=>1, 'from'=>1], 'Sent' => ['subject'=>1, 'to'=>1]]; + +// Defaults of the addressbook search field configuration. +$config['addressbook_search_mods'] = null; // Example: ['name'=>1, 'firstname'=>1, 'surname'=>1, 'email'=>1, '*'=>1]; + +// Directly delete messages in Junk instead of moving to Trash +$config['delete_junk'] = false; + +// Behavior if a received message requests a message delivery notification (read receipt) +// 0 = ask the user, +// 1 = send automatically, +// 2 = ignore (never send or ask) +// 3 = send automatically if sender is in my contacts, otherwise ask the user +// 4 = send automatically if sender is in my contacts, otherwise ignore +// 5 = send automatically if sender is a trusted sender, otherwise ask the user +// 6 = send automatically if sender is a trusted sender, otherwise ignore +$config['mdn_requests'] = 0; + +// Return receipt checkbox default state +$config['mdn_default'] = 0; + +// Delivery Status Notification checkbox default state +$config['dsn_default'] = 0; + +// Place replies in the folder of the message being replied to +$config['reply_same_folder'] = false; + +// Sets default mode of Forward feature to "forward as attachment" +$config['forward_attachment'] = false; + +// Defines address book (internal index) to which new contacts will be added +// By default it is the first writeable addressbook. +// Note: Use '0' for built-in address book. +$config['default_addressbook'] = null; + +// Enables spell checking before sending a message. +$config['spellcheck_before_send'] = false; + +// Skip alternative email addresses in autocompletion (show one address per contact) +$config['autocomplete_single'] = false; + +// Default font for composed HTML message. +// Supported values: Andale Mono, Arial, Arial Black, Book Antiqua, Courier New, +// Georgia, Helvetica, Impact, Tahoma, Terminal, Times New Roman, Trebuchet MS, Verdana +$config['default_font'] = 'Verdana'; + +// Default font size for composed HTML message. +// Supported sizes: 8pt, 10pt, 12pt, 14pt, 18pt, 24pt, 36pt +$config['default_font_size'] = '10pt'; + +// Enables display of email address with name instead of a name (and address in title) +$config['message_show_email'] = false; + +// Default behavior of Reply-All button: +// 0 - Reply-All always +// 1 - Reply-List if mailing list is detected +$config['reply_all_mode'] = 0; diff --git a/ruty/mails/config/mimetypes.php b/ruty/mails/config/mimetypes.php new file mode 100644 index 0000000..efb4698 --- /dev/null +++ b/ruty/mails/config/mimetypes.php @@ -0,0 +1,56 @@ + 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xla' => 'application/vnd.ms-excel', + 'xlc' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xlw' => 'application/vnd.ms-excel', + 'pdf' => 'application/pdf', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'pot' => 'application/vnd.ms-powerpoint', + 'doc' => 'application/msword', + 'dot' => 'application/msword', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'otf' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'otm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xps' => 'application/vnd.ms-xpsdocument', + 'rar' => 'application/x-rar-compressed', + '7z' => 'application/x-7z-compressed', + 's7z' => 'application/x-7z-compressed', + 'vcf' => 'text/vcard', + 'ics' => 'text/calendar', +]; diff --git a/ruty/mails/installer/check.php b/ruty/mails/installer/check.php new file mode 100644 index 0000000..e7c941d --- /dev/null +++ b/ruty/mails/installer/check.php @@ -0,0 +1,290 @@ + | + +-----------------------------------------------------------------------+ +*/ + +if (!class_exists('rcmail_install', false) || !isset($RCI)) { + die("Not allowed! Please open installer/index.php instead."); +} + +$required_php_exts = [ + 'PCRE' => 'pcre', + 'DOM' => 'dom', + 'Session' => 'session', + 'XML' => 'xml', + 'Intl' => 'intl', + 'JSON' => 'json', + 'PDO' => 'PDO', + 'Multibyte' => 'mbstring', + 'OpenSSL' => 'openssl', + 'Filter' => 'filter', + 'Ctype' => 'ctype', +]; + +$optional_php_exts = [ + 'cURL' => 'curl', + 'FileInfo' => 'fileinfo', + 'Exif' => 'exif', + 'Iconv' => 'iconv', + 'LDAP' => 'ldap', + 'GD' => 'gd', + 'Imagick' => 'imagick', + 'XMLWriter' => 'xmlwriter', + 'Zip' => 'zip', +]; + +$required_libs = [ + 'PEAR' => 'pear.php.net', + 'Auth_SASL' => 'pear.php.net', + 'Net_SMTP' => 'pear.php.net', + 'Mail_mime' => 'pear.php.net', + 'GuzzleHttp\Client' => 'github.com/guzzle/guzzle', +]; + +$optional_libs = [ + 'Net_LDAP3' => 'git.kolab.org', +]; + +$ini_checks = [ + 'file_uploads' => 1, + 'session.auto_start' => 0, + 'mbstring.func_overload' => 0, + 'suhosin.session.encrypt' => 0, +]; + +$optional_checks = [ + 'date.timezone' => '-VALID-', +]; + +$source_urls = [ + 'cURL' => 'https://www.php.net/manual/en/book.curl.php', + 'Sockets' => 'https://www.php.net/manual/en/book.sockets.php', + 'Session' => 'https://www.php.net/manual/en/book.session.php', + 'PCRE' => 'https://www.php.net/manual/en/book.pcre.php', + 'FileInfo' => 'https://www.php.net/manual/en/book.fileinfo.php', + 'Multibyte' => 'https://www.php.net/manual/en/book.mbstring.php', + 'OpenSSL' => 'https://www.php.net/manual/en/book.openssl.php', + 'JSON' => 'https://www.php.net/manual/en/book.json.php', + 'DOM' => 'https://www.php.net/manual/en/book.dom.php', + 'Iconv' => 'https://www.php.net/manual/en/book.iconv.php', + 'Intl' => 'https://www.php.net/manual/en/book.intl.php', + 'Exif' => 'https://www.php.net/manual/en/book.exif.php', + 'oci8' => 'https://www.php.net/manual/en/book.oci8.php', + 'PDO' => 'https://www.php.net/manual/en/book.pdo.php', + 'LDAP' => 'https://www.php.net/manual/en/book.ldap.php', + 'GD' => 'https://www.php.net/manual/en/book.image.php', + 'Imagick' => 'https://www.php.net/manual/en/book.imagick.php', + 'XML' => 'https://www.php.net/manual/en/book.xml.php', + 'XMLWriter' => 'https://www.php.net/manual/en/book.xmlwriter.php', + 'Zip' => 'https://www.php.net/manual/en/book.zip.php', + 'Filter' => 'https://www.php.net/manual/en/book.filter.php', + 'Ctype' => 'https://www.php.net/manual/en/book.ctype.php', + 'pdo_mysql' => 'https://www.php.net/manual/en/ref.pdo-mysql.php', + 'pdo_pgsql' => 'https://www.php.net/manual/en/ref.pdo-pgsql.php', + 'pdo_sqlite' => 'https://www.php.net/manual/en/ref.pdo-sqlite.php', + 'pdo_sqlite2' => 'https://www.php.net/manual/en/ref.pdo-sqlite.php', + 'pdo_sqlsrv' => 'https://www.php.net/manual/en/ref.pdo-sqlsrv.php', + 'pdo_dblib' => 'https://www.php.net/manual/en/ref.pdo-dblib.php', + 'PEAR' => 'https://pear.php.net', + 'Net_SMTP' => 'https://pear.php.net/package/Net_SMTP', + 'Mail_mime' => 'https://pear.php.net/package/Mail_mime', + 'Net_LDAP3' => 'https://git.kolab.org/diffusion/PNL', +]; + +?> +
+ +configured ? 3 : 2) . '" />'; +?> + +

Checking PHP version

+=')) { + $RCI->pass('Version', 'PHP ' . PHP_VERSION . ' detected'); +} +else { + $RCI->fail('Version', 'PHP Version ' . MIN_PHP_VERSION . ' or greater is required ' . PHP_VERSION . ' detected'); +} +?> + +

Checking PHP extensions

+

The following modules/extensions are required to run Roundcube:

+ $ext) { + if (extension_loaded($ext)) { + $RCI->pass($name); + } + else { + $_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX; + $msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : ''; + $RCI->fail($name, $msg, $source_urls[$name]); + } + echo '
'; +} + +?> + +

The next couple of extensions are optional and recommended to get the best performance:

+ $ext) { + if (extension_loaded($ext)) { + $RCI->pass($name); + } + else { + $_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX; + $msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : ''; + $RCI->na($name, $msg, $source_urls[$name]); + } + echo '
'; +} + +?> + +

Checking available databases

+

Check which of the supported extensions are installed. At least one of them is required.

+ +supported_dbs as $database => $ext) { + if (extension_loaded($ext)) { + $RCI->pass($database); + $found_db_driver = true; + } + else { + $_ext = $ext_dir . '/' . $prefix . $ext . '.' . PHP_SHLIB_SUFFIX; + $msg = @is_readable($_ext) ? 'Could be loaded. Please add in php.ini' : ''; + $RCI->na($database, $msg, $source_urls[$ext]); + } + echo '
'; +} +if (empty($found_db_driver)) { + $RCI->failures++; +} + +?> + +

Check for required 3rd party libs

+

This also checks if the include path is set correctly.

+ + $vendor) { + if (class_exists($classname)) { + $RCI->pass($classname); + } + else { + $RCI->fail($classname, "Failed to load class $classname from $vendor", $source_urls[$classname]); + } + echo "
"; +} + +foreach ($optional_libs as $classname => $vendor) { + if (class_exists($classname)) { + $RCI->pass($classname); + } + else { + $RCI->na($classname, "Recommended to install $classname from $vendor", $source_urls[$classname]); + } + echo "
"; +} + +?> + +

Checking php.ini/.htaccess settings

+

The following settings are required to run Roundcube:

+ + $val) { + $status = ini_get($var); + if ($val === '-NOTEMPTY-') { + if (empty($status)) { + $RCI->fail($var, "empty value detected"); + } + else { + $RCI->pass($var); + } + } + else if (filter_var($status, FILTER_VALIDATE_BOOLEAN) == $val) { + $RCI->pass($var); + } + else { + $RCI->fail($var, "is '$status', should be '$val'"); + } + echo '
'; +} +?> + +

The following settings are optional and recommended:

+ + $val) { + $status = ini_get($var); + if ($val === '-NOTEMPTY-') { + if (empty($status)) { + $RCI->optfail($var, "Could be set"); + } + else { + $RCI->pass($var); + } + echo '
'; + continue; + } + if ($val === '-VALID-') { + if ($var == 'date.timezone') { + try { + $tz = new DateTimeZone($status); + $RCI->pass($var); + } + catch (Exception $e) { + $RCI->optfail($var, empty($status) ? "not set" : "invalid value detected: $status"); + } + } + else { + $RCI->pass($var); + } + } + else if (filter_var($status, FILTER_VALIDATE_BOOLEAN) == $val) { + $RCI->pass($var); + } + else { + $RCI->optfail($var, "is '$status', could be '$val'"); + } + echo '
'; +} +?> + +failures) { + echo '

Sorry but your webserver does not meet the requirements for Roundcube!
+ Please install the missing modules or fix the php.ini settings according to the above check results.
+ Hint: only checks showing NOT OK need to be fixed.

'; +} +echo '


failures ? 'disabled' : '') . ' />

'; + +?> + +
diff --git a/ruty/mails/installer/client.js b/ruty/mails/installer/client.js new file mode 100644 index 0000000..e08ce22 --- /dev/null +++ b/ruty/mails/installer/client.js @@ -0,0 +1,48 @@ +/* + +-----------------------------------------------------------------------+ + | Roundcube installer client function | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli | + +-----------------------------------------------------------------------+ +*/ + +function toggleblock(id, link) +{ + var block = document.getElementById(id); + + return false; +} + + +function addhostfield() +{ + var container = document.getElementById('defaulthostlist'); + var row = document.createElement('div'); + var input = document.createElement('input'); + var link = document.createElement('a'); + + input.name = '_imap_host[]'; + input.size = '30'; + link.href = '#'; + link.onclick = function() { removehostfield(this.parentNode); return false }; + link.className = 'removelink'; + link.innerHTML = 'remove'; + + row.appendChild(input); + row.appendChild(link); + container.appendChild(row); +} + + +function removehostfield(row) +{ + var container = document.getElementById('defaulthostlist'); + container.removeChild(row); +} diff --git a/ruty/mails/installer/config.php b/ruty/mails/installer/config.php new file mode 100644 index 0000000..3deeac5 --- /dev/null +++ b/ruty/mails/installer/config.php @@ -0,0 +1,647 @@ + | + +-----------------------------------------------------------------------+ +*/ + +if (!class_exists('rcmail_install', false) || !isset($RCI)) { + die("Not allowed! Please open installer/index.php instead."); +} + +// allow the current user to get to the next step +$_SESSION['allowinstaller'] = true; + +if (!empty($_POST['submit'])) { + $_SESSION['config'] = $RCI->create_config(); + + if ($RCI->save_configfile($_SESSION['config'])) { + echo '

The config file was saved successfully into' + . ' '.RCMAIL_CONFIG_DIR.' directory of your Roundcube installation.'; + + if ($RCI->legacy_config) { + echo '

Afterwards, please remove the old configuration files' + . ' main.inc.php and db.inc.php from the config directory.'; + } + + echo '

'; + } + else { + $save_button = ''; + if (($dir = sys_get_temp_dir()) && @is_writable($dir)) { + echo ''; + echo ''; + + $button_txt = html::quote('Save in ' . $dir); + $save_button = ' '; + } + + echo '

Copy or download the following configuration and save it'; + echo ' as config.inc.php within the '.RCUBE_CONFIG_DIR.' directory of your Roundcube installation.
'; + echo ' Make sure that there are no characters before the <?php bracket when saving the file.'; + echo ' '; + echo $save_button; + + if ($RCI->legacy_config) { + echo '

Afterwards, please remove the old configuration files' + . ' main.inc.php and db.inc.php from the config directory.'; + } + + echo '

'; + + $textbox = new html_textarea(['rows' => 16, 'cols' => 60, 'class' => 'configfile']); + echo $textbox->show(($_SESSION['config'])); + } + + echo '

Of course there are more options to configure. + Have a look at the defaults.inc.php file or visit Howto_Config to find out.

'; + + echo '

'; + + // echo ''; + echo "\n
\n"; +} + +?> +
+ + +
+General configuration +
+ +
product_name
+
+ '_product_name', 'size' => 30, 'id' => 'cfgprodname']); +echo $input_prodname->show($RCI->getprop('product_name')); + +?> +
The name of your service (used to compose page titles)
+
+ +
support_url
+
+ '_support_url', 'size' => 50, 'id' => 'cfgsupporturl']); +echo $input_support->show($RCI->getprop('support_url')); + +?> +
Provide a URL where a user can get support for this Roundcube installation.
PLEASE DO NOT LINK TO THE ROUNDCUBE.NET WEBSITE HERE!
+

Enter an absolute URL (including http://) to a support page/form or a mailto: link.

+
+ +
temp_dir
+
+ '_temp_dir', 'size' => 30, 'id' => 'cfgtempdir']); +echo $input_tempdir->show($RCI->getprop('temp_dir')); + +?> +
Use this folder to store temp files (must be writeable for webserver)
+
+ +
des_key
+
+ '_des_key', 'size' => 30, 'id' => 'cfgdeskey']); +echo $input_deskey->show($RCI->getprop('des_key')); + +?> +
This key is used to encrypt the users imap password before storing in the session record
+

It's a random generated string to ensure that every installation has its own key.

+
+ +
ip_check
+
+ '_ip_check', 'id' => 'cfgipcheck']); +echo $check_ipcheck->show(intval($RCI->getprop('ip_check')), array('value' => 1)); + +?> +
+ +

This increases security but can cause sudden logouts when someone uses a proxy with changing IPs.

+
+ +
enable_spellcheck
+
+ '_enable_spellcheck', 'id' => 'cfgspellcheck']); +echo $check_spell->show(intval($RCI->getprop('enable_spellcheck')), ['value' => 1]); +?> + +
+
+ +
spellcheck_engine
+
+ + '_spellcheck_engine', 'id' => 'cfgspellcheckengine']); +if (extension_loaded('pspell')) { + $select_spell->add('Pspell', 'pspell'); +} +if (extension_loaded('enchant')) { + $select_spell->add('Enchant', 'enchant'); +} +$select_spell->add('Googie', 'googie'); +$select_spell->add('ATD', 'atd'); + +echo $select_spell->show($RCI->is_post ? $_POST['_spellcheck_engine'] : 'pspell'); +?> + +
Which spell checker to use
+

Googie implies that the message content will be sent to external server to check the spelling.

+
+ +
identities_level
+
+ + '_identities_level', 'id' => 'cfgidentitieslevel']); +$input_ilevel->add('many identities with possibility to edit all params', 0); +$input_ilevel->add('many identities with possibility to edit all params but not email address', 1); +$input_ilevel->add('one identity with possibility to edit all params', 2); +$input_ilevel->add('one identity with possibility to edit all params but not email address', 3); +$input_ilevel->add('one identity with possibility to edit only signature', 4); +echo $input_ilevel->show($RCI->getprop('identities_level'), 0); + +?> + +
Level of identities access
+

Defines what users can do with their identities.

+
+ +
+
+ +
+Logging & Debugging +
+ +
log_driver
+
+ + '_log_driver', 'id' => 'cfglogdriver']); +$select_log_driver->add(['file', 'syslog', 'stdout'], ['file', 'syslog', 'stdout']); +echo $select_log_driver->show($RCI->getprop('log_driver', 'file')); +?> + +
How to do logging? 'file' - write to files in the log directory, 'syslog' - use the syslog facility, 'stdout' writes to the process' STDOUT file descriptor.
+
+ +
log_dir
+
+ '_log_dir', 'size' => 30, 'id' => 'cfglogdir']); +echo $input_logdir->show($RCI->getprop('log_dir')); + +?> +
Use this folder to store log files (must be writeable for webserver). Note that this only applies if you are using the 'file' log_driver.
+
+ +
syslog_id
+
+ '_syslog_id', 'size' => 30, 'id' => 'cfgsyslogid']); +echo $input_syslogid->show($RCI->getprop('syslog_id', 'roundcube')); + +?> +
What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.
+
+ +
syslog_facility
+
+ '_syslog_facility', 'id' => 'cfgsyslogfacility']); +$input_syslogfacility->add('user-level messages', LOG_USER); +if (defined('LOG_MAIL')) { + $input_syslogfacility->add('mail subsystem', LOG_MAIL); +} +if (defined('LOG_LOCAL0')) { + $input_syslogfacility->add('local level 0', LOG_LOCAL0); + $input_syslogfacility->add('local level 1', LOG_LOCAL1); + $input_syslogfacility->add('local level 2', LOG_LOCAL2); + $input_syslogfacility->add('local level 3', LOG_LOCAL3); + $input_syslogfacility->add('local level 4', LOG_LOCAL4); + $input_syslogfacility->add('local level 5', LOG_LOCAL5); + $input_syslogfacility->add('local level 6', LOG_LOCAL6); + $input_syslogfacility->add('local level 7', LOG_LOCAL7); +} +echo $input_syslogfacility->show($RCI->getprop('syslog_facility'), LOG_USER); + +?> +
What ID to use when logging with syslog. Note that this only applies if you are using the 'syslog' log_driver.
+
+ +
+
+ +
+Database setup +
+
db_dsnw
+
+

Database settings for read/write operations:

+ '_dbtype', 'id' => 'cfgdbtype']); +foreach ($RCI->supported_dbs as $database => $ext) { + if (extension_loaded($ext)) { + $select_dbtype->add($database, substr($ext, 4)); + } +} + +$input_dbhost = new html_inputfield(['name' => '_dbhost', 'size' => 20, 'id' => 'cfgdbhos']); +$input_dbname = new html_inputfield(['name' => '_dbname', 'size' => 20, 'id' => 'cfgdbname']); +$input_dbuser = new html_inputfield(['name' => '_dbuser', 'size' => 20, 'id' => 'cfgdbuser']); +$input_dbpass = new html_inputfield(['name' => '_dbpass', 'size' => 20, 'id' => 'cfgdbpass']); + +$dsnw = rcube_db::parse_dsn($RCI->getprop('db_dsnw')); + +echo $select_dbtype->show($RCI->is_post ? $_POST['_dbtype'] : ($dsnw['phptype'] ?? '')); +echo '
'; +echo $input_dbhost->show($RCI->is_post ? $_POST['_dbhost'] : ($dsnw['hostspec'] ?? '')); +echo '
'; +echo $input_dbname->show($RCI->is_post ? $_POST['_dbname'] : ($dsnw['database'] ?? '')); +echo '
'; +echo $input_dbuser->show($RCI->is_post ? $_POST['_dbuser'] : ($dsnw['username'] ?? '')); +echo '
'; +echo $input_dbpass->show($RCI->is_post ? $_POST['_dbpass'] : ($dsnw['password'] ?? '')); +echo '
'; + +?> +
+ +
db_prefix
+
+ '_db_prefix', 'size' => 20, 'id' => 'cfgdbprefix']); +echo $input_prefix->show($RCI->getprop('db_prefix')); + +?> +
Optional prefix that will be added to database object names (tables and sequences).
+
+ +
+
+ + +
+IMAP Settings +
+
imap_host
+
+
+ '_imap_host[]', 'size' => 30]); +$default_hosts = $RCI->get_hostlist(); + +if (empty($default_hosts)) { + $default_hosts = ['']; +} + +$i = 0; +foreach ($default_hosts as $host) { + echo '
' . $text_imaphost->show($host); + if ($i++ > 0) { + echo 'remove'; + } + echo '
'; +} + +?> +
+ + +
The IMAP host(s) chosen to perform the log-in
+

Leave blank to show a textbox at login. To use SSL/STARTTLS connection add ssl:// or tls:// prefix. It can also contain the port number, e.g. tls://imap.domain.tld:143. +

+ +
username_domain
+
+ '_username_domain', 'size' => 30, 'id' => 'cfguserdomain']); +echo $text_userdomain->show($RCI->getprop('username_domain')); + +?> +
Automatically add this domain to user names for login
+ +

Only for IMAP servers that require full e-mail addresses for login

+
+ +
auto_create_user
+
+ '_auto_create_user', 'id' => 'cfgautocreate']); +echo $check_autocreate->show(intval($RCI->getprop('auto_create_user')), ['value' => 1]); + +?> +
+ +

A user is authenticated by the IMAP server but it requires a local record to store settings +and contacts. With this option enabled a new user record will automatically be created once the IMAP login succeeds.

+ +

If this option is disabled, the login only succeeds if there's a matching user-record in the local Roundcube database +what means that you have to create those records manually or disable this option after the first login.

+
+ +
sent_mbox
+
+ '_sent_mbox', 'size' => 20, 'id' => 'cfgsentmbox']); +echo $text_sentmbox->show($RCI->getprop('sent_mbox')); + +?> +
Store sent messages in this folder
+ +

Leave blank if sent messages should not be stored. Note: folder must include namespace prefix if any.

+
+ +
trash_mbox
+
+ '_trash_mbox', 'size' => 20, 'id' => 'cfgtrashmbox']); +echo $text_trashmbox->show($RCI->getprop('trash_mbox')); + +?> +
Move messages to this folder when deleting them
+ +

Leave blank if they should be deleted directly. Note: folder must include namespace prefix if any.

+
+ +
drafts_mbox
+
+ '_drafts_mbox', 'size' => 20, 'id' => 'cfgdraftsmbox']); +echo $text_draftsmbox->show($RCI->getprop('drafts_mbox')); + +?> +
Store draft messages in this folder
+ +

Leave blank if they should not be stored. Note: folder must include namespace prefix if any.

+
+ +
junk_mbox
+
+ '_junk_mbox', 'size' => 20, 'id' => 'cfgjunkmbox']); +echo $text_junkmbox->show($RCI->getprop('junk_mbox')); + +?> +
Store spam messages in this folder
+ +

Note: folder must include namespace prefix if any.

+
+ + +
+
+ +
+SMTP Settings +
+
smtp_host
+
+ '_smtp_host', 'size' => 30, 'id' => 'cfgsmtphost']); +echo $text_smtphost->show($RCI->getprop('smtp_host', 'localhost:587')); + +?> +
Use this host for sending mails
+

To use SSL/STARTTLS connection add ssl:// or tls:// prefix. It can also contain the port number, e.g. tls://smtp.domain.tld:587.

+
+ +
smtp_user/smtp_pass
+
+ '_smtp_user', 'size' => 20, 'id' => 'cfgsmtpuser']); +$text_smtppass = new html_inputfield(['name' => '_smtp_pass', 'size' => 20, 'id' => 'cfgsmtppass']); +echo $text_smtpuser->show($RCI->getprop('smtp_user')); +echo $text_smtppass->show($RCI->getprop('smtp_pass')); + +?> +
SMTP username and password (if required)
+

+ '_smtp_user_u', 'id' => 'cfgsmtpuseru']); +echo $check_smtpuser->show($RCI->getprop('smtp_user') == '%u' || !empty($_POST['_smtp_user_u']) ? 1 : 0, ['value' => 1]); + +?> + +

+
+ +
smtp_log
+
+ '_smtp_log', 'id' => 'cfgsmtplog']); +echo $check_smtplog->show(intval($RCI->getprop('smtp_log')), ['value' => 1]); + +?> +
+
+ +
+
+ + +
+Display settings & user prefs +
+ +
language *
+
+ '_language', 'size' => 6, 'id' => 'cfglocale']); +echo $input_locale->show($RCI->getprop('language')); + +?> +
The default locale setting. This also defines the language of the login screen.
Leave it empty to auto-detect the user agent language.
+

Enter a RFC1766 formatted language name. Examples: en_US, de_DE, de_CH, fr_FR, pt_BR

+
+ +
skin *
+
+ '_skin', 'id' => 'cfgskin']); +$skins = $RCI->list_skins(); +$input_skin->add($skins, $skins); +echo $input_skin->show($RCI->getprop('skin')); + +?> +
Name of interface skin (folder in /skins)
+
+ +
mail_pagesize *
+
+getprop('mail_pagesize'); +if (!$pagesize) { + $pagesize = $RCI->getprop('pagesize'); +} +$input_pagesize = new html_inputfield(['name' => '_mail_pagesize', 'size' => 6, 'id' => 'cfgmailpagesize']); +echo $input_pagesize->show($pagesize); + +?> +
Show up to X items in the mail messages list view.
+
+ +
addressbook_pagesize *
+
+getprop('addressbook_pagesize'); +if (!$pagesize) { + $pagesize = $RCI->getprop('pagesize'); +} +$input_pagesize = new html_inputfield(['name' => '_addressbook_pagesize', 'size' => 6, 'id' => 'cfgabookpagesize']); +echo $input_pagesize->show($pagesize); + +?> +
Show up to X items in the contacts list view.
+
+ +
prefer_html *
+
+ '_prefer_html', 'id' => 'cfghtmlview', 'value' => 1]); +echo $check_htmlview->show(intval($RCI->getprop('prefer_html'))); + +?> +
+
+ +
htmleditor *
+
+ + '_htmleditor', 'id' => 'cfghtmlcompose']); +$select_htmlcomp->add('never', 0); +$select_htmlcomp->add('always', 1); +$select_htmlcomp->add('on reply to HTML message only', 2); +echo $select_htmlcomp->show(intval($RCI->getprop('htmleditor'))); + +?> +
+ +
draft_autosave *
+
+ + '_draft_autosave', 'id' => 'cfgautosave']); +$select_autosave->add('never', 0); +foreach ([1, 3, 5, 10] as $i => $min) { + $select_autosave->add("$min min", $min * 60); +} + +echo $select_autosave->show(intval($RCI->getprop('draft_autosave'))); + +?> +
+ +
mdn_requests *
+
+ 'ask the user', + 1 => 'send automatically', + 3 => 'send receipt to user contacts, otherwise ask the user', + 4 => 'send receipt to user contacts, otherwise ignore', + 2 => 'ignore', +]; + +$select_mdnreq = new html_select(['name' => '_mdn_requests', 'id' => 'cfgmdnreq']); +$select_mdnreq->add(array_values($mdn_opts), array_keys($mdn_opts)); +echo $select_mdnreq->show(intval($RCI->getprop('mdn_requests'))); + +?> +
Behavior if a received message requests a message delivery notification (read receipt)
+
+ +
mime_param_folding *
+
+ '_mime_param_folding', 'id' => 'cfgmimeparamfolding']); +$select_param_folding->add('Full RFC 2231 (Roundcube, Thunderbird)', '0'); +$select_param_folding->add('RFC 2047/2231 (MS Outlook, OE)', '1'); +$select_param_folding->add('Full RFC 2047 (deprecated)', '2'); + +echo $select_param_folding->show(strval($RCI->getprop('mime_param_folding'))); + +?> +
How to encode attachment long/non-ascii names
+
+ +
+ +

*  These settings are defaults for the user preferences

+
+ +
+Plugins +
+ +list_plugins(); +foreach ($plugins as $p) { + $p_check = new html_checkbox(['name' => '_plugins_'.$p['name'], 'id' => 'cfgplugin_'.$p['name'], 'value' => $p['name']]); + echo '
'; + echo '
'; +} + +?> +
+ +

Please consider checking dependencies of enabled plugins

+
+ +failures ? 'disabled' : '') . ' />

'; + +?> +
diff --git a/ruty/mails/installer/images/add.png b/ruty/mails/installer/images/add.png new file mode 100644 index 0000000000000000000000000000000000000000..d72c566a8c66c046fcbbc3297399051458a346bb GIT binary patch literal 653 zcmV;80&@L{P)e7_MI|70PKJ^fS?K!M3PHOsjFX@I)lYb! zz9Ar}cV`j&u4X9~(7pipHaV?$*cIx-yU_&h^#)Lm2@&1J!)?h0YBj(qxBcJzx*GB+A;Esw>2aym z1*gpx`IAYwCOq?hHQ-lb`M-kBgJ6UW{-Ecs0d})B3MLbAG5o|@z_ZYlQVa?pn}~p4 zw>@0i%wbuexdAf2Tb(}SgCP(%`{QiLk+mhb{EJv06?0i|$k$`=yCdAa ztPK~*H07zfVdBHTK9v@zA`!)47}$+g2&lJ!VjvWi1l#B9-~|7`UxL-Qy~;YuyXxw) zo9~|}uR_|+M)s%-*&{MUH2dJ1_!NWRug43+nwk72IVyYh+t#}DSlxE$H2ZARZuHr_ nMgO*OqfV_sy$06IL=gr6K}O3Tl)a@z00000NkvXXu0mjfy4^eY literal 0 HcmV?d00001 diff --git a/ruty/mails/installer/images/banner_gradient.gif b/ruty/mails/installer/images/banner_gradient.gif new file mode 100644 index 0000000000000000000000000000000000000000..8ab1b06304d35a8ca4cec9d2822b535eed434d2c GIT binary patch literal 506 zcmVh}HS^!@Mn{p0fe-SGY2@crBF{o(TcmPE2?5Q_&dklu0SL?#*wGXT*xlRQ z+Y|%c*W=#e1LNuG78dCg?E~%b^6>5P_ZA2D`|bS)2Qp*;I56Nqfd~^S3}`T60fY|~ zW+?EmLWPJEGg#bsaic~89794N0J5Y>kPt?9l3baxWl91sN4|uq!KF->HFM@Puv3Fi z4LpBpxF8g0P@V=Z@Vr2j>4gnLH$0u{)G1V}8?tKE`cx|i1spbn&C22H*9{8Neg#`r w0ot%>)hhVV7VL+)W!I+tK({Pk4t((f4$QZ(;lqd%D_+dF@!`CX^M(KbJEvw58UO$Q literal 0 HcmV?d00001 diff --git a/ruty/mails/installer/images/banner_schraffur.gif b/ruty/mails/installer/images/banner_schraffur.gif new file mode 100644 index 0000000000000000000000000000000000000000..50182b4d6a6ddcb5c70c98f91e8e321daf408081 GIT binary patch literal 12454 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 D)J6lV literal 0 HcmV?d00001 diff --git a/ruty/mails/installer/images/roundcube_logo.png b/ruty/mails/installer/images/roundcube_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..facb8f86d6a7c30f13200aec2a715908921c2c4b GIT binary patch literal 3783 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 | + +-------------------------------------------------------------------------+ +*/ + +ini_set('display_errors', 1); + +define('INSTALL_PATH', realpath(__DIR__ . '/../').'/'); + +require INSTALL_PATH . 'program/include/iniset.php'; + +if (function_exists('session_start')) { + session_start(); +} + +$RCI = rcmail_install::get_instance(); +$RCI->load_config(); + +if (isset($_GET['_getconfig'])) { + $filename = 'config.inc.php'; + if (!empty($_SESSION['config']) && $_GET['_getconfig'] == 2) { + $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $filename; + @unlink($path); + file_put_contents($path, $_SESSION['config']); + exit; + } + + if (!empty($_SESSION['config'])) { + header('Content-type: text/plain'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + echo $_SESSION['config']; + exit; + } + + header('HTTP/1.0 404 Not found'); + die("The requested configuration was not found. Please run the installer from the beginning."); +} + +if ( + $RCI->configured + && !empty($_GET['_mergeconfig']) + && ($RCI->getprop('enable_installer') || !empty($_SESSION['allowinstaller'])) +) { + $filename = 'config.inc.php'; + + header('Content-type: text/plain'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + + $RCI->merge_config(); + echo $RCI->create_config(); + exit; +} + +// go to 'check env' step if we have a local configuration +if ($RCI->configured && empty($_REQUEST['_step'])) { + header("Location: ./?_step=1"); + exit; +} + +?> + + + +Roundcube Webmail Installer + + + + + + + + + + + + +
+ +configured && !$RCI->getprop('enable_installer') && empty($_SESSION['allowinstaller'])) { + // header("HTTP/1.0 404 Not Found"); + if ($RCI->configured && $RCI->legacy_config) { + echo '

Your configuration needs to be migrated!

'; + echo '

We changed the configuration files structure and your installation needs to be updated accordingly.

'; + echo '

Please run the bin/update.sh script from the command line or set

  $rcube_config[\'enable_installer\'] = true;

'; + echo ' in your RCUBE_CONFIG_DIR/main.inc.php to let the installer help you migrating it.

'; + } + else { + echo '

The installer is disabled!

'; + echo '

To enable it again, set $config[\'enable_installer\'] = true; in RCUBE_CONFIG_DIR/config.inc.php

'; + } + + echo '
'; + exit; +} + +?> + +

Roundcube Webmail Installer

+ +
    + './check.php', + 2 => './config.php', + 3 => './test.php', +]; + +if (!in_array($RCI->step, array_keys($include_steps))) { + $RCI->step = 1; +} + +foreach (['Check environment', 'Create config', 'Test config'] as $i => $item) { + $j = $i + 1; + $link = ($RCI->step >= $j || $RCI->configured) ? '' . rcube::Q($item) . '' : rcube::Q($item); + printf('
  1. %s
  2. ', $j+1, $RCI->step > $j ? ' passed' : ($RCI->step == $j ? ' current' : ''), $link); +} +?> +
+ +step]; + +?> + + + + + diff --git a/ruty/mails/installer/styles.css b/ruty/mails/installer/styles.css new file mode 100644 index 0000000..339b6b5 --- /dev/null +++ b/ruty/mails/installer/styles.css @@ -0,0 +1,240 @@ +body { + background: white; + font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; + font-size: small; + color: black; + margin: 0; +} + +#banner { + position: relative; + height: 58px; + margin: 0 0 1em 0; + padding: 10px 20px; + background: url('images/banner_gradient.gif') top left repeat-x #d8edfd; + overflow: hidden; +} + +#banner .banner-bg { + position: absolute; + top: 0; + right: 0; + width: 630px; + height: 78px; + background: url('images/banner_schraffur.gif') top right no-repeat; + z-index: 0; +} + +#banner .banner-logo { + position: absolute; + top: 10px; + left: 20px; + z-index: 4; +} + +#banner .banner-logo a { + border: 0; +} + +#topnav { + position: absolute; + top: 3.6em; + right: 20px; +} + +#topnav a { + color: #666; +} + +#content { + margin: 2em 20px; +} + +#footer { + margin: 2em 20px 1em 20px; + padding-top: 0.6em; + font-size: smaller; + text-align: center; + border-top: 1px dotted #999; +} + +#progress { + margin-bottom: 2em; + border: 1px solid #aaa; + background-color: #f9f9f9; +} + +#progress:after { + content: "."; + display: block; + height: 0; + font-size: 0; + clear: both; + visibility: hidden; +} + +#progress li { + float: left; + color: #999; + padding: 1em 5em 1em 0.2em; +} + +#progress li a { + color: #999; + text-decoration: none; +} + +#progress li a:hover { + text-decoration: underline; +} + +#progress li.current { + color: #000; + font-weight: bold; +} + +#progress li.passed, +#progress li.passed a, +#progress li.current a { + color: #333; +} + +fieldset { + margin-bottom: 1.5em; + border: 1px solid #aaa; + background-color: #f9f9f9; +} + +fieldset p.hint { + margin-top: 0.5em; +} + +legend { + font-size: 1.1em; + font-weight: bold; +} + +textarea.configfile { + background-color: #f9f9f9; + font-family: monospace; + font-size: 9pt; + width: 100%; + height: 30em; +} + +.propname { + font-family: monospace; + font-size: 9pt; + margin-top: 1em; + margin-bottom: 0.6em; +} + +dd div { + margin-top: 0.3em; +} + +dd label { + padding-left: 0.5em; +} + +th { + text-align: left; +} + +td > label { + min-width: 6em; + display: inline-block; +} + +ul li { + margin: 0.3em 0 0.4em -1em; +} + +ul li ul li { + margin-bottom: 0.2em; +} + +h3 { + font-size: 1.1em; + margin-top: 1.5em; + margin-bottom: 0.6em; +} + +h4 { + margin-bottom: 0.2em; +} + +a.blocktoggle { + color: #666; + text-decoration: none; +} + +a.addlink { + color: #999; + font-size: 0.9em; + padding: 1px 0 1px 20px; + background: url('images/add.png') top left no-repeat; + text-decoration: none; +} + +a.removelink { + color: #999; + font-size: 0.9em; + padding: 1px 0 1px 24px; + background: url('images/delete.png') 4px 0 no-repeat; + text-decoration: none; +} + +.hint { + color: #666; + font-size: 0.95em; +} + +.success { + color: #006400; + font-weight: bold !important; +} + +.fail { + color: #ff0000 !important; + font-weight: bold !important; +} + +.na { + color: #f60; + font-weight: bold; +} + +.indent { + padding-left: 0.8em; +} + +.notice { + padding: 1em; + background-color: #f7fdcb; + border: 2px solid #c2d071; +} + +.suggestion { + padding: 0.6em; + background-color: #ebebeb; + border: 1px solid #999; +} + +p.warning, +div.warning { + padding: 1em; + background-color: #ef9398; + border: 2px solid #dc5757; +} + +h3.warning { + color: #c00; + background: url('images/error.png') top left no-repeat; + padding-left: 24px; +} + +.userconf { + color: #00c; + font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; +} diff --git a/ruty/mails/installer/test.php b/ruty/mails/installer/test.php new file mode 100644 index 0000000..10179b7 --- /dev/null +++ b/ruty/mails/installer/test.php @@ -0,0 +1,499 @@ + | + +-----------------------------------------------------------------------+ +*/ + +if (!class_exists('rcmail_install', false) || !isset($RCI)) { + die("Not allowed! Please open installer/index.php instead."); +} + +?> + +

Check config file

+load_config_file(RCUBE_CONFIG_DIR . 'defaults.inc.php'); + if (!empty($config)) { + $RCI->pass('defaults.inc.php'); + } + else { + $RCI->fail('defaults.inc.php', 'Syntax error'); + } +} +else { + $RCI->fail('defaults.inc.php', 'Unable to read default config file?'); +} + +echo '
'; + +if ($read_config = is_readable(RCUBE_CONFIG_DIR . 'config.inc.php')) { + $config = $RCI->load_config_file(RCUBE_CONFIG_DIR . 'config.inc.php'); + if (!empty($config)) { + $RCI->pass('config.inc.php'); + } + else { + $RCI->fail('config.inc.php', 'Syntax error'); + } +} +else { + $RCI->fail('config.inc.php', 'Unable to read file. Did you create the config file?'); +} + +echo '
'; + +if ($RCI->configured && ($messages = $RCI->check_config())) { + if (is_array($messages['replaced'])) { + echo '

Replaced config options

'; + echo '

The following config options have been replaced or renamed. '; + echo 'Please update them accordingly in your config files.

'; + + echo '
    '; + foreach ($messages['replaced'] as $msg) { + echo html::tag('li', null, html::span('propname', $msg['prop']) . + ' was replaced by ' . html::span('propname', $msg['replacement'])); + } + echo '
'; + } + + if (is_array($messages['obsolete'])) { + echo '

Obsolete config options

'; + echo '

You still have some obsolete or inexistent properties set. This isn\'t a problem but should be noticed.

'; + + echo '
    '; + foreach ($messages['obsolete'] as $msg) { + echo html::tag('li', null, html::span('propname', $msg['prop']) + . (!empty($msg['explain']) ? ': ' . $msg['explain'] : '')); + } + echo '
'; + } + + echo '

OK, lazy people can download the updated config file here: '; + echo html::a(['href' => './?_mergeconfig=1'], 'config.inc.php') . '  '; + echo "

"; + + if (is_array($messages['dependencies'])) { + echo '

Dependency check failed

'; + echo '

Some of your configuration settings require other options to be configured or additional PHP modules to be installed

'; + + echo '
    '; + foreach ($messages['dependencies'] as $msg) { + echo html::tag('li', null, html::span('propname', $msg['prop']) . ': ' . $msg['explain']); + } + echo '
'; + } +} + +?> + +

Check if directories are writable

+

Roundcube may need to write/save files into these directories

+config['temp_dir']) ? $RCI->config['temp_dir'] : 'temp'; +if ($RCI->config['log_driver'] != 'syslog') { + $dirs[] = $RCI->config['log_dir'] ? $RCI->config['log_dir'] : 'logs'; +} + +foreach ($dirs as $dir) { + $dirpath = rcube_utils::is_absolute_path($dir) ? $dir : INSTALL_PATH . $dir; + if (is_writable(realpath($dirpath))) { + $RCI->pass($dir); + $pass = true; + } + else { + $RCI->fail($dir, 'not writeable for the webserver'); + } + echo '
'; +} + +if (empty($pass)) { + echo '

Use chmod or chown to grant write privileges to the webserver

'; +} + +?> + +

Check DB config

+configured) { + if (!empty($RCI->config['db_dsnw'])) { + $DB = rcube_db::factory($RCI->config['db_dsnw'], '', false); + $DB->set_debug((bool)$RCI->config['sql_debug']); + $DB->db_connect('w'); + + if (!($db_error_msg = $DB->is_error())) { + $RCI->pass('DSN (write)'); + echo '
'; + $db_working = true; + } + else { + $RCI->fail('DSN (write)', $db_error_msg); + echo '

Make sure that the configured database exists and that the user has write privileges
'; + echo 'DSN: ' . rcube::Q($RCI->config['db_dsnw']) . '

'; + } + } + else { + $RCI->fail('DSN (write)', 'not set'); + } +} +else { + $RCI->fail('DSN (write)', 'Could not read config file'); +} + +// initialize db with schema found in /SQL/* +if ($db_working && !empty($_POST['initdb'])) { + if (!$RCI->init_db($DB)) { + $db_working = false; + echo '

Please try to initialize the database manually as described in the INSTALL guide. + Make sure that the configured database exists and that the user as write privileges

'; + } +} +else if ($db_working && !empty($_POST['updatedb'])) { + if (!$RCI->update_db($_POST['version'])) { + echo '

Database schema update failed.

'; + } +} + +// test database +if ($db_working) { + $db_read = $DB->query("SELECT count(*) FROM " . $DB->quote_identifier($RCI->config['db_prefix'] . 'users')); + if ($DB->is_error()) { + $RCI->fail('DB Schema', "Database not initialized"); + echo '
' + . '

' + . '
'; + + $db_working = false; + } + else if ($err = $RCI->db_schema_check($DB, $update = !empty($_POST['updatedb']))) { + $RCI->fail('DB Schema', "Database schema differs"); + echo '
  • ' . join("
  • \n
  • ", $err) . "
"; + + $select = $RCI->versions_select(['name' => 'version']); + $select->add('0.9 or newer', ''); + + echo '
' + . '

You should run the update queries to get the schema fixed.' + . '

Version to update from: ' . $select->show('') + . ' 

' + . '
'; + + $db_working = false; + } + else { + $RCI->pass('DB Schema'); + echo '
'; + } +} + +// more database tests +if ($db_working) { + // Using transactions to workaround SQLite bug (#7064) + if ($DB->db_provider == 'sqlite') { + $DB->startTransaction(); + } + + // write test + $insert_id = md5(uniqid()); + $db_write = $DB->query("INSERT INTO " . $DB->quote_identifier($RCI->config['db_prefix'] . 'session') + . " (`sess_id`, `changed`, `ip`, `vars`) VALUES (?, ".$DB->now().", '127.0.0.1', 'foo')", $insert_id); + + if ($db_write) { + $RCI->pass('DB Write'); + $DB->query("DELETE FROM " . $DB->quote_identifier($RCI->config['db_prefix'] . 'session') + . " WHERE `sess_id` = ?", $insert_id); + } + else { + $RCI->fail('DB Write', $RCI->get_error()); + } + echo '
'; + + // Transaction end + if ($DB->db_provider == 'sqlite') { + $DB->rollbackTransaction(); + } + + // check timezone settings + $tz_db = 'SELECT ' . $DB->unixtimestamp($DB->now()) . ' AS tz_db'; + $tz_db = $DB->query($tz_db); + $tz_db = $DB->fetch_assoc($tz_db); + $tz_db = (int) $tz_db['tz_db']; + $tz_local = (int) time(); + $tz_diff = $tz_local - $tz_db; + + // sometimes db and web servers are on separate hosts, so allow a 30 minutes delta + if (abs($tz_diff) > 1800) { + $RCI->fail('DB Time', "Database time differs {$tz_diff}s from PHP time"); + } + else { + $RCI->pass('DB Time'); + } +} + +?> + +

Test filetype detection

+ +check_mime_detection()) { + $RCI->fail('Fileinfo/mime_content_type configuration'); + if (!empty($RCI->config['mime_magic'])) { + echo '

Try setting the mime_magic config option to null.

'; + } + else { + echo '

Check the Fileinfo functions of your PHP installation.
'; + echo 'The path to the magic.mime file can be set using the mime_magic config option in Roundcube.

'; + } +} +else { + $RCI->pass('Fileinfo/mime_content_type configuration'); + echo "
"; +} + + +if ($errors = $RCI->check_mime_extensions()) { + $RCI->fail('Mimetype to file extension mapping'); + echo '

Please set a valid path to your webserver\'s mime.types file to the mime_types config option.
'; + echo 'If you can\'t find such a file, download it from svn.apache.org.

'; +} +else { + $RCI->pass('Mimetype to file extension mapping'); + echo "
"; +} + +$smtp_hosts = $RCI->get_hostlist('smtp_host'); +if (!empty($smtp_hosts)) { + $smtp_host_field = new html_select(['name' => '_smtp_host', 'id' => 'smtp_host']); + $smtp_host_field->add($smtp_hosts, $smtp_hosts); +} +else { + $smtp_host_field = new html_inputfield(['name' => '_smtp_host', 'id' => 'smtp_host']); +} + +$user = $RCI->getprop('smtp_user', '(none)'); +$pass = $RCI->getprop('smtp_pass', '(none)'); + +if ($user == '%u') { + $user_field = new html_inputfield(['name' => '_smtp_user', 'id' => 'smtp_user']); + $user = $user_field->show(isset($_POST['_smtp_user']) ? $_POST['_smtp_user'] : ''); +} +else { + $user = html::quote($user); +} +if ($pass == '%p') { + $pass_field = new html_passwordfield(['name' => '_smtp_pass', 'id' => 'smtp_pass']); + $pass = $pass_field->show(); +} +else { + $pass = html::quote($pass); +} + +?> + +
+ +

Test SMTP config

+ +

+ + + + + + + + + + + + + + + +
show(isset($_POST['_smtp_host']) ? $_POST['_smtp_host'] : ''); ?>
+

+ + '_from', 'id' => 'sendmailfrom']); +$to_field = new html_inputfield(['name' => '_to', 'id' => 'sendmailto']); + +if (isset($_POST['sendmail'])) { + + echo '

Trying to send email...
'; + + $smtp_host = trim($_POST['_smtp_host']); + + $from = rcube_utils::idn_to_ascii(trim($_POST['_from'])); + $to = rcube_utils::idn_to_ascii(trim($_POST['_to'])); + + if ( + preg_match('/^' . $RCI->email_pattern . '$/i', $from) + && preg_match('/^' . $RCI->email_pattern . '$/i', $to) + ) { + $headers = [ + 'From' => $from, + 'To' => $to, + 'Subject' => 'Test message from Roundcube', + ]; + + $body = 'This is a test to confirm that Roundcube can send email.'; + + // send mail using configured SMTP server + $CONFIG = $RCI->config; + + if (!empty($_POST['_smtp_user'])) { + $CONFIG['smtp_user'] = $_POST['_smtp_user']; + } + if (!empty($_POST['_smtp_pass'])) { + $CONFIG['smtp_pass'] = $_POST['_smtp_pass']; + } + + $mail_object = new Mail_mime(); + $send_headers = $mail_object->headers($headers); + $head = $mail_object->txtHeaders($send_headers); + + $SMTP = new rcube_smtp(); + $SMTP->connect($smtp_host, null, $CONFIG['smtp_user'], $CONFIG['smtp_pass']); + + $status = $SMTP->send_mail($headers['From'], $headers['To'], $head, $body); + $smtp_response = $SMTP->get_response(); + + if ($status) { + $RCI->pass('SMTP send'); + } + else { + $RCI->fail('SMTP send', join('; ', $smtp_response)); + } + } + else { + $RCI->fail('SMTP send', 'Invalid sender or recipient'); + } + + echo '

'; +} + +?> + + + + + + + + + + + + +
show(isset($_POST['_from']) ? $_POST['_from'] : ''); ?>
show(isset($_POST['_to']) ? $_POST['_to'] : ''); ?>
+ +

+ +
+ +
+ +

Test IMAP config

+ +get_hostlist(); +if (!empty($default_hosts)) { + $host_field = new html_select(['name' => '_host', 'id' => 'imaphost']); + $host_field->add($default_hosts, $default_hosts); +} +else { + $host_field = new html_inputfield(['name' => '_host', 'id' => 'imaphost']); +} + +$user_field = new html_inputfield(['name' => '_user', 'id' => 'imapuser']); +$pass_field = new html_passwordfield(['name' => '_pass', 'id' => 'imappass']); + +?> + + + + + + + + + + + + + + + + +
show(isset($_POST['_host']) ? $_POST['_host'] : ''); ?>
show(isset($_POST['_user']) ? $_POST['_user'] : ''); ?>
show(); ?>
+ +Connecting to ' . rcube::Q($_POST['_host']) . '...
'; + + $imap_host = trim($_POST['_host']); + $imap_port = 143; + $imap_ssl = false; + + $a_host = parse_url($imap_host); + if ($a_host['host']) { + $imap_host = $a_host['host']; + $imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], ['ssl','imaps','tls'])) ? $a_host['scheme'] : null; + $imap_port = $a_host['port'] ?? ($imap_ssl && $imap_ssl != 'tls' ? 993 : 143); + } + + $imap_host = rcube_utils::idn_to_ascii($imap_host); + $imap_user = rcube_utils::idn_to_ascii($_POST['_user']); + + $imap = new rcube_imap; + $imap->set_options([ + 'auth_type' => $RCI->getprop('imap_auth_type'), + 'debug' => $RCI->getprop('imap_debug'), + 'socket_options' => $RCI->getprop('imap_conn_options'), + ]); + + if ($imap->connect($imap_host, $imap_user, $_POST['_pass'], $imap_port, $imap_ssl)) { + $RCI->pass('IMAP connect', 'SORT capability: ' . ($imap->get_capability('SORT') ? 'yes' : 'no')); + $imap->close(); + } + else { + $RCI->fail('IMAP connect', $RCI->get_error()); + } +} + +?> + +

+ +
+ +
+ +

+ +After completing the installation and the final tests please remove the whole +installer folder from the document root of the webserver or make sure that +enable_installer option in config.inc.php is disabled.
+
+ +These files may expose sensitive configuration data like server passwords and encryption keys +to the public. Make sure you cannot access this installer from your browser. + +

diff --git a/ruty/mails/logs/.htaccess b/ruty/mails/logs/.htaccess new file mode 100644 index 0000000..43e24ed --- /dev/null +++ b/ruty/mails/logs/.htaccess @@ -0,0 +1,7 @@ +# deny webserver access to this directory + + Require all denied + + + Deny from all + diff --git a/ruty/mails/logs/errors.log b/ruty/mails/logs/errors.log new file mode 100644 index 0000000..beaaaed --- /dev/null +++ b/ruty/mails/logs/errors.log @@ -0,0 +1,11 @@ +[02-Oct-2023 18:04:08 +0000]: <13n3u2rh> DB Error: SQLSTATE[HY000] [1045] Accès refusé pour l'utilisateur: 'roundcube'@'@localhost' (mot de passe: NON) in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_db.php on line 201 (GET /ruty/mails/) +[02-Oct-2023 18:24:21 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:24:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:33:55 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:44:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:45:00 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:45:14 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:45:41 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 18:48:29 +0000]: <13n3u2rh> IMAP Error: Login failed for whykioh@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 19:02:57 +0000]: <13n3u2rh> IMAP Error: Login failed for root against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) +[02-Oct-2023 19:11:53 +0000]: <13n3u2rh> IMAP Error: Login failed for whykorp@gmail.com against localhost from 192.168.0.254. Could not connect to localhost:143: Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée in C:\wamp64\www\ruty\mails\program\lib\Roundcube\rcube_imap.php on line 211 (POST /ruty/mails/?_task=login&_action=login) diff --git a/ruty/mails/program/actions/contacts/copy.php b/ruty/mails/program/actions/contacts/copy.php new file mode 100644 index 0000000..c04d1f2 --- /dev/null +++ b/ruty/mails/program/actions/contacts/copy.php @@ -0,0 +1,150 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_copy extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + + $cids = self::get_cids(); + $target = rcube_utils::get_input_string('_to', rcube_utils::INPUT_POST); + $target_group = rcube_utils::get_input_string('_togid', rcube_utils::INPUT_POST); + + $success = 0; + $errormsg = 'copyerror'; + $maxnum = $rcmail->config->get('max_group_members', 0); + + foreach ($cids as $source => $cid) { + // Something wrong, target not specified + if (!strlen($target)) { + break; + } + + // It might happen when copying records from search result + // Do nothing, go to next source + if ((string) $target == (string) $source) { + continue; + } + + $CONTACTS = $rcmail->get_address_book($source); + $TARGET = $rcmail->get_address_book($target); + + if (!$TARGET || !$TARGET->ready || $TARGET->readonly) { + break; + } + + $ids = []; + + foreach ($cid as $cid) { + $a_record = $CONTACTS->get_record($cid, true); + + // avoid copying groups + if (isset($a_record['_type']) && $a_record['_type'] == 'group') { + continue; + } + + // Check if contact exists, if so, we'll need it's ID + // Note: Some addressbooks allows empty email address field + // @TODO: should we check all email addresses? + $email = $CONTACTS->get_col_values('email', $a_record, true); + if (!empty($email)) { + $result = $TARGET->search('email', $email[0], 1, true, true); + } + else if (!empty($a_record['name'])) { + $result = $TARGET->search('name', $a_record['name'], 1, true, true); + } + else { + $result = new rcube_result_set(); + } + + // insert contact record + if (!$result->count) { + $plugin = $rcmail->plugins->exec_hook('contact_create', [ + 'record' => $a_record, + 'source' => $target, + 'group' => $target_group + ]); + + if (!$plugin['abort']) { + if ($insert_id = $TARGET->insert($plugin['record'], false)) { + $ids[] = $insert_id; + $success++; + } + } + else if ($plugin['result']) { + $ids = array_merge($ids, $plugin['result']); + $success++; + } + } + else { + $record = $result->first(); + $ids[] = $record['ID']; + $errormsg = empty($email) ? 'contactnameexists' : 'contactexists'; + } + } + + // assign to group + if ($target_group && $TARGET->groups && !empty($ids)) { + $plugin = $rcmail->plugins->exec_hook('group_addmembers', [ + 'group_id' => $target_group, + 'ids' => $ids, + 'source' => $target + ]); + + if (!$plugin['abort']) { + $TARGET->reset(); + $TARGET->set_group($target_group); + + if ($maxnum && ($TARGET->count()->count + count($plugin['ids']) > $maxnum)) { + $rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]); + $rcmail->output->send(); + } + + if (($cnt = $TARGET->add_to_group($target_group, $plugin['ids'])) && $cnt > $success) { + $success = $cnt; + } + } + else if (!empty($plugin['result'])) { + $success = $plugin['result']; + } + + $errormsg = !empty($plugin['message']) ? $plugin['message'] : 'copyerror'; + } + } + + if (!$success) { + $rcmail->output->show_message($errormsg, 'error'); + } + else { + $rcmail->output->show_message('copysuccess', 'confirmation', ['nr' => $success]); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/delete.php b/ruty/mails/program/actions/contacts/delete.php new file mode 100644 index 0000000..c941cd9 --- /dev/null +++ b/ruty/mails/program/actions/contacts/delete.php @@ -0,0 +1,172 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_delete extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $cids = self::get_cids(null, rcube_utils::INPUT_POST); + $delcnt = 0; + + // remove previous deletes + $undo_time = $rcmail->config->get('undo_timeout', 0); + $rcmail->session->remove('contact_undo'); + + foreach ($cids as $source => $cid) { + $CONTACTS = self::contact_source($source); + + if ($CONTACTS->readonly && empty($CONTACTS->deletable)) { + // more sources? do nothing, probably we have search results from + // more than one source, some of these sources can be readonly + if (count($cids) == 1) { + $rcmail->output->show_message('contactdelerror', 'error'); + $rcmail->output->command('list_contacts'); + $rcmail->output->send(); + } + continue; + } + + $plugin = $rcmail->plugins->exec_hook('contact_delete', [ + 'id' => $cid, + 'source' => $source + ]); + + $deleted = !$plugin['abort'] ? $CONTACTS->delete($cid, $undo_time < 1) : $plugin['result']; + + if (!$deleted) { + if (!empty($plugin['message'])) { + $error = $plugin['message']; + } + else if (($error = $CONTACTS->get_error()) && !empty($error['message'])) { + $error = $error['message']; + } + else { + $error = 'contactdelerror'; + } + + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GP); + $group = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP); + + $rcmail->output->show_message($error, 'error'); + $rcmail->output->command('list_contacts', $source, $group); + $rcmail->output->send(); + } + else { + $delcnt += $deleted; + + // store deleted contacts IDs in session for undo action + if ($undo_time > 0 && $CONTACTS->undelete) { + $_SESSION['contact_undo']['data'][$source] = $cid; + } + } + } + + if (!empty($_SESSION['contact_undo'])) { + $_SESSION['contact_undo']['ts'] = time(); + $msg = html::span(null, $rcmail->gettext('contactdeleted')) + . ' ' . html::a( + ['onclick' => rcmail_output::JS_OBJECT_NAME.".command('undo', '', this)"], + $rcmail->gettext('undo') + ); + + $rcmail->output->show_message($msg, 'confirmation', null, true, $undo_time); + } + else { + $rcmail->output->show_message('contactdeleted', 'confirmation'); + } + + $page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50)); + $page = $_SESSION['page'] ?? 1; + + // update saved search after data changed + if (($records = self::search_update(true)) !== false) { + // create resultset object + $count = count($records); + $first = ($page-1) * $page_size; + $result = new rcube_result_set($count, $first); + $pages = ceil((count($records) + $delcnt) / $page_size); + + // last page and it's empty, display previous one + if ($result->count && $result->count <= ($page_size * ($page - 1))) { + $rcmail->output->command('list_page', 'prev'); + $rowcount = $rcmail->gettext('loading'); + } + // get records from the next page to add to the list + else if ($pages > 1 && $page < $pages) { + // sort the records + ksort($records, SORT_LOCALE_STRING); + + $first += $page_size; + // create resultset object + $res = new rcube_result_set($count, $first - $delcnt); + + if ($page_size < $count) { + $records = array_slice($records, $first - $delcnt, $delcnt); + } + + $res->records = array_values($records); + $records = $res; + } + else { + unset($records); + } + } + else if (isset($CONTACTS)) { + // count contacts for this user + $result = $CONTACTS->count(); + $pages = ceil(($result->count + $delcnt) / $page_size); + + // last page and it's empty, display previous one + if ($result->count && $result->count <= ($page_size * ($page - 1))) { + $rcmail->output->command('list_page', 'prev'); + $rowcount = $rcmail->gettext('loading'); + } + // get records from the next page to add to the list + else if ($pages > 1 && $page < $pages) { + $CONTACTS->set_page($page); + $records = $CONTACTS->list_records(null, -$delcnt); + } + } + + if (!isset($rowcount)) { + $rowcount = isset($result) ? self::get_rowcount_text($result) : ''; + } + + // update message count display + $rcmail->output->set_env('pagecount', isset($result) ? ceil($result->count / $page_size) : 0); + $rcmail->output->command('set_rowcount', $rowcount); + + // add new rows from next page (if any) + if (!empty($records)) { + self::js_contacts_list($records); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/edit.php b/ruty/mails/program/actions/contacts/edit.php new file mode 100644 index 0000000..2be33b7 --- /dev/null +++ b/ruty/mails/program/actions/contacts/edit.php @@ -0,0 +1,269 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_edit extends rcmail_action_contacts_index +{ + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + + if ($rcmail->action == 'edit') { + // Get contact ID and source ID from request + $cids = self::get_cids(); + $source = key($cids); + $cid = array_first($cids[$source]); + + // Initialize addressbook + $CONTACTS = self::contact_source($source, true); + + // Contact edit + if ($cid && (self::$contact = $CONTACTS->get_record($cid, true))) { + $rcmail->output->set_env('cid', self::$contact['ID']); + } + + // editing not allowed here + if ($CONTACTS->readonly || !empty(self::$contact['readonly'])) { + $rcmail->output->show_message('sourceisreadonly'); + $rcmail->overwrite_action('show'); + return; + } + + if (empty(self::$contact)) { + $rcmail->output->show_message('contactnotfound', 'error'); + } + } + else { + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + + if (strlen($source)) { + $CONTACTS = $rcmail->get_address_book($source, true); + } + + if (empty($CONTACTS) || $CONTACTS->readonly) { + $CONTACTS = $rcmail->get_address_book(rcube_addressbook::TYPE_DEFAULT, true); + $source = $rcmail->get_address_book_id($CONTACTS); + } + + // Initialize addressbook + $CONTACTS = self::contact_source($source, true); + } + + self::$SOURCE_ID = $source; + self::$CONTACTS = $CONTACTS; + self::set_sourcename($CONTACTS); + + // check if we have a valid result + if (!empty($args['contact'])) { + self::$contact = $args['contact']; + } + + $rcmail->output->add_handlers([ + 'contactedithead' => [$this, 'contact_edithead'], + 'contacteditform' => [$this, 'contact_editform'], + 'contactphoto' => [$this, 'contact_photo'], + 'photouploadform' => [$this, 'upload_photo_form'], + 'sourceselector' => [$this, 'source_selector'], + 'filedroparea' => [$this, 'photo_drop_area'], + ]); + + $rcmail->output->set_pagetitle($rcmail->gettext(($rcmail->action == 'add' ? 'addcontact' : 'editcontact'))); + + if ($rcmail->action == 'add' && $rcmail->output->template_exists('contactadd')) { + $rcmail->output->send('contactadd'); + } + + // this will be executed if no template for addcontact exists + $rcmail->output->send('contactedit'); + } + + public static function contact_edithead($attrib) + { + $rcmail = rcmail::get_instance(); + $business_mode = $rcmail->config->get('contact_form_mode') === 'business'; + + // check if we have a valid result + $i_size = !empty($attrib['size']) ? $attrib['size'] : 20; + + $form = [ + 'head' => [ + 'name' => $rcmail->gettext('contactnameandorg'), + 'content' => [ + 'source' => ['id' => '_source', 'label' => $rcmail->gettext('addressbook')], + 'prefix' => ['size' => $i_size], + 'firstname' => ['size' => $i_size, 'visible' => true], + 'middlename' => ['size' => $i_size], + 'surname' => ['size' => $i_size, 'visible' => true], + 'suffix' => ['size' => $i_size], + 'name' => ['size' => $i_size * 2], + 'nickname' => ['size' => $i_size * 2], + 'organization' => ['size' => $i_size * 2, 'visible' => $business_mode], + 'department' => ['size' => $i_size * 2, 'visible' => $business_mode], + 'jobtitle' => ['size' => $i_size * 2, 'visible' => $business_mode], + ] + ] + ]; + + list($form_start, $form_end) = self::get_form_tags($attrib); + unset($attrib['form'], $attrib['name'], $attrib['size']); + + // return the address edit form + $out = self::contact_form($form, self::$contact, $attrib); + + return $form_start . $out . $form_end; + } + + public static function contact_editform($attrib) + { + $rcmail = rcmail::get_instance(); + $addr_tpl = $rcmail->config->get('address_template', ''); + + // copy (parsed) address template to client + if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $addr_tpl, $templ, PREG_SET_ORDER)) { + $rcmail->output->set_env('address_template', $templ); + } + + $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; + $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10; + $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; + $short_labels = self::get_bool_attr($attrib, 'short-legend-labels'); + + $form = [ + 'contact' => [ + 'name' => $rcmail->gettext('properties'), + 'content' => [ + 'email' => ['size' => $i_size, 'maxlength' => 254, 'visible' => true], + 'phone' => ['size' => $i_size, 'visible' => true], + 'address' => ['visible' => true], + 'website' => ['size' => $i_size], + 'im' => ['size' => $i_size], + ], + ], + 'personal' => [ + 'name' => $rcmail->gettext($short_labels ? 'personal' : 'personalinfo'), + 'content' => [ + 'gender' => ['visible' => true], + 'maidenname' => ['size' => $i_size], + 'birthday' => ['visible' => true], + 'anniversary' => [], + 'manager' => ['size' => $i_size], + 'assistant' => ['size' => $i_size], + 'spouse' => ['size' => $i_size], + ], + ], + ]; + + if (isset(self::$CONTACT_COLTYPES['notes'])) { + $form['notes'] = [ + 'name' => $rcmail->gettext('notes'), + 'single' => true, + 'content' => [ + 'notes' => ['size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1], + ], + ]; + } + + list($form_start, $form_end) = self::get_form_tags($attrib); + unset($attrib['form']); + + // return the complete address edit form as table + $out = self::contact_form($form, self::$contact, $attrib); + + return $form_start . $out . $form_end; + } + + public static function upload_photo_form($attrib) + { + $rcmail = rcmail::get_instance(); + $hidden = new html_hiddenfield(['name' => '_cid', 'value' => $rcmail->output->get_env('cid')]); + + $attrib['prefix'] = $hidden->show(); + $input_attr = ['name' => '_photo', 'accept' => 'image/*']; + + $rcmail->output->add_label('addphoto','replacephoto'); + + return self::upload_form($attrib, 'uploadform', 'upload-photo', $input_attr); + } + + /** + * similar function as in /steps/settings/edit_identity.inc + * @todo: Use rcmail_action::get_form_tags() + */ + public static function get_form_tags($attrib, $action = null, $id = null, $hidden = null) + { + static $edit_form; + + $rcmail = rcmail::get_instance(); + $form_start = $form_end = ''; + + if (empty($edit_form)) { + $hiddenfields = new html_hiddenfield(); + + if ($rcmail->action == 'edit') { + $hiddenfields->add(['name' => '_source', 'value' => self::$SOURCE_ID]); + } + + $hiddenfields->add(['name' => '_gid', 'value' => self::$CONTACTS->group_id]); + $hiddenfields->add(['name' => '_search', 'value' => rcube_utils::get_input_string('_search', rcube_utils::INPUT_GPC)]); + + if ($cid = $rcmail->output->get_env('cid')) { + $hiddenfields->add(['name' => '_cid', 'value' => $cid]); + } + + $form_attrib = [ + 'name' => 'form', + 'method' => 'post', + 'task' => $rcmail->task, + 'action' => 'save', + 'request' => 'save.' . intval($cid), + 'noclose' => true, + ]; + + $form_start = $rcmail->output->request_form($form_attrib + $attrib, $hiddenfields->show()); + $form_end = empty($attrib['form']) ? '' : ''; + $edit_form = !empty($attrib['form']) ? $attrib['form'] : 'form'; + + $rcmail->output->add_gui_object('editform', $edit_form); + } + + return [$form_start, $form_end]; + } + + /** + * Register container as active area to drop photos onto + */ + public static function photo_drop_area($attrib) + { + $rcmail = rcmail::get_instance(); + + if (!empty($attrib['id'])) { + $rcmail->output->add_gui_object('filedrop', $attrib['id']); + $rcmail->output->set_env('filedrop', [ + 'action' => 'upload-photo', + 'fieldname' => '_photo', + 'single' => 1, + 'filter' => '^image/.+' + ]); + } + } +} diff --git a/ruty/mails/program/actions/contacts/export.php b/ruty/mails/program/actions/contacts/export.php new file mode 100644 index 0000000..632bdfc --- /dev/null +++ b/ruty/mails/program/actions/contacts/export.php @@ -0,0 +1,192 @@ + | + | Author: Aleksander Machniak | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_export extends rcmail_action_contacts_index +{ + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + + $rcmail->request_security_check(rcube_utils::INPUT_GET); + + $sort_col = $rcmail->config->get('addressbook_sort_col', 'name'); + + // Use search result + if (!empty($_REQUEST['_search']) && isset($_SESSION['contact_search'][$_REQUEST['_search']])) { + $search = (array) $_SESSION['contact_search'][$_REQUEST['_search']]; + $records = []; + + // Get records from all sources + foreach ($search as $s => $set) { + $source = $rcmail->get_address_book($s); + + // reset page + $source->set_page(1); + $source->set_pagesize(99999); + $source->set_search_set($set); + + // get records + $result = $source->list_records(); + + while ($record = $result->next()) { + // because vcard_map is per-source we need to create vcard here + self::prepare_for_export($record, $source); + + $record['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($record, $sort_col); + $records[$key] = $record; + } + + unset($result); + } + + // sort the records + ksort($records, SORT_LOCALE_STRING); + + // create resultset object + $count = count($records); + $result = new rcube_result_set($count); + $result->records = array_values($records); + } + // selected contacts + else if (!empty($_REQUEST['_cid'])) { + $records = []; + + // Selected contact IDs (with multi-source support) + $cids = self::get_cids(); + + foreach ($cids as $s => $ids) { + $source = $rcmail->get_address_book($s); + + // reset page and page size (#6103) + $source->set_page(1); + $source->set_pagesize(count($ids)); + + $result = $source->search('ID', $ids, 1, true, true); + + while ($record = $result->next()) { + // because vcard_map is per-source we need to create vcard here + self::prepare_for_export($record, $source); + + $record['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($record, $sort_col); + $records[$key] = $record; + } + } + + ksort($records, SORT_LOCALE_STRING); + + // create resultset object + $count = count($records); + $result = new rcube_result_set($count); + $result->records = array_values($records); + } + // selected directory/group + else { + $CONTACTS = self::contact_source(null, true); + + // get contacts for this user + $CONTACTS->set_page(1); + $CONTACTS->set_pagesize(99999); + $result = $CONTACTS->list_records(null, 0, true); + } + + // Give plugins a possibility to implement other output formats or modify the result + $plugin = $rcmail->plugins->exec_hook('addressbook_export', ['result' => $result]); + $result = $plugin['result']; + + if ($plugin['abort']) { + $rcmail->output->sendExit(); + } + + // send download headers + $rcmail->output->header('Content-Type: text/vcard; charset=' . RCUBE_CHARSET); + $rcmail->output->header('Content-Disposition: attachment; filename="contacts.vcf"'); + + while ($result && ($row = $result->next())) { + if (!empty($CONTACTS)) { + self::prepare_for_export($row, $CONTACTS); + } + + // fix folding and end-of-line chars + $row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']); + $row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']); + + echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol; + } + + $rcmail->output->sendExit(); + } + + /** + * Copy contact record properties into a vcard object + */ + public static function prepare_for_export(&$record, $source = null) + { + $groups = $source && $source->groups && $source->export_groups ? $source->get_record_groups($record['ID']) : null; + $fieldmap = $source ? $source->vcard_map : null; + + if (empty($record['vcard'])) { + $vcard = new rcube_vcard(null, RCUBE_CHARSET, false, $fieldmap); + $vcard->reset(); + + foreach ($record as $key => $values) { + list($field, $section) = rcube_utils::explode(':', $key); + // avoid unwanted casting of DateTime objects to an array + // (same as in rcube_contacts::convert_save_data()) + if (is_object($values) && is_a($values, 'DateTime')) { + $values = [$values]; + } + + foreach ((array) $values as $value) { + if (is_array($value) || is_a($value, 'DateTime') || @strlen($value)) { + $vcard->set($field, $value, $section ? strtoupper($section) : ''); + } + } + } + + // append group names + if ($groups) { + $vcard->set('groups', join(',', $groups), null); + } + + $record['vcard'] = $vcard->export(); + } + // patch categories to already existing vcard block + else { + $vcard = new rcube_vcard($record['vcard'], RCUBE_CHARSET, false, $fieldmap); + + // unset CATEGORIES entry, it might be not up-to-date (#1490277) + $vcard->set('groups', null); + $record['vcard'] = $vcard->export(); + + if (!empty($groups)) { + $vgroups = 'CATEGORIES:' . rcube_vcard::vcard_quote($groups, ',') . rcube_vcard::$eol; + $record['vcard'] = str_replace('END:VCARD', $vgroups . 'END:VCARD', $record['vcard']); + } + } + } +} diff --git a/ruty/mails/program/actions/contacts/group_addmembers.php b/ruty/mails/program/actions/contacts/group_addmembers.php new file mode 100644 index 0000000..d34e858 --- /dev/null +++ b/ruty/mails/program/actions/contacts/group_addmembers.php @@ -0,0 +1,86 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_group_addmembers extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + $contacts = self::contact_source($source); + + if ($contacts->readonly || !$contacts->groups) { + $rcmail->output->show_message('sourceisreadonly', 'warning'); + $rcmail->output->send(); + } + + $gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST); + $ids = self::get_cids($source); + $result = false; + + if ($gid && $ids) { + $plugin = $rcmail->plugins->exec_hook('group_addmembers', [ + 'group_id' => $gid, + 'ids' => $ids, + 'source' => $source, + ]); + + $contacts->set_group($gid); + $num2add = count($plugin['ids']); + + if (empty($plugin['abort'])) { + if ( + ($maxnum = $rcmail->config->get('max_group_members')) + && ($contacts->count()->count + $num2add > $maxnum) + ) { + $rcmail->output->show_message('maxgroupmembersreached', 'warning', ['max' => $maxnum]); + $rcmail->output->send(); + } + + $result = $contacts->add_to_group($gid, $plugin['ids']); + } + else { + $result = $plugin['result']; + } + } + + if ($result) { + $rcmail->output->show_message('contactaddedtogroup', 'confirmation'); + } + else if (!empty($plugin['abort']) || $contacts->get_error()) { + $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving'; + $rcmail->output->show_message($error, 'error'); + } + else { + $message = !empty($plugin['message']) ? $plugin['message'] : 'nogroupassignmentschanged'; + $rcmail->output->show_message($message); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/group_create.php b/ruty/mails/program/actions/contacts/group_create.php new file mode 100644 index 0000000..d72da5b --- /dev/null +++ b/ruty/mails/program/actions/contacts/group_create.php @@ -0,0 +1,67 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_group_create extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + $contacts = self::contact_source($source); + + if ($contacts->readonly || !$contacts->groups) { + $rcmail->output->show_message('sourceisreadonly', 'warning'); + $rcmail->output->send(); + } + + if ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true))) { + $plugin = $rcmail->plugins->exec_hook('group_create', [ + 'name' => $name, + 'source' => $source, + ]); + + if (empty($plugin['abort'])) { + $created = $contacts->create_group($plugin['name']); + } + else { + $created = $plugin['result']; + } + } + + if (!empty($created)) { + $rcmail->output->show_message('groupcreated', 'confirmation'); + $rcmail->output->command('insert_contact_group', ['source' => $source] + $created); + } + else if (empty($created)) { + $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving'; + $rcmail->output->show_message($error, 'error'); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/group_delete.php b/ruty/mails/program/actions/contacts/group_delete.php new file mode 100644 index 0000000..a07098b --- /dev/null +++ b/ruty/mails/program/actions/contacts/group_delete.php @@ -0,0 +1,67 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_group_delete extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + $contacts = self::contact_source($source); + + if ($contacts->readonly || !$contacts->groups) { + $rcmail->output->show_message('sourceisreadonly', 'warning'); + $rcmail->output->send(); + } + + if ($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST)) { + $plugin = $rcmail->plugins->exec_hook('group_delete', [ + 'group_id' => $gid, + 'source' => $source, + ]); + + if (empty($plugin['abort'])) { + $deleted = $contacts->delete_group($gid); + } + else { + $deleted = $plugin['result']; + } + } + + if (!empty($deleted)) { + $rcmail->output->show_message('groupdeleted', 'confirmation'); + $rcmail->output->command('remove_group_item', ['source' => $source, 'id' => $gid]); + } + else { + $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving'; + $rcmail->output->show_message($error, 'error'); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/group_delmembers.php b/ruty/mails/program/actions/contacts/group_delmembers.php new file mode 100644 index 0000000..16f664e --- /dev/null +++ b/ruty/mails/program/actions/contacts/group_delmembers.php @@ -0,0 +1,71 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_group_delmembers extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + $contacts = self::contact_source($source); + + if ($contacts->readonly || !$contacts->groups) { + $rcmail->output->show_message('sourceisreadonly', 'warning'); + $rcmail->output->send(); + } + + $gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST); + $ids = self::get_cids($source); + + if ($gid && $ids) { + $plugin = $rcmail->plugins->exec_hook('group_delmembers', [ + 'group_id' => $gid, + 'ids' => $ids, + 'source' => $source, + ]); + + if (empty($plugin['abort'])) { + $result = $contacts->remove_from_group($gid, $plugin['ids']); + } + else { + $result = $plugin['result']; + } + } + + if (!empty($result)) { + $rcmail->output->show_message('contactremovedfromgroup', 'confirmation'); + $rcmail->output->command('remove_group_contacts', ['source' => $source, 'gid' => $gid]); + } + else { + $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving'; + $rcmail->output->show_message($error, 'error'); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/group_rename.php b/ruty/mails/program/actions/contacts/group_rename.php new file mode 100644 index 0000000..cc8cf72 --- /dev/null +++ b/ruty/mails/program/actions/contacts/group_rename.php @@ -0,0 +1,77 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_group_rename extends rcmail_action_contacts_index +{ + // only process ajax requests + protected static $mode = self::MODE_AJAX; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + $contacts = self::contact_source($source); + + if ($contacts->readonly || !$contacts->groups) { + $rcmail->output->show_message('sourceisreadonly', 'warning'); + $rcmail->output->send(); + } + + if ( + ($gid = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_POST)) + && ($name = trim(rcube_utils::get_input_string('_name', rcube_utils::INPUT_POST, true))) + ) { + $newgid = null; + $plugin = $rcmail->plugins->exec_hook('group_rename', [ + 'group_id' => $gid, + 'name' => $name, + 'source' => $source, + ]); + + if (empty($plugin['abort'])) { + $newname = $contacts->rename_group($gid, $plugin['name'], $newgid); + } + else { + $newname = $plugin['result']; + } + } + + if (!empty($newname)) { + $rcmail->output->show_message('grouprenamed', 'confirmation'); + $rcmail->output->command('update_contact_group', [ + 'source' => $source, + 'id' => $gid, + 'name' => $newname, + 'newid' => $newgid ?? null + ]); + } + else { + $error = !empty($plugin['message']) ? $plugin['message'] : 'errorsaving'; + $rcmail->output->show_message($error, 'error'); + } + + // send response + $rcmail->output->send(); + } +} diff --git a/ruty/mails/program/actions/contacts/import.php b/ruty/mails/program/actions/contacts/import.php new file mode 100644 index 0000000..295d377 --- /dev/null +++ b/ruty/mails/program/actions/contacts/import.php @@ -0,0 +1,486 @@ + | + | Author: Aleksander Machniak | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_import extends rcmail_action_contacts_index +{ + const UPLOAD_ERR_CSV_FIELDS = 101; + + protected static $stats; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + $importstep = 'import_form'; + $has_map = isset($_POST['_map']) && is_array($_POST['_map']); + + if ($has_map || (isset($_FILES['_file']) && is_array($_FILES['_file']))) { + $replace = (bool) rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC); + $target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC); + $with_groups = (int) rcube_utils::get_input_string('_groups', rcube_utils::INPUT_GPC); + + // reload params for CSV field mapping screen + if ($has_map && !empty($_SESSION['contactcsvimport']['params'])) { + $params = $_SESSION['contactcsvimport']['params']; + + $replace = $params['replace']; + $target = $params['target']; + $with_groups = $params['with_groups']; + } + + $vcards = []; + $csvs = []; + $map = []; + $upload_error = null; + + $CONTACTS = $rcmail->get_address_book($target, true); + + if (!$CONTACTS->groups) { + $with_groups = false; + } + + if ($CONTACTS->readonly) { + $rcmail->output->show_message('addresswriterror', 'error'); + } + else { + $filepaths = []; + if ($has_map) { + $filepaths = $_SESSION['contactcsvimport']['files']; + } + else { + foreach ((array) $_FILES['_file']['tmp_name'] as $i => $filepath) { + // Process uploaded file if there is no error + $err = $_FILES['_file']['error'][$i]; + + if ($err) { + $upload_error = $err; + } + else { + $filepaths[] = $filepath; + } + } + } + + foreach ($filepaths as $filepath) { + $file_content = file_get_contents($filepath); + + // let rcube_vcard do the hard work :-) + $vcard_o = new rcube_vcard(); + $vcard_o->extend_fieldmap($CONTACTS->vcard_map); + $v_list = $vcard_o->import($file_content); + + if (!empty($v_list)) { + $vcards = array_merge($vcards, $v_list); + continue; + } + + // no vCards found, try CSV + $csv = new rcube_csv2vcard($_SESSION['language']); + + if ($has_map) { + $skip_head = isset($_POST['_skip_header']); + $map = rcube_utils::get_input_value('_map', rcube_utils::INPUT_GPC); + $map = array_filter($map); + + $csv->set_map($map); + $csv->import($file_content, false, $skip_head); + + unlink($filepath); + } + else { + // save uploaded file for the real import in the next step + $temp_csv = rcube_utils::temp_filename('csvimpt'); + if (move_uploaded_file($filepath, $temp_csv) && file_exists($temp_csv)) { + $fields = $csv->get_fields(); + $last_map = $map; + $map = $csv->import($file_content, true); + + // when multiple CSV files are uploaded check they all have the same structure + if ($last_map && $last_map !== $map) { + $csvs = []; + $upload_error = self::UPLOAD_ERR_CSV_FIELDS; + break; + } + + $csvs[] = $temp_csv; + } + else { + $upload_error = UPLOAD_ERR_CANT_WRITE; + } + + continue; + } + + $v_list = $csv->export(); + + if (!empty($v_list)) { + $vcards = array_merge($vcards, $v_list); + } + } + } + + if (count($csvs) > 0) { + // csv import, show field mapping options + $importstep = 'import_map'; + + $_SESSION['contactcsvimport']['files'] = $csvs; + $_SESSION['contactcsvimport']['params'] = [ + 'replace' => $replace, + 'target' => $target, + 'with_groups' => $with_groups, + 'fields' => !empty($fields) ? $fields : [], + ]; + + // Stored separately due to nested array limitations in session + $_SESSION['contactcsvimport']['map'] = $map; + + // Re-enable the import button + $rcmail->output->command('parent.import_state_set', 'error'); + } + elseif (count($vcards) > 0) { + // import vcards + self::$stats = new stdClass; + self::$stats->names = []; + self::$stats->skipped_names = []; + self::$stats->count = count($vcards); + self::$stats->inserted = 0; + self::$stats->skipped = 0; + self::$stats->invalid = 0; + self::$stats->errors = 0; + + if ($replace) { + $CONTACTS->delete_all($CONTACTS->groups && $with_groups < 2); + } + + if ($with_groups) { + $import_groups = $CONTACTS->list_groups(); + } + + foreach ($vcards as $vcard) { + $a_record = $vcard->get_assoc(); + + // Generate contact's display name (must be before validation), the same we do in save.inc + if (empty($a_record['name'])) { + $a_record['name'] = rcube_addressbook::compose_display_name($a_record, true); + // Reset it if equals to email address (from compose_display_name()) + if ($a_record['name'] == ($a_record['email'][0] ?? null)) { + $a_record['name'] = ''; + } + } + + // skip invalid (incomplete) entries + if (!$CONTACTS->validate($a_record, true)) { + self::$stats->invalid++; + continue; + } + + // We're using UTF8 internally + $email = null; + if (isset($vcard->email[0])) { + $email = $vcard->email[0]; + $email = rcube_utils::idn_to_utf8($email); + } + + if (!$replace) { + $existing = null; + // compare e-mail address + if ($email) { + $existing = $CONTACTS->search('email', $email, 1, false); + } + // compare display name if email not found + if ((!$existing || !$existing->count) && $vcard->displayname) { + $existing = $CONTACTS->search('name', $vcard->displayname, 1, false); + } + if ($existing && $existing->count) { + self::$stats->skipped++; + self::$stats->skipped_names[] = $vcard->displayname ?: $email; + continue; + } + } + + $a_record['vcard'] = $vcard->export(); + + $plugin = $rcmail->plugins->exec_hook('contact_create', ['record' => $a_record, 'source' => null]); + $a_record = $plugin['record']; + + // insert record and send response + if (empty($plugin['abort'])) { + $success = $CONTACTS->insert($a_record); + } + else { + $success = $plugin['result']; + } + + if ($success) { + // assign groups for this contact (if enabled) + if ($with_groups && !empty($a_record['groups'])) { + foreach (explode(',', $a_record['groups'][0]) as $group_name) { + if ($group_id = self::import_group_id($group_name, $CONTACTS, $with_groups == 1, $import_groups)) { + $CONTACTS->add_to_group($group_id, $success); + } + } + } + + self::$stats->inserted++; + self::$stats->names[] = $a_record['name'] ?: $email; + } + else { + self::$stats->errors++; + } + } + + $importstep = 'import_confirm'; + $_SESSION['contactcsvimport'] = null; + + $rcmail->output->command('parent.import_state_set', self::$stats->inserted ? 'reload' : 'ok'); + } + else { + if ($upload_error == self::UPLOAD_ERR_CSV_FIELDS) { + $rcmail->output->show_message('csvfilemismatch', 'error'); + } + else { + self::upload_error($upload_error); + } + + $rcmail->output->command('parent.import_state_set', 'error'); + } + } + + $rcmail->output->set_pagetitle($rcmail->gettext('importcontacts')); + + $rcmail->output->add_handlers([ + 'importstep' => [$this, $importstep], + ]); + + // render page + if ($rcmail->output->template_exists('contactimport')) { + $rcmail->output->send('contactimport'); + } + else { + $rcmail->output->send('importcontacts'); // deprecated + } + } + + /** + * Handler function to display the import/upload form + */ + public static function import_form($attrib) + { + $rcmail = rcmail::get_instance(); + $target = rcube_utils::get_input_string('_target', rcube_utils::INPUT_GPC); + + $attrib += ['id' => 'rcmImportForm']; + + $writable_books = $rcmail->get_address_sources(true, true); + $max_filesize = self::upload_init(); + + $form = ''; + $hint = $rcmail->gettext(['id' => 'importfile', 'name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]]); + $table = new html_table(['cols' => 2]); + $upload = new html_inputfield([ + 'type' => 'file', + 'name' => '_file[]', + 'id' => 'rcmimportfile', + 'size' => 40, + 'multiple' => 'multiple', + 'class' => 'form-control-file', + ]); + + $table->add('title', html::label('rcmimportfile', $rcmail->gettext('importfromfile'))); + $table->add(null, $upload->show() . html::div('hint', $hint)); + + // addressbook selector + if (count($writable_books) > 1) { + $select = new html_select([ + 'name' => '_target', + 'id' => 'rcmimporttarget', + 'is_escaped' => true, + 'class' => 'custom-select' + ]); + + foreach ($writable_books as $book) { + $select->add($book['name'], $book['id']); + } + + $table->add('title', html::label('rcmimporttarget', $rcmail->gettext('importtarget'))); + $table->add(null, $select->show($target)); + } + else { + $abook = new html_hiddenfield(['name' => '_target', 'value' => key($writable_books)]); + $form .= $abook->show(); + } + + $form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']); + + // selector for group import options + if (count($writable_books) >= 1 || $writable_books[0]->groups) { + $select = new html_select([ + 'name' => '_groups', + 'id' => 'rcmimportgroups', + 'is_escaped' => true, + 'class' => 'custom-select' + ]); + $select->add($rcmail->gettext('none'), '0'); + $select->add($rcmail->gettext('importgroupsall'), '1'); + $select->add($rcmail->gettext('importgroupsexisting'), '2'); + + $table->add('title', html::label('rcmimportgroups', $rcmail->gettext('importgroups'))); + $table->add(null, $select->show(rcube_utils::get_input_value('_groups', rcube_utils::INPUT_GPC))); + } + + // checkbox to replace the entire address book + $check_replace = new html_checkbox(['name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace']); + $table->add('title', html::label('rcmimportreplace', $rcmail->gettext('importreplace'))); + $table->add(null, $check_replace->show(rcube_utils::get_input_string('_replace', rcube_utils::INPUT_GPC))); + + $form .= $table->show(['id' => null] + $attrib); + + // remove any info left over info from previous import attempts + $_SESSION['contactcsvimport'] = null; + + $rcmail->output->set_env('writable_source', !empty($writable_books)); + $rcmail->output->add_label('selectimportfile','importwait'); + $rcmail->output->add_gui_object('importform', $attrib['id']); + + $attrib = [ + 'action' => $rcmail->url('import'), + 'method' => 'post', + 'enctype' => 'multipart/form-data' + ] + $attrib; + + return html::p(null, rcube::Q($rcmail->gettext('importdesc'), 'show')) + . $rcmail->output->form_tag($attrib, $form); + } + + /** + * Render the field mapping page for the CSV import process + */ + public static function import_map($attrib) + { + $rcmail = rcmail::get_instance(); + $params = $_SESSION['contactcsvimport']['params']; + + // hide groups field from list when group import disabled + if (empty($params['with_groups'])) { + unset($params['fields']['groups']); + } + + $fieldlist = new html_select(['name' => '_map[]']); + $fieldlist->add($rcmail->gettext('fieldnotmapped'), ''); + foreach ($params['fields'] as $id => $name) { + $fieldlist->add($name, $id); + } + + $field_table = new html_table(['cols' => 2] + $attrib); + + if ($classes = $attrib['table-header-class']) { + $field_table->set_header_attribs($classes); + } + + $field_table->add_header($attrib['table-col-source-class'] ?: null, $rcmail->gettext('source')); + $field_table->add_header($attrib['table-col-destination-class'] ?: null, $rcmail->gettext('destination')); + + $map = $_SESSION['contactcsvimport']['map']; + foreach ($map['source'] as $i => $name) { + $field_table->add('title', html::label('rcmimportmap' . $i, rcube::Q($name))); + $field_table->add(null, $fieldlist->show(array_key_exists($i, $map['destination']) ? $map['destination'][$i] : '', ['id' => 'rcmimportmap' . $i])); + } + + $form = ''; + $form .= html::tag('input', ['type' => 'hidden', 'name' => '_unlock', 'value' => '']); + + // show option to import data from first line of the file + $check_header = new html_checkbox(['name' => '_skip_header', 'value' => 1, 'id' => 'rcmskipheader']); + $form .= html::p(null, html::label('rcmskipheader', $check_header->show(1) . $rcmail->gettext('skipheader'))); + + $form .= $field_table->show(); + + $attrib = ['action' => $rcmail->url('import'), 'method' => 'post'] + $attrib + ['id' => 'rcmImportFormMap']; + + $rcmail->output->add_gui_object('importformmap', $attrib['id']); + + return html::p(null, rcube::Q($rcmail->gettext('importmapdesc'), 'show')) + . $rcmail->output->form_tag($attrib, $form); + } + + /** + * Render the confirmation page for the import process + */ + public static function import_confirm($attrib) + { + $rcmail = rcmail::get_instance(); + $vars = get_object_vars(self::$stats); + $vars['names'] = $vars['skipped_names'] = ''; + + $content = html::p(null, $rcmail->gettext([ + 'name' => 'importconfirm', + 'nr' => self::$stats->inserted, + 'vars' => $vars, + ]) . (self::$stats->names ? ':' : '.') + ); + + if (self::$stats->names) { + $content .= html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->names))); + } + + if (self::$stats->skipped) { + $content .= html::p(null, $rcmail->gettext([ + 'name' => 'importconfirmskipped', + 'nr' => self::$stats->skipped, + 'vars' => $vars, + ]) . ':') + . html::p('em', join(', ', array_map(['rcube', 'Q'], self::$stats->skipped_names))); + } + + return html::div($attrib, $content); + } + + /** + * Returns the matching group id. If group doesn't exist, it'll be created if allowed. + */ + public static function import_group_id($group_name, $contacts, $create, &$import_groups) + { + $group_id = 0; + foreach ($import_groups as $group) { + if (strtolower($group['name']) === strtolower($group_name)) { + $group_id = $group['ID']; + break; + } + } + + // create a new group + if (!$group_id && $create) { + $new_group = $contacts->create_group($group_name); + + if (empty($new_group['ID'])) { + $new_group['ID'] = $new_group['id']; + } + + $import_groups[] = $new_group; + $group_id = $new_group['ID']; + } + + return $group_id; + } +} diff --git a/ruty/mails/program/actions/contacts/index.php b/ruty/mails/program/actions/contacts/index.php new file mode 100644 index 0000000..63c4ba2 --- /dev/null +++ b/ruty/mails/program/actions/contacts/index.php @@ -0,0 +1,1524 @@ + | + +-----------------------------------------------------------------------+ +*/ + +class rcmail_action_contacts_index extends rcmail_action +{ + public static $aliases = [ + 'add' => 'edit', + ]; + + protected static $SEARCH_MODS_DEFAULT = [ + 'name' => 1, + 'firstname' => 1, + 'surname' => 1, + 'email' => 1, + '*' => 1, + ]; + + /** + * General definition of contact coltypes + */ + public static $CONTACT_COLTYPES = [ + 'name' => [ + 'size' => 40, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'name', + 'category' => 'main' + ], + 'firstname' => [ + 'size' => 19, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'firstname', + 'category' => 'main' + ], + 'surname' => [ + 'size' => 19, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'surname', + 'category' => 'main' + ], + 'email' => [ + 'size' => 40, + 'maxlength' => 254, + 'label' => 'email', + 'subtypes' => ['home', 'work', 'other'], + 'category' => 'main' + ], + 'middlename' => [ + 'size' => 19, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'middlename', + 'category' => 'main' + ], + 'prefix' => [ + 'size' => 8, + 'maxlength' => 20, + 'limit' => 1, + 'label' => 'nameprefix', + 'category' => 'main' + ], + 'suffix' => [ + 'size' => 8, + 'maxlength' => 20, + 'limit' => 1, + 'label' => 'namesuffix', + 'category' => 'main' + ], + 'nickname' => [ + 'size' => 40, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'nickname', + 'category' => 'main' + ], + 'jobtitle' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'jobtitle', + 'category' => 'main' + ], + 'organization' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'organization', + 'category' => 'main' + ], + 'department' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'department', + 'category' => 'main' + ], + 'gender' => [ + 'type' => 'select', + 'limit' => 1, + 'label' => 'gender', + 'category' => 'personal', + 'options' => [ + 'male' => 'male', + 'female' => 'female' + ], + ], + 'maidenname' => [ + 'size' => 40, + 'maxlength' => 50, + 'limit' => 1, + 'label' => 'maidenname', + 'category' => 'personal' + ], + 'phone' => [ + 'size' => 40, + 'maxlength' => 20, + 'label' => 'phone', + 'category' => 'main', + 'subtypes' => ['home', 'home2', 'work', 'work2', 'mobile', 'main', 'homefax', 'workfax', 'car', + 'pager', 'video', 'assistant', 'other'], + ], + 'address' => [ + 'type' => 'composite', + 'label' => 'address', + 'subtypes' => ['home', 'work', 'other'], + 'category' => 'main', + 'childs' => [ + 'street' => [ + 'label' => 'street', + 'size' => 40, + 'maxlength' => 50, + ], + 'locality' => [ + 'label' => 'locality', + 'size' => 28, + 'maxlength' => 50, + ], + 'zipcode' => [ + 'label' => 'zipcode', + 'size' => 8, + 'maxlength' => 15, + ], + 'region' => [ + 'label' => 'region', + 'size' => 12, + 'maxlength' => 50, + ], + 'country' => [ + 'label' => 'country', + 'size' => 40, + 'maxlength' => 50, + ], + ], + ], + 'birthday' => [ + 'type' => 'date', + 'size' => 12, + 'maxlength' => 16, + 'label' => 'birthday', + 'limit' => 1, + 'render_func' => 'rcmail_action_contacts_index::format_date_col', + 'category' => 'personal' + ], + 'anniversary' => [ + 'type' => 'date', + 'size' => 12, + 'maxlength' => 16, + 'label' => 'anniversary', + 'limit' => 1, + 'render_func' => 'rcmail_action_contacts_index::format_date_col', + 'category' => 'personal' + ], + 'website' => [ + 'size' => 40, + 'maxlength' => 128, + 'label' => 'website', + 'subtypes' => ['homepage', 'work', 'blog', 'profile', 'other'], + 'category' => 'main' + ], + 'im' => [ + 'size' => 40, + 'maxlength' => 128, + 'label' => 'instantmessenger', + 'subtypes' => ['aim', 'icq', 'msn', 'yahoo', 'jabber', 'skype', 'other'], + 'category' => 'main' + ], + 'notes' => [ + 'type' => 'textarea', + 'size' => 40, + 'rows' => 15, + 'maxlength' => 500, + 'label' => 'notes', + 'limit' => 1 + ], + 'photo' => [ + 'type' => 'image', + 'limit' => 1, + 'category' => 'main' + ], + 'assistant' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'assistant', + 'category' => 'personal' + ], + 'manager' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'manager', + 'category' => 'personal' + ], + 'spouse' => [ + 'size' => 40, + 'maxlength' => 128, + 'limit' => 1, + 'label' => 'spouse', + 'category' => 'personal' + ], + ]; + + protected static $CONTACTS; + protected static $SOURCE_ID; + protected static $contact; + + /** + * Request handler. + * + * @param array $args Arguments from the previous step(s) + */ + public function run($args = []) + { + $rcmail = rcmail::get_instance(); + + // Prepare coltypes + foreach (self::$CONTACT_COLTYPES as $idx => $val) { + if (!empty($val['label'])) { + self::$CONTACT_COLTYPES[$idx]['label'] = $rcmail->gettext($val['label']); + } + if (!empty($val['options'])) { + foreach ($val['options'] as $i => $v) { + self::$CONTACT_COLTYPES[$idx]['options'][$i] = $rcmail->gettext($v); + } + } + if (!empty($val['childs'])) { + foreach ($val['childs'] as $i => $v) { + self::$CONTACT_COLTYPES[$idx]['childs'][$i]['label'] = $rcmail->gettext($v['label']); + if (empty($v['type'])) { + self::$CONTACT_COLTYPES[$idx]['childs'][$i]['type'] = 'text'; + } + } + } + if (empty($val['type'])) { + self::$CONTACT_COLTYPES[$idx]['type'] = 'text'; + } + } + + // Addressbook UI + if (!$rcmail->action && !$rcmail->output->ajax_call) { + // add list of address sources to client env + $js_list = $rcmail->get_address_sources(); + + // count all/writeable sources + $writeable = 0; + $count = 0; + + foreach ($js_list as $sid => $s) { + $count++; + if (!$s['readonly']) { + $writeable++; + } + // unset hidden sources + if (!empty($s['hidden'])) { + unset($js_list[$sid]); + } + } + + $rcmail->output->set_env('display_next', (bool) $rcmail->config->get('display_next')); + $rcmail->output->set_env('search_mods', $rcmail->config->get('addressbook_search_mods', self::$SEARCH_MODS_DEFAULT)); + $rcmail->output->set_env('address_sources', $js_list); + $rcmail->output->set_env('writable_source', $writeable); + $rcmail->output->set_env('contact_move_enabled', $writeable > 1); + $rcmail->output->set_env('contact_copy_enabled', $writeable > 1 || ($writeable == 1 && count($js_list) > 1)); + + $rcmail->output->set_pagetitle($rcmail->gettext('contacts')); + + $_SESSION['addressbooks_count'] = $count; + $_SESSION['addressbooks_count_writeable'] = $writeable; + + // select address book + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + + // use first directory by default + if (!strlen($source) || !isset($js_list[$source])) { + $source = $rcmail->config->get('default_addressbook'); + if (!is_string($source) || !strlen($source) || !isset($js_list[$source])) { + $source = strval(key($js_list)); + } + } + + self::$CONTACTS = self::contact_source($source, true); + } + + // remove undo information... + if (!empty($_SESSION['contact_undo'])) { + // ...after timeout + $undo = $_SESSION['contact_undo']; + $undo_time = $rcmail->config->get('undo_timeout', 0); + if ($undo['ts'] < time() - $undo_time) { + $rcmail->session->remove('contact_undo'); + } + } + + // register UI objects + $rcmail->output->add_handlers([ + 'directorylist' => [$this, 'directory_list'], + 'savedsearchlist' => [$this, 'savedsearch_list'], + 'addresslist' => [$this, 'contacts_list'], + 'addresslisttitle' => [$this, 'contacts_list_title'], + 'recordscountdisplay' => [$this, 'rowcount_display'], + 'searchform' => [$rcmail->output, 'search_form'] + ]); + + // Disable qr-code if imagick, iconv or BaconQrCode is not installed + if (!$rcmail->output->ajax_call && rcmail_action_contacts_qrcode::check_support()) { + $rcmail->output->set_env('qrcode', true); + $rcmail->output->add_label('qrcode'); + } + } + + // instantiate a contacts object according to the given source + public static function contact_source($source = null, $init_env = false, $writable = false) + { + if ($source === null || !strlen((string) $source)) { + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + } + + $rcmail = rcmail::get_instance(); + $page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50)); + + // Get object + $contacts = $rcmail->get_address_book($source, $writable); + + if (!$contacts) { + return null; + } + + $contacts->set_pagesize($page_size); + + // set list properties and session vars + if (!empty($_GET['_page'])) { + $contacts->set_page(($_SESSION['page'] = intval($_GET['_page']))); + } + else { + $contacts->set_page($_SESSION['page'] ?? 1); + } + + if ($group = rcube_utils::get_input_string('_gid', rcube_utils::INPUT_GP)) { + $contacts->set_group($group); + } + + if (!$init_env) { + return $contacts; + } + + $rcmail->output->set_env('readonly', $contacts->readonly); + $rcmail->output->set_env('source', (string) $source); + $rcmail->output->set_env('group', $group); + + // reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object + if (is_array($contacts->coltypes)) { + // remove cols not listed by the backend class + $contact_cols = isset($contacts->coltypes[0]) ? array_flip($contacts->coltypes) : $contacts->coltypes; + self::$CONTACT_COLTYPES = array_intersect_key(self::$CONTACT_COLTYPES, $contact_cols); + + // add associative coltypes definition + if (empty($contacts->coltypes[0])) { + foreach ($contacts->coltypes as $col => $colprop) { + if (!empty($colprop['childs'])) { + foreach ($colprop['childs'] as $childcol => $childprop) { + $colprop['childs'][$childcol] = array_merge((array) self::$CONTACT_COLTYPES[$col]['childs'][$childcol], $childprop); + } + } + + if (isset(self::$CONTACT_COLTYPES[$col])) { + self::$CONTACT_COLTYPES[$col] = array_merge(self::$CONTACT_COLTYPES[$col], $colprop); + } + else { + self::$CONTACT_COLTYPES[$col] = $colprop; + } + } + } + } + + $rcmail->output->set_env('photocol', !empty(self::$CONTACT_COLTYPES['photo'])); + + return $contacts; + } + + public static function set_sourcename($abook) + { + $rcmail = rcmail::get_instance(); + + // get address book name (for display) + if ($abook && !empty($_SESSION['addressbooks_count']) && $_SESSION['addressbooks_count'] > 1) { + $name = $abook->get_name(); + if (!$name) { + $name = $rcmail->gettext('personaladrbook'); + } + + $rcmail->output->set_env('sourcename', html_entity_decode($name, ENT_COMPAT, 'UTF-8')); + } + } + + public static function directory_list($attrib) + { + + if (empty($attrib['id'])) { + $attrib['id'] = 'rcmdirectorylist'; + } + + $rcmail = rcmail::get_instance(); + $out = ''; + $jsdata = []; + + $line_templ = html::tag('li', + ['id' => 'rcmli%s', 'class' => '%s', 'noclose' => true], + html::a( + [ + 'href' => '%s', + 'rel' => '%s', + 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('list','%s',this)" + ], + '%s' + ) + ); + + $sources = (array) $rcmail->output->get_env('address_sources'); + reset($sources); + + // currently selected source + $current = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + + foreach ($sources as $j => $source) { + $id = strval(strlen($source['id']) ? $source['id'] : $j); + $js_id = rcube::JQ($id); + + // set class name(s) + $class_name = 'addressbook'; + if ($current === $id) { + $class_name .= ' selected'; + } + if (!empty($source['readonly'])) { + $class_name .= ' readonly'; + } + if (!empty($source['class_name'])) { + $class_name .= ' ' . $source['class_name']; + } + + $name = $source['name'] ?: $id; + $out .= sprintf($line_templ, + rcube_utils::html_identifier($id, true), + $class_name, + rcube::Q($rcmail->url(['_source' => $id])), + $source['id'], + $js_id, + $name + ); + + $groupdata = ['out' => $out, 'jsdata' => $jsdata, 'source' => $id]; + if (!empty($source['groups'])) { + $groupdata = self::contact_groups($groupdata); + } + $jsdata = $groupdata['jsdata']; + $out = $groupdata['out']; + $out .= ''; + } + + $rcmail->output->set_env('contactgroups', $jsdata); + $rcmail->output->set_env('collapsed_abooks', (string) $rcmail->config->get('collapsed_abooks','')); + $rcmail->output->add_gui_object('folderlist', $attrib['id']); + $rcmail->output->include_script('treelist.js'); + + // add some labels to client + $rcmail->output->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember', + 'newgroup', 'grouprename', 'searchsave', 'namex', 'save', 'import', 'importcontacts', + 'advsearch', 'search' + ); + + return html::tag('ul', $attrib, $out, html::$common_attrib); + } + + public static function savedsearch_list($attrib) + { + if (empty($attrib['id'])) { + $attrib['id'] = 'rcmsavedsearchlist'; + } + + $rcmail = rcmail::get_instance(); + $out = ''; + $line_templ = html::tag('li', + ['id' => 'rcmli%s', 'class' => '%s'], + html::a([ + 'href' => '#', + 'rel' => 'S%s', + 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listsearch', '%s', this)" + ], + '%s' + ) + ); + + // Saved searches + $sources = $rcmail->user->list_searches(rcube_user::SEARCH_ADDRESSBOOK); + foreach ($sources as $source) { + $id = $source['id']; + $js_id = rcube::JQ($id); + + // set class name(s) + $classes = ['contactsearch']; + if (!empty($source['class_name'])) { + $classes[] = $source['class_name']; + } + + $out .= sprintf($line_templ, + rcube_utils::html_identifier('S' . $id, true), + join(' ', $classes), + $id, + $js_id, + rcube::Q($source['name'] ?: $id) + ); + } + + $rcmail->output->add_gui_object('savedsearchlist', $attrib['id']); + + return html::tag('ul', $attrib, $out, html::$common_attrib); + } + + public static function contact_groups($args) + { + $rcmail = rcmail::get_instance(); + $groups = $rcmail->get_address_book($args['source'])->list_groups(); + $groups_html = ''; + + if (!empty($groups)) { + $line_templ = html::tag('li', + ['id' => 'rcmli%s', 'class' => 'contactgroup'], + html::a([ + 'href' => '#', + 'rel' => '%s:%s', + 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)" + ], + '%s' + ) + ); + + // append collapse/expand toggle and open a new
    + $is_collapsed = strpos($rcmail->config->get('collapsed_abooks',''), '&'.rawurlencode($args['source']).'&') !== false; + $args['out'] .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); + + foreach ($groups as $group) { + $groups_html .= sprintf($line_templ, + rcube_utils::html_identifier('G' . $args['source'] . $group['ID'], true), + $args['source'], + $group['ID'], + $args['source'], + $group['ID'], + rcube::Q($group['name']) + ); + + $args['jsdata']['G' . $args['source'] . $group['ID']] = [ + 'source' => $args['source'], + 'id' => $group['ID'], + 'name' => $group['name'], + 'type' => 'group' + ]; + } + } + + $style = !empty($is_collapsed) || empty($groups) ? 'display:none;' : null; + + $args['out'] .= html::tag('ul', ['class' => 'groups', 'style' => $style], $groups_html); + + return $args; + } + + // return the contacts list as HTML table + public static function contacts_list($attrib) + { + $rcmail = rcmail::get_instance(); + + // define list of cols to be displayed + $a_show_cols = ['name', 'action']; + + // add id to message list table if not specified + if (empty($attrib['id'])) { + $attrib['id'] = 'rcmAddressList'; + } + + // create XHTML table + $out = self::table_output($attrib, [], $a_show_cols, self::$CONTACTS->primary_key); + + // set client env + $rcmail->output->add_gui_object('contactslist', $attrib['id']); + $rcmail->output->set_env('current_page', (int) self::$CONTACTS->list_page); + $rcmail->output->include_script('list.js'); + + // add some labels to client + $rcmail->output->add_label('deletecontactconfirm', 'copyingcontact', 'movingcontact', 'contactdeleting'); + + return $out; + } + + public static function js_contacts_list($result, $prefix = '') + { + if (empty($result) || $result->count == 0) { + return; + } + + $rcmail = rcmail::get_instance(); + + // define list of cols to be displayed + $a_show_cols = ['name', 'action']; + + while ($row = $result->next()) { + $emails = rcube_addressbook::get_col_values('email', $row, true); + $row['CID'] = $row['ID']; + $row['email'] = reset($emails); + $source_id = $rcmail->output->get_env('source'); + $a_row_cols = []; + $type = !empty($row['_type']) ? $row['_type'] : 'person'; + $classes = [$type]; + + // build contact ID with source ID + if (isset($row['sourceid'])) { + $row['ID'] = $row['ID'].'-'.$row['sourceid']; + $source_id = $row['sourceid']; + } + + // format each col + foreach ($a_show_cols as $col) { + $val = null; + switch ($col) { + case 'name': + $val = rcube::Q(rcube_addressbook::compose_list_name($row)); + break; + + case 'action': + if ($type == 'group') { + $val = html::a([ + 'href' => '#list', + 'rel' => $row['ID'], + 'title' => $rcmail->gettext('listgroup'), + 'onclick' => sprintf( + "return %s.command('pushgroup',{'source':'%s','id':'%s'},this,event)", + rcmail_output::JS_OBJECT_NAME, + $source_id, + $row['CID'] + ), + 'class' => 'pushgroup', + 'data-action-link' => true, + ], + '»' + ); + } + else { + $val = null; + } + break; + + default: + $val = rcube::Q($row[$col]); + break; + } + + if ($val !== null) { + $a_row_cols[$col] = $val; + } + } + + if (!empty($row['readonly'])) { + $classes[] = 'readonly'; + } + + $rcmail->output->command($prefix . 'add_contact_row', $row['ID'], $a_row_cols, join(' ', $classes), + array_intersect_key($row, ['ID' => 1,'readonly' => 1, '_type' => 1, 'email' => 1,'name' => 1]) + ); + } + } + + public static function contacts_list_title($attrib) + { + $rcmail = rcmail::get_instance(); + $attrib += ['label' => 'contacts', 'id' => 'rcmabooklisttitle', 'tag' => 'span']; + unset($attrib['name']); + + $rcmail->output->add_gui_object('addresslist_title', $attrib['id']); + $rcmail->output->add_label('contacts','uponelevel'); + + return html::tag($attrib['tag'], $attrib, $rcmail->gettext($attrib['label']), html::$common_attrib); + } + + public static function rowcount_display($attrib) + { + $rcmail = rcmail::get_instance(); + + if (empty($attrib['id'])) { + $attrib['id'] = 'rcmcountdisplay'; + } + + $rcmail->output->add_gui_object('countdisplay', $attrib['id']); + + if (!empty($attrib['label'])) { + $_SESSION['contactcountdisplay'] = $attrib['label']; + } + + return html::span($attrib, $rcmail->gettext('loading')); + } + + public static function get_rowcount_text($result = null) + { + $rcmail = rcmail::get_instance(); + + // read nr of contacts + if (empty($result) && !empty(self::$CONTACTS)) { + $result = self::$CONTACTS->get_result(); + } + + if (empty($result) || $result->count == 0) { + return $rcmail->gettext('nocontactsfound'); + } + + $page_size = $rcmail->config->get('addressbook_pagesize', $rcmail->config->get('pagesize', 50)); + + return $rcmail->gettext([ + 'name' => !empty($_SESSION['contactcountdisplay']) ? $_SESSION['contactcountdisplay'] : 'contactsfromto', + 'vars' => [ + 'from' => $result->first + 1, + 'to' => min($result->count, $result->first + $page_size), + 'count' => $result->count + ] + ]); + } + + public static function get_type_label($type) + { + $rcmail = rcmail::get_instance(); + $label = 'type' . $type; + + if ($rcmail->text_exists($label, '*', $domain)) { + return $rcmail->gettext($label, $domain); + } + + if ( + preg_match('/\w+(\d+)$/', $label, $m) + && ($label = preg_replace('/(\d+)$/', '', $label)) + && $rcmail->text_exists($label, '*', $domain) + ) { + return $rcmail->gettext($label, $domain) . ' ' . $m[1]; + } + + return ucfirst($type); + } + + public static function contact_form($form, $record, $attrib = null) + { + $rcmail = rcmail::get_instance(); + + // group fields + $head_fields = [ + 'source' => ['source'], + 'names' => ['prefix','firstname','middlename','surname','suffix'], + 'displayname' => ['name'], + 'nickname' => ['nickname'], + 'organization' => ['organization'], + 'department' => ['department'], + 'jobtitle' => ['jobtitle'], + ]; + + // Allow plugins to modify contact form content + $plugin = $rcmail->plugins->exec_hook('contact_form', [ + 'form' => $form, + 'record' => $record, + 'head_fields' => $head_fields + ]); + + $form = $plugin['form']; + $record = $plugin['record']; + $head_fields = $plugin['head_fields']; + $edit_mode = $rcmail->action != 'show' && $rcmail->action != 'print'; + $compact = self::get_bool_attr($attrib, 'compact-form'); + $use_labels = self::get_bool_attr($attrib, 'use-labels'); + $with_source = self::get_bool_attr($attrib, 'with-source'); + $out = ''; + + if (!empty($attrib['deleteicon'])) { + $del_button = html::img([ + 'src' => $rcmail->output->get_skin_file($attrib['deleteicon']), + 'alt' => $rcmail->gettext('delete') + ]); + } + else { + $del_button = html::span('inner', $rcmail->gettext('delete')); + } + + unset($attrib['deleteicon']); + + // get default coltypes + $coltypes = self::$CONTACT_COLTYPES; + $coltype_labels = []; + $business_mode = $rcmail->config->get('contact_form_mode') === 'business'; + + foreach ($coltypes as $col => $prop) { + if (!empty($prop['subtypes'])) { + // re-order subtypes, so 'work' is before 'home' + if ($business_mode) { + $work_opts = array_filter($prop['subtypes'], function($var) { return strpos($var, 'work') !== false; }); + if (!empty($work_opts)) { + $coltypes[$col]['subtypes'] = $prop['subtypes'] = array_merge( + $work_opts, + array_diff($prop['subtypes'], $work_opts) + ); + } + } + + $subtype_names = array_map('rcmail_action_contacts_index::get_type_label', $prop['subtypes']); + $select_subtype = new html_select([ + 'name' => "_subtype_{$col}[]", + 'class' => 'contactselectsubtype custom-select', + 'title' => $prop['label'] . ' ' . $rcmail->gettext('type') + ]); + $select_subtype->add($subtype_names, $prop['subtypes']); + + $coltypes[$col]['subtypes_select'] = $select_subtype->show(); + } + + if (!empty($prop['childs'])) { + foreach ($prop['childs'] as $childcol => $cp) { + $coltype_labels[$childcol] = ['label' => $cp['label']]; + } + } + } + + foreach ($form as $section => $fieldset) { + // skip empty sections + if (empty($fieldset['content'])) { + continue; + } + + $select_add = new html_select([ + 'class' => 'addfieldmenu custom-select', + 'rel' => $section, + 'data-compact' => $compact ? "true" : null + ]); + + $select_add->add($rcmail->gettext('addfield'), ''); + $select_add_count = 0; + + // render head section with name fields (not a regular list of rows) + if ($section == 'head') { + $content = ''; + + // unset display name if it is composed from name parts + $dname = rcube_addressbook::compose_display_name(['name' => ''] + (array) $record); + if (isset($record['name']) && $record['name'] == $dname) { + unset($record['name']); + } + + foreach ($head_fields as $blockname => $colnames) { + $fields = ''; + $block_attr = ['class' => $blockname . (count($colnames) == 1 ? ' row' : '')]; + + foreach ($colnames as $col) { + if ($col == 'source') { + if (!$with_source || !($source = $rcmail->output->get_env('sourcename'))) { + continue; + } + + if (!$edit_mode) { + $record['source'] = $rcmail->gettext('addressbook') . ': ' . $source; + } + else if ($rcmail->action == 'add') { + $record['source'] = $source; + } + else { + continue; + } + } + // skip cols unknown to the backend + else if (empty($coltypes[$col])) { + continue; + } + + // skip cols not listed in the form definition + if (is_array($fieldset['content']) && !in_array($col, array_keys($fieldset['content']))) { + continue; + } + + // only string values are expected here + if (isset($record[$col]) && is_array($record[$col])) { + $record[$col] = join(' ', $record[$col]); + } + + if (!$edit_mode) { + if (!empty($record[$col])) { + $fields .= html::span('namefield ' . $col, rcube::Q($record[$col])) . ' '; + } + } + else { + $visible = true; + $colprop = []; + + if (!empty($fieldset['content'][$col])) { + $colprop += (array) $fieldset['content'][$col]; + } + + if (!empty($coltypes[$col])) { + $colprop += (array) $coltypes[$col]; + } + + if (empty($colprop['id'])) { + $colprop['id'] = 'ff_' . $col; + } + + if (empty($record[$col]) && empty($colprop['visible'])) { + $visible = false; + $colprop['style'] = $use_labels ? null : 'display:none'; + $select_add->add($colprop['label'], $col); + } + + if ($col == 'source') { + $input = self::source_selector(['id' => $colprop['id']]); + } + else { + $val = $record[$col] ?? null; + $input = rcube_output::get_edit_field($col, $val, $colprop); + } + + if ($use_labels) { + $_content = html::label($colprop['id'], rcube::Q($colprop['label'])) . html::div(null, $input); + if (count($colnames) > 1) { + $fields .= html::div(['class' => 'row', 'style' => $visible ? null : 'display:none'], $_content); + } + else { + $fields .= $_content; + $block_attr['style'] = $visible ? null : 'display:none'; + } + } + else { + $fields .= $input; + } + } + } + + if ($fields) { + $content .= html::div($block_attr, $fields); + } + } + + if ($edit_mode) { + $content .= html::p('addfield', $select_add->show(null)); + } + + $legend = !empty($fieldset['name']) ? html::tag('legend', null, rcube::Q($fieldset['name'])) : ''; + $out .= html::tag('fieldset', $attrib, $legend . $content, html::$common_attrib) ."\n"; + continue; + } + + $content = ''; + if (is_array($fieldset['content'])) { + foreach ($fieldset['content'] as $col => $colprop) { + // remove subtype part of col name + $tokens = explode(':', $col); + $field = $tokens[0]; + + if (empty($tokens[1])) { + $subtype = $business_mode ? 'work' : 'home'; + } + else { + $subtype = $tokens[1]; + } + + // skip cols unknown to the backend + if (empty($coltypes[$field]) && empty($colprop['value'])) { + continue; + } + + // merge colprop with global coltype configuration + if (!empty($coltypes[$field])) { + $colprop += $coltypes[$field]; + } + + if (!isset($colprop['type'])) { + $colprop['type'] = 'text'; + } + + $label = $colprop['label'] ?? $rcmail->gettext($col); + + // prepare subtype selector in edit mode + if ($edit_mode && isset($colprop['subtypes']) && is_array($colprop['subtypes'])) { + $subtype_names = array_map('rcmail_action_contacts_index::get_type_label', $colprop['subtypes']); + $select_subtype = new html_select([ + 'name' => "_subtype_{$col}[]", + 'class' => 'contactselectsubtype custom-select', + 'title' => $colprop['label'] . ' ' . $rcmail->gettext('type') + ]); + $select_subtype->add($subtype_names, $colprop['subtypes']); + } + else { + $select_subtype = null; + } + + $rows = ''; + + list($values, $subtypes) = self::contact_field_values($record, "$field:$subtype", $colprop); + + foreach ($values as $i => $val) { + if (!empty($subtypes[$i])) { + $subtype = $subtypes[$i]; + } + + $fc = intval($coltypes[$field]['count'] ?? 0); + $colprop['id'] = 'ff_' . $col . $fc; + $row_class = 'row'; + + // render composite field + if ($colprop['type'] == 'composite') { + $row_class .= ' composite'; + $composite = []; + $template = $rcmail->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}'); + $j = 0; + + foreach ($colprop['childs'] as $childcol => $cp) { + if (!empty($val) && is_array($val)) { + if (!empty($val[$childcol])) { + $childvalue = $val[$childcol]; + } + else { + $childvalue = $val[$j] ?? null; + } + } + else { + $childvalue = ''; + } + + if ($edit_mode) { + if (!empty($colprop['subtypes']) || $colprop['limit'] != 1) { + $cp['array'] = true; + } + + $cp_type = $cp['type'] ?? null; + $composite['{'.$childcol.'}'] = rcube_output::get_edit_field($childcol, $childvalue, $cp, $cp_type) . ' '; + } + else { + if (!empty($cp['render_func'])) { + $childval = call_user_func($cp['render_func'], $childvalue, $childcol); + } + else { + $childval = rcube::Q($childvalue); + } + + $composite['{' . $childcol . '}'] = html::span('data ' . $childcol, $childval) . ' '; + } + + $j++; + } + + $coltypes[$field] += (array) $colprop; + + if (isset($coltypes[$field]['count'])) { + $coltypes[$field]['count']++; + } + else { + $coltypes[$field]['count'] = 1; + } + + $val = preg_replace('/\{\w+\}/', '', strtr($template, $composite)); + + if ($compact) { + $val = html::div('content', str_replace('
    ', '', $val)); + } + } + else if ($edit_mode) { + // call callback to render/format value + if (!empty($colprop['render_func'])) { + $val = call_user_func($colprop['render_func'], $val, $col); + } + + $coltypes[$field] = (array) $colprop + $coltypes[$field]; + + if (!empty($colprop['subtypes']) || $colprop['limit'] != 1) { + $colprop['array'] = true; + } + + // load jquery UI datepicker for date fields + if (isset($colprop['type']) && $colprop['type'] == 'date') { + $colprop['class'] = (!empty($colprop['class']) ? $colprop['class'] . ' ' : '') . 'datepicker'; + if (empty($colprop['render_func'])) { + $val = self::format_date_col($val); + } + } + + $val = rcube_output::get_edit_field($col, $val, $colprop, $colprop['type']); + + if (empty($coltypes[$field]['count'])) { + $coltypes[$field]['count'] = 1; + } + else { + $coltypes[$field]['count']++; + } + } + else if (!empty($colprop['render_func'])) { + $val = call_user_func($colprop['render_func'], $val, $col); + } + else if (isset($colprop['options']) && isset($colprop['options'][$val])) { + $val = $colprop['options'][$val]; + } + else { + $val = rcube::Q($val); + } + + // use subtype as label + if (!empty($colprop['subtypes'])) { + $label = self::get_type_label($subtype); + } + + $_del_btn = html::a([ + 'href' => '#del', + 'class' => 'contactfieldbutton deletebutton', + 'title' => $rcmail->gettext('delete'), + 'rel' => $col + ], + $del_button + ); + + // add delete button/link + if (!$compact && $edit_mode + && (empty($colprop['visible']) || empty($colprop['limit']) || $colprop['limit'] > 1) + ) { + $val .= $_del_btn; + } + + // display row with label + if ($label) { + if ($rcmail->action == 'print') { + $_label = rcube::Q($colprop['label'] . ($label != $colprop['label'] ? ' (' . $label . ')' : '')); + if (!$compact) { + $_label = html::div('contactfieldlabel label', $_label); + } + } + else if ($select_subtype) { + $_label = $select_subtype->show($subtype); + if (!$compact) { + $_label = html::div('contactfieldlabel label', $_label); + } + } + else { + $_label = html::label(['class' => 'contactfieldlabel label', 'for' => $colprop['id']], rcube::Q($label)); + } + + if (!$compact) { + $val = html::div('contactfieldcontent ' . $colprop['type'], $val); + } + else { + $val .= $_del_btn; + } + + $rows .= html::div($row_class, $_label . $val); + } + // row without label + else { + $rows .= html::div($row_class, $compact ? $val : html::div('contactfield', $val)); + } + } + + // add option to the add-field menu + if (empty($colprop['limit']) || empty($coltypes[$field]['count']) || $coltypes[$field]['count'] < $colprop['limit']) { + $select_add->add($colprop['label'], $col); + $select_add_count++; + } + + // wrap rows in fieldgroup container + if ($rows) { + $c_class = 'contactfieldgroup ' + . (!empty($colprop['subtypes']) ? 'contactfieldgroupmulti ' : '') + . 'contactcontroller' . $col; + $with_label = !empty($colprop['subtypes']) && $rcmail->action != 'print'; + $content .= html::tag( + 'fieldset', + ['class' => $c_class], + ($with_label ? html::tag('legend', null, rcube::Q($colprop['label'])) : ' ') . $rows + ); + } + } + + if (!$content && (!$edit_mode || !$select_add_count)) { + continue; + } + + // also render add-field selector + if ($edit_mode) { + $content .= html::p('addfield', $select_add->show(null, ['style' => $select_add_count ? null : 'display:none'])); + } + + $content = html::div(['id' => 'contactsection' . $section], $content); + } + else { + $content = $fieldset['content']; + } + + if ($content) { + $fattribs = !empty($attrib['fieldset-class']) ? ['class' => $attrib['fieldset-class']] : null; + $fcontent = html::tag('legend', null, rcube::Q($fieldset['name'])) . $content; + $out .= html::tag('fieldset', $fattribs, $fcontent) . "\n"; + } + } + + if ($edit_mode) { + $rcmail->output->set_env('coltypes', $coltypes + $coltype_labels); + $rcmail->output->set_env('delbutton', $del_button); + $rcmail->output->add_label('delete'); + } + + return $out; + } + + public static function contact_field_values($record, $field_name, $colprop) + { + list($field, $subtype) = explode(':', $field_name); + + $subtypes = []; + $values = []; + + if (!empty($colprop['value'])) { + $values = (array) $colprop['value']; + } + else if (!empty($colprop['subtypes'])) { + // iterate over possible subtypes and collect values with their subtype + $c_values = rcube_addressbook::get_col_values($field, $record); + + foreach ($colprop['subtypes'] as $st) { + if (isset($c_values[$st])) { + foreach ((array) $c_values[$st] as $value) { + $i = count($values); + $subtypes[$i] = $st; + $values[$i] = $value; + } + + $c_values[$st] = null; + } + } + + // TODO: add $st to $select_subtype if missing ? + foreach ($c_values as $st => $vals) { + foreach ((array) $vals as $value) { + $i = count($values); + $subtypes[$i] = $st; + $values[$i] = $value; + } + } + } + else if (isset($record[$field_name])) { + $values = $record[$field_name]; + } + else if (isset($record[$field])) { + $values = $record[$field]; + } + + // hack: create empty values array to force this field to be displayed + if (empty($values) && !empty($colprop['visible'])) { + $values = ['']; + } + + if (!is_array($values)) { + // $values can be an object, don't use (array)$values syntax + $values = !empty($values) ? [$values] : []; + } + + return [$values, $subtypes]; + } + + public static function contact_photo($attrib) + { + if ($result = self::$CONTACTS->get_result()) { + $record = $result->first(); + } + else { + $record = ['photo' => null, '_type' => 'contact']; + } + + $rcmail = rcmail::get_instance(); + + if (!empty($record['_type']) && $record['_type'] == 'group' && !empty($attrib['placeholdergroup'])) { + $photo_img = $rcmail->output->abs_url($attrib['placeholdergroup'], true); + $photo_img = $rcmail->output->asset_url($photo_img); + } + elseif (!empty($attrib['placeholder'])) { + $photo_img = $rcmail->output->abs_url($attrib['placeholder'], true); + $photo_img = $rcmail->output->asset_url($photo_img); + } + else { + $photo_img = 'data:image/gif;base64,' . rcmail_output::BLANK_GIF; + } + + $rcmail->output->set_env('photo_placeholder', $photo_img); + + unset($attrib['placeholder']); + + $plugin = $rcmail->plugins->exec_hook('contact_photo', [ + 'record' => $record, + 'data' => $record['photo'] ?? null, + 'attrib' => $attrib + ]); + + // check if we have photo data from contact form + if (!empty(self::$contact)) { + if (!empty(self::$contact['photo'])) { + if (self::$contact['photo'] == '-del-') { + $record['photo'] = ''; + } + else if (!empty($_SESSION['contacts']['files'][self::$contact['photo']])) { + $record['photo'] = $file_id = self::$contact['photo']; + } + } + } + + $ff_value = ''; + + if (!empty($plugin['url'])) { + $photo_img = $plugin['url']; + } + else if (!empty($record['photo']) && preg_match('!^https?://!i', $record['photo'])) { + $photo_img = $record['photo']; + } + else if (!empty($record['photo'])) { + $url = ['_action' => 'photo', '_cid' => $record['ID'], '_source' => self::$SOURCE_ID]; + if (!empty($file_id)) { + $url['_photo'] = $ff_value = $file_id; + } + $photo_img = $rcmail->url($url); + } + else { + $ff_value = '-del-'; // will disable delete-photo action + } + + $content = html::div($attrib, html::img([ + 'src' => $photo_img, + 'alt' => $rcmail->gettext('contactphoto'), + 'onerror' => 'this.onerror = null; this.src = rcmail.env.photo_placeholder;', + ])); + + if (!empty(self::$CONTACT_COLTYPES['photo']) && ($rcmail->action == 'edit' || $rcmail->action == 'add')) { + $rcmail->output->add_gui_object('contactphoto', $attrib['id']); + $hidden = new html_hiddenfield(['name' => '_photo', 'id' => 'ff_photo', 'value' => $ff_value]); + $content .= $hidden->show(); + } + + return $content; + } + + public static function format_date_col($val) + { + $rcmail = rcmail::get_instance(); + return $rcmail->format_date($val, $rcmail->config->get('date_format', 'Y-m-d'), false); + } + + /** + * Updates saved search after data changed + */ + public static function search_update($return = false) + { + $rcmail = rcmail::get_instance(); + + if (empty($_REQUEST['_search'])) { + return false; + } + + $search_request = $_REQUEST['_search']; + + if (!isset($_SESSION['contact_search'][$search_request])) { + return false; + } + + $search = (array) $_SESSION['contact_search'][$search_request]; + $sort_col = $rcmail->config->get('addressbook_sort_col', 'name'); + $afields = $return ? $rcmail->config->get('contactlist_fields') : ['name', 'email']; + $records = []; + + foreach ($search as $s => $set) { + $source = $rcmail->get_address_book($s); + + // reset page + $source->set_page(1); + $source->set_pagesize(9999); + $source->set_search_set($set); + + // get records + $result = $source->list_records($afields); + + if (!$result->count) { + unset($search[$s]); + continue; + } + + if ($return) { + while ($row = $result->next()) { + $row['sourceid'] = $s; + $key = rcube_addressbook::compose_contact_key($row, $sort_col); + $records[$key] = $row; + } + unset($result); + } + + $search[$s] = $source->get_search_set(); + } + + $_SESSION['contact_search'][$search_request] = $search; + + return $records; + } + + /** + * Returns contact ID(s) and source(s) from GET/POST data + * + * @param string $filter Return contact identifier for this specific source + * @param int $request_type Type of the input var (rcube_utils::INPUT_*) + * + * @return array List of contact IDs per-source + */ + public static function get_cids($filter = null, $request_type = rcube_utils::INPUT_GPC) + { + // contact ID (or comma-separated list of IDs) is provided in two + // forms. If _source is an empty string then the ID is a string + // containing contact ID and source name in form: - + + $cid = rcube_utils::get_input_value('_cid', $request_type); + $source = rcube_utils::get_input_string('_source', rcube_utils::INPUT_GPC); + + if (is_array($cid)) { + return $cid; + } + + if (!is_string($cid) || !preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid)) { + return []; + } + + $cid = explode(',', $cid); + $got_source = strlen($source); + $result = []; + + // create per-source contact IDs array + foreach ($cid as $id) { + // extract source ID from contact ID (it's there in search mode) + // see #1488959 and #1488862 for reference + if (!$got_source) { + if ($sep = strrpos($id, '-')) { + $contact_id = substr($id, 0, $sep); + $source_id = (string) substr($id, $sep+1); + if (strlen($source_id)) { + $result[$source_id][] = $contact_id; + } + } + } + else { + if (substr($id, -($got_source+1)) === "-$source") { + $id = substr($id, 0, -($got_source+1)); + } + $result[$source][] = $id; + } + } + + return $filter !== null ? $result[$filter] : $result; + } + + /** + * Returns HTML code for an addressbook selector + * + * @param array $attrib Template object attributes + * + * @return string HTML code of a for the mailbox tree + */ + protected static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = []) + { + $out = ''; + $rcmail = rcmail::get_instance(); + $storage = $rcmail->get_storage(); + + foreach ($arrFolders as $folder) { + // skip exceptions (and its subfolders) + if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) { + continue; + } + + // skip folders in which it isn't possible to create subfolders + if (!empty($opts['skip_noinferiors'])) { + $attrs = $storage->folder_attributes($folder['id']); + if ($attrs && in_array_nocase('\\Noinferiors', $attrs)) { + continue; + } + } + + $folder_class = self::folder_classname($folder['id'], $folder['class'] ?? null); + $realname = $folder['realname'] ?? $realnames; + + if ($folder_class && !$realname && $rcmail->text_exists($folder_class)) { + $foldername = $rcmail->gettext($folder_class); + } + else { + $foldername = $folder['name']; + + // shorten the folder name to a given length + if ($maxlength && $maxlength > 1) { + $foldername = abbreviate_string($foldername, $maxlength); + } + } + + $select->add(str_repeat(' ', $nestLevel*4) . html::quote($foldername), $folder['id']); + + if (!empty($folder['folders'])) { + $out .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, + $select, $realnames, $nestLevel+1, $opts); + } + } + + return $out; + } + + /** + * Returns class name for the given folder if it is a special folder + * (including shared/other users namespace roots). + * + * @param string $folder_id IMAP Folder name + * @param string $fallback Fallback Folder CSS class name + * + * @return string|null CSS class name + */ + public static function folder_classname($folder_id, $fallback = null) + { + static $classes; + + if ($classes === null) { + $rcmail = rcmail::get_instance(); + $storage = $rcmail->get_storage(); + $classes = ['INBOX' => 'inbox']; + + // for these mailboxes we have css classes + foreach (['sent', 'drafts', 'trash', 'junk'] as $type) { + if (($mbox = $rcmail->config->get($type . '_mbox')) && !isset($classes[$mbox])) { + $classes[$mbox] = $type; + } + } + + // add classes for shared/other user namespace roots + foreach (['other', 'shared'] as $ns_name) { + if ($ns = $storage->get_namespace($ns_name)) { + foreach ($ns as $root) { + $root = substr($root[0], 0, -1); + if (strlen($root) && !isset($classes[$root])) { + $classes[$root] = "ns-$ns_name"; + } + } + } + } + } + + return !empty($classes[$folder_id]) ? $classes[$folder_id] : $fallback; + } + + /** + * Try to localize the given IMAP folder name. + * UTF-7 decode it in case no localized text was found + * + * @param string $name Folder name + * @param bool $with_path Enable path localization + * @param bool $path_remove Remove the path + * + * @return string Localized folder name in UTF-8 encoding + */ + public static function localize_foldername($name, $with_path = false, $path_remove = false) + { + $rcmail = rcmail::get_instance(); + $realnames = $rcmail->config->get('show_real_foldernames'); + + if (!$realnames && ($folder_class = self::folder_classname($name)) && $rcmail->text_exists($folder_class)) { + return $rcmail->gettext($folder_class); + } + + $storage = $rcmail->get_storage(); + $delimiter = $storage->get_hierarchy_delimiter(); + + // Remove the path + if ($path_remove) { + if (strpos($name, $delimiter)) { + $path = explode($delimiter, $name); + $name = array_pop($path); + } + } + // try to localize path of the folder + else if ($with_path && !$realnames) { + $path = explode($delimiter, $name); + $count = count($path); + + if ($count > 1) { + for ($i = 1; $i < $count; $i++) { + $folder = implode($delimiter, array_slice($path, 0, -$i)); + $folder_class = self::folder_classname($folder); + + if ($folder_class && $rcmail->text_exists($folder_class)) { + $name = implode($delimiter, array_slice($path, $count - $i)); + $name = rcube_charset::convert($name, 'UTF7-IMAP'); + + return $rcmail->gettext($folder_class) . $delimiter . $name; + } + } + } + } + + return rcube_charset::convert($name, 'UTF7-IMAP'); + } + + /** + * Localize folder path + */ + public static function localize_folderpath($path) + { + $rcmail = rcmail::get_instance(); + $protect_folders = $rcmail->config->get('protect_default_folders'); + $delimiter = $rcmail->storage->get_hierarchy_delimiter(); + $path = explode($delimiter, $path); + $result = []; + + foreach ($path as $idx => $dir) { + $directory = implode($delimiter, array_slice($path, 0, $idx+1)); + if ($protect_folders && $rcmail->storage->is_special_folder($directory)) { + unset($result); + $result[] = self::localize_foldername($directory); + } + else { + $result[] = rcube_charset::convert($dir, 'UTF7-IMAP'); + } + } + + return implode($delimiter, $result); + } + + /** + * Gets a value of a boolean attribute from template object attributes + * + * @param array $attributes Template object attributes + * @param string $name Attribute name + */ + public static function get_bool_attr($attributes, $name) + { + if (!isset($attributes[$name])) { + return false; + } + + return rcube_utils::get_boolean($attributes[$name]); + } +} diff --git a/ruty/mails/program/include/rcmail_attachment_handler.php b/ruty/mails/program/include/rcmail_attachment_handler.php new file mode 100644 index 0000000..2ee02e0 --- /dev/null +++ b/ruty/mails/program/include/rcmail_attachment_handler.php @@ -0,0 +1,393 @@ + | + | Author: Aleksander Machniak | + +-----------------------------------------------------------------------+ +*/ + +/** + * Unified access to attachment properties and body + * Unified for message parts as well as uploaded attachments + * + * @package Webmail + */ +class rcmail_attachment_handler +{ + public $filename; + public $size; + public $mimetype; + public $ident; + public $charset = RCUBE_CHARSET; + + private $message; + private $part; + private $upload; + private $body; + private $body_file; + private $download = false; + + /** + * Class constructor. + * Reads request parameters and initializes attachment/part props. + */ + public function __construct() + { + ob_end_clean(); + + $part_id = rcube_utils::get_input_string('_part', rcube_utils::INPUT_GET); + $file_id = rcube_utils::get_input_string('_file', rcube_utils::INPUT_GET); + $compose_id = rcube_utils::get_input_string('_id', rcube_utils::INPUT_GET); + $uid = rcube_utils::get_input_string('_uid', rcube_utils::INPUT_GET); + $rcube = rcube::get_instance(); + + $this->download = !empty($_GET['_download']); + + // similar code as in program/steps/mail/show.inc + if (!empty($uid)) { + $rcube->config->set('prefer_html', true); + $this->message = new rcube_message($uid, null, !empty($_GET['_safe'])); + + if ($this->part = $this->message->mime_parts[$part_id]) { + $this->filename = rcmail_action_mail_index::attachment_name($this->part); + $this->mimetype = $this->part->mimetype; + $this->size = $this->part->size; + $this->ident = $this->message->headers->messageID . ':' . $this->part->mime_id . ':' . $this->size . ':' . $this->mimetype; + $this->charset = $this->part->charset ?: RCUBE_CHARSET; + + if (empty($_GET['_frame'])) { + // allow post-processing of the attachment body + $plugin = $rcube->plugins->exec_hook('message_part_get', [ + 'uid' => $uid, + 'id' => $this->part->mime_id, + 'mimetype' => $this->mimetype, + 'part' => $this->part, + 'download' => $this->download, + ]); + + if ($plugin['abort']) { + exit; + } + + // overwrite modified vars from plugin + $this->mimetype = $plugin['mimetype']; + + if (!empty($plugin['body'])) { + $this->body = $plugin['body']; + $this->size = strlen($this->body); + } + } + } + } + else if ($file_id && $compose_id) { + $file_id = preg_replace('/^rcmfile/', '', $file_id); + + if (($compose = $_SESSION['compose_data_' . $compose_id]) + && ($this->upload = $compose['attachments'][$file_id]) + ) { + $this->filename = $this->upload['name']; + $this->mimetype = $this->upload['mimetype']; + $this->size = $this->upload['size']; + $this->ident = sprintf('%s:%s%s', $compose_id, $file_id, $this->size); + $this->charset = !empty($this->upload['charset']) ? $this->upload['charset'] : RCUBE_CHARSET; + } + } + + if (empty($this->part) && empty($this->upload)) { + header('HTTP/1.1 404 Not Found'); + exit; + } + + // check connection status + self::check_storage_status(); + + $this->mimetype = rcube_mime::fix_mimetype($this->mimetype); + } + + /** + * Remove temp files, etc. + */ + public function __destruct() + { + if ($this->body_file) { + @unlink($this->body_file); + } + } + + /** + * Check if the object is a message part not uploaded file + * + * @return bool True if the object is a message part + */ + public function is_message_part() + { + return !empty($this->message); + } + + /** + * Object/request status + * + * @return bool Status + */ + public function is_valid() + { + return !empty($this->part) || !empty($this->upload); + } + + /** + * Return attachment/part mimetype if this is an image + * of supported type. + * + * @return string Image mimetype + */ + public function image_type() + { + $part = (object) [ + 'filename' => $this->filename, + 'mimetype' => $this->mimetype, + ]; + + return rcmail_action_mail_index::part_image_type($part); + } + + /** + * Formatted attachment/part size (with units) + * + * @return string Attachment/part size (with units) + */ + public function size() + { + $part = $this->part ?: ((object) ['size' => $this->size, 'exact_size' => true]); + return rcmail_action::message_part_size($part); + } + + /** + * Returns, prints or saves the attachment/part body + */ + public function body($size = null, $fp = null) + { + // we may have the body in memory or file already + if ($this->body !== null) { + if ($fp == -1) { + echo $size ? substr($this->body, 0, $size) : $this->body; + } + else if ($fp) { + $result = fwrite($fp, $size ? substr($this->body, $size) : $this->body) !== false; + } + else { + $result = $size ? substr($this->body, 0, $size) : $this->body; + } + } + else if ($this->body_file) { + if ($size) { + $result = file_get_contents($this->body_file, false, null, 0, $size); + } + else { + $result = file_get_contents($this->body_file); + } + + if ($fp == -1) { + echo $result; + } + else if ($fp) { + $result = fwrite($fp, $result) !== false; + } + } + else if ($this->message) { + $result = $this->message->get_part_body($this->part->mime_id, false, 0, $fp); + + // check connection status + if (!$fp && $this->size && empty($result)) { + self::check_storage_status(); + } + } + else if ($this->upload) { + // This hook retrieves the attachment contents from the file storage backend + $attachment = rcube::get_instance()->plugins->exec_hook('attachment_get', $this->upload); + + if ($fp && $fp != -1) { + if ($attachment['data']) { + $result = fwrite($fp, $size ? substr($attachment['data'], 0, $size) : $attachment['data']) !== false; + } + else if ($attachment['path']) { + if ($fh = fopen($attachment['path'], 'rb')) { + $result = stream_copy_to_stream($fh, $fp, $size ? $size : -1); + } + } + } + else { + $data = $attachment['data'] ?? ''; + if (!$data && $attachment['path']) { + $data = file_get_contents($attachment['path']); + } + + if ($fp == -1) { + echo $size ? substr($data, 0, $size) : $data; + } + else { + $result = $size ? substr($data, 0, $size) : $data; + } + } + } + + return $result ?? null; + } + + /** + * Save the body to a file + * + * @param string $filename File name with path + * + * @return bool True on success, False on failure + */ + public function body_to_file($filename) + { + if ($filename && $this->size && ($fp = fopen($filename, 'w'))) { + $this->body(0, $fp); + $this->body_file = $filename; + fclose($fp); + @chmod($filename, 0600); + + return true; + } + + return false; + } + + /** + * Output attachment body with content filtering + */ + public function output($mimetype) + { + if (!$this->size) { + return false; + } + + $secure = stripos($mimetype, 'image/') === false || $this->download; + + // Remove