From b68022ca3782d5eb5a1a7ef6f8cf7abe1dc15bd6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9my=20Bobbio?= Date: Sat, 18 Jun 2011 17:03:11 +0200 Subject: [PATCH] Imported Upstream version 0.3 --- .htaccess | 32 +- CHANGELOG | 101 +- INSTALL | 98 +- INSTALL.orig | 212 + README | 6 +- SQL/mssql.initial.sql | 9 +- SQL/mysql.initial.sql | 3 - SQL/postgres.initial.sql | 2 + SQL/sqlite.initial.sql | 4 - UPGRADING | 2 + bin/decrypt.php | 70 + bin/modcss.php | 53 +- bin/quotaimg.php | 7 +- config/main.inc.php.dist | 52 +- config/mimetypes.php | 1 + index.php | 91 +- installer/check.php | 47 +- installer/config.php | 34 +- installer/index.php | 6 +- installer/rcube_install.php | 42 +- installer/test.php | 18 +- installer/utils.php | 49 +- .../additional_message_headers.php | 42 + plugins/archive/archive.js | 36 + plugins/archive/archive.php | 133 + .../archive/archive_act.png | Bin 4416 -> 3663 bytes plugins/archive/archive_pas.png | Bin 0 -> 977 bytes .../archive/foldericon.png | Bin 4447 -> 3312 bytes plugins/archive/localization/de_CH.inc | 8 + plugins/archive/localization/de_DE.inc | 8 + plugins/archive/localization/en_US.inc | 8 + plugins/archive/localization/fr_FR.inc | 9 + plugins/archive/localization/pl_PL.inc | 8 + plugins/autologon/autologon.php | 44 + .../database_attachments.php | 156 + plugins/debug_logger/debug_logger.php | 146 + plugins/debug_logger/runlog/runlog.php | 227 + plugins/emoticons/emoticons.php | 39 + .../example_addressbook.php | 42 + .../example_addressbook_backend.php | 72 + .../filesystem_attachments.php | 149 + plugins/help/config.inc.php.dist | 8 + plugins/help/content/about.html | 37 + plugins/help/content/license.html | 387 + plugins/help/help.php | 91 + plugins/help/localization/en_GB.inc | 8 + plugins/help/localization/en_US.inc | 8 + plugins/help/localization/et_EE.inc | 8 + plugins/help/localization/pl_PL.inc | 8 + plugins/help/localization/sv_SE.inc | 8 + plugins/help/skins/default/help.css | 26 + plugins/help/skins/default/help.gif | Bin 0 -> 1146 bytes .../help/skins/default/templates/help.html | 38 + .../http_authentication.php | 41 + plugins/managesieve/Changelog | 79 + plugins/managesieve/config.inc.php.dist | 28 + plugins/managesieve/lib/Net/Sieve.php | 1188 +++ plugins/managesieve/lib/rcube_sieve.php | 727 ++ plugins/managesieve/localization/bg_BG.inc | 50 + plugins/managesieve/localization/de_CH.inc | 52 + plugins/managesieve/localization/de_DE.inc | 53 + plugins/managesieve/localization/en_US.inc | 53 + plugins/managesieve/localization/es_ES.inc | 54 + plugins/managesieve/localization/et_EE.inc | 53 + plugins/managesieve/localization/fi_FI.inc | 49 + plugins/managesieve/localization/fr_FR.inc | 53 + plugins/managesieve/localization/gb_GB.inc | 53 + plugins/managesieve/localization/hu_HU.inc | 54 + plugins/managesieve/localization/it_IT.inc | 54 + plugins/managesieve/localization/nl_NL.inc | 49 + plugins/managesieve/localization/pl_PL.inc | 54 + plugins/managesieve/localization/pt_BR.inc | 53 + plugins/managesieve/localization/ru_RU.inc | 49 + plugins/managesieve/localization/sl_SI.inc | 53 + plugins/managesieve/localization/sv_SE.inc | 54 + plugins/managesieve/localization/uk_UA.inc | 54 + plugins/managesieve/localization/zh_CN.inc | 49 + plugins/managesieve/managesieve.js | 381 + plugins/managesieve/managesieve.php | 854 ++ .../skins/default/filter_add_act.png | Bin 0 -> 1391 bytes .../skins/default/filter_add_pas.png | Bin 0 -> 1409 bytes .../skins/default/filter_add_sel.png | Bin 0 -> 1305 bytes .../skins/default/filter_del_act.png | Bin 0 -> 1337 bytes .../skins/default/filter_del_pas.png | Bin 0 -> 1339 bytes .../skins/default/filter_del_sel.png | Bin 0 -> 1230 bytes .../skins/default/filter_down_act.png | Bin 0 -> 1327 bytes .../skins/default/filter_down_pas.png | Bin 0 -> 1350 bytes .../skins/default/filter_down_sel.png | Bin 0 -> 1231 bytes .../skins/default/filter_up_act.png | Bin 0 -> 1321 bytes .../skins/default/filter_up_pas.png | Bin 0 -> 1333 bytes .../skins/default/filter_up_sel.png | Bin 0 -> 1192 bytes .../managesieve/skins/default/managesieve.css | 157 + .../skins/default/templates/managesieve.html | 31 + .../default/templates/managesieveedit.html | 109 + plugins/markasjunk/junk_act.png | Bin 0 -> 1995 bytes plugins/markasjunk/junk_pas.png | Bin 0 -> 1988 bytes plugins/markasjunk/localization/en_US.inc | 7 + plugins/markasjunk/localization/et_EE.inc | 7 + plugins/markasjunk/localization/pl_PL.inc | 7 + plugins/markasjunk/localization/sv_SE.inc | 7 + plugins/markasjunk/markasjunk.js | 28 + plugins/markasjunk/markasjunk.php | 47 + .../new_user_dialog/localization/de_CH.inc | 7 + .../new_user_dialog/localization/de_DE.inc | 7 + .../new_user_dialog/localization/en_US.inc | 7 + .../new_user_dialog/localization/et_EE.inc | 7 + .../new_user_dialog/localization/pl_PL.inc | 7 + .../new_user_dialog/localization/sv_SE.inc | 7 + plugins/new_user_dialog/new_user_dialog.php | 109 + plugins/new_user_dialog/newuserdialog.css | 59 + .../new_user_identity/new_user_identity.php | 49 + plugins/password/README | 184 + plugins/password/config.inc.php.dist | 142 + plugins/password/drivers/chgsaslpasswd.c | 29 + plugins/password/drivers/directadmin.php | 483 ++ plugins/password/drivers/ldap.php | 186 + plugins/password/drivers/poppassd.php | 56 + plugins/password/drivers/sasl.php | 44 + plugins/password/drivers/sql.php | 107 + plugins/password/localization/de_CH.inc | 19 + plugins/password/localization/de_DE.inc | 19 + plugins/password/localization/en_US.inc | 18 + plugins/password/localization/et_EE.inc | 17 + plugins/password/localization/fr_FR.inc | 18 + plugins/password/localization/hu_HU.inc | 17 + plugins/password/localization/it_IT.inc | 18 + plugins/password/localization/nl_NL.inc | 17 + plugins/password/localization/pl_PL.inc | 18 + plugins/password/localization/pt_BR.inc | 18 + plugins/password/localization/pt_PT.inc | 18 + plugins/password/localization/sl_SI.inc | 18 + plugins/password/localization/sv_SE.inc | 18 + plugins/password/password.js | 36 + plugins/password/password.php | 212 + .../show_additional_headers.php | 52 + .../squirrelmail_usercopy/config.inc.php.dist | 5 + .../squirrelmail_usercopy.php | 94 + .../localization/en_US.inc | 6 + .../localization/et_EE.inc | 6 + .../localization/pl_PL.inc | 6 + .../localization/sv_SE.inc | 6 + .../subscriptions_option.php | 92 + plugins/userinfo/localization/de_CH.inc | 9 + plugins/userinfo/localization/en_US.inc | 9 + plugins/userinfo/localization/et_EE.inc | 9 + plugins/userinfo/localization/pl_PL.inc | 9 + plugins/userinfo/localization/pt_PT.inc | 9 + plugins/userinfo/localization/sv_SE.inc | 9 + plugins/userinfo/userinfo.js | 16 + plugins/userinfo/userinfo.php | 53 + .../vcard_attachments/vcard_add_contact.png | Bin 0 -> 1361 bytes .../vcard_attachments/vcard_attachments.php | 115 + plugins/vcard_attachments/vcardattach.js | 10 + program/include/bugs.inc | 27 +- program/include/html.php | 32 +- program/include/iniset.php | 44 +- program/include/main.inc | 428 +- program/include/rcmail.php | 247 +- program/include/rcube_addressbook.php | 169 + program/include/rcube_browser.php | 10 +- program/include/rcube_config.php | 86 +- program/include/rcube_contacts.php | 47 +- program/include/rcube_html_page.php | 29 +- program/include/rcube_imap.php | 958 +-- program/include/rcube_json_output.php | 67 +- program/include/rcube_ldap.php | 131 +- program/include/rcube_mail_mime.php | 2 +- program/include/rcube_mdb2.php | 112 +- program/include/rcube_message.php | 38 +- program/include/rcube_plugin.php | 245 + program/include/rcube_plugin_api.php | 329 + program/include/rcube_shared.inc | 174 +- program/include/rcube_smtp.inc | 349 - program/include/rcube_smtp.php | 394 + program/include/rcube_string_replacer.php | 27 +- program/include/rcube_template.php | 293 +- program/include/rcube_user.php | 156 +- program/include/rcube_vcard.php | 17 +- program/include/session.inc | 150 +- program/js/app.js | 7591 +++++++++-------- program/js/app.js.src | 4226 --------- program/js/common.js | 1093 ++- program/js/common.js.src | 680 -- program/js/editor.js | 27 +- program/js/googiespell.js | 1937 ++--- program/js/googiespell.js.src | 1308 --- program/js/jquery-1.3.min.js | 19 + program/js/list.js | 1441 ++-- program/js/list.js.src | 895 -- .../plugins/compat2x/editor_plugin.js | 2 +- .../plugins/contextmenu/editor_plugin.js | 2 +- .../plugins/directionality/editor_plugin.js | 2 +- .../plugins/emotions/editor_plugin.js | 2 +- .../js/tiny_mce/plugins/emotions/emotions.htm | 3 +- .../tiny_mce/plugins/media/editor_plugin.js | 2 +- .../plugins/media/editor_plugin_src.js | 130 +- program/js/tiny_mce/plugins/media/js/media.js | 14 +- program/js/tiny_mce/plugins/media/media.htm | 6 +- .../plugins/nonbreaking/editor_plugin.js | 2 +- .../tiny_mce/plugins/paste/editor_plugin.js | 2 +- .../plugins/paste/editor_plugin_src.js | 638 +- .../js/tiny_mce/plugins/paste/js/pastetext.js | 62 +- .../js/tiny_mce/plugins/paste/js/pasteword.js | 91 +- .../js/tiny_mce/plugins/paste/pastetext.htm | 35 +- .../js/tiny_mce/plugins/paste/pasteword.htm | 8 +- .../plugins/searchreplace/editor_plugin.js | 2 +- .../plugins/searchreplace/js/searchreplace.js | 19 +- .../plugins/searchreplace/searchreplace.htm | 1 - program/js/tiny_mce/plugins/table/cell.htm | 1 - .../tiny_mce/plugins/table/editor_plugin.js | 2 +- .../plugins/table/editor_plugin_src.js | 27 +- program/js/tiny_mce/plugins/table/js/cell.js | 20 +- .../tiny_mce/plugins/table/js/merge_cells.js | 2 +- program/js/tiny_mce/plugins/table/js/table.js | 48 +- .../js/tiny_mce/plugins/table/merge_cells.htm | 3 +- program/js/tiny_mce/plugins/table/row.htm | 3 +- program/js/tiny_mce/plugins/table/table.htm | 3 +- .../plugins/visualchars/editor_plugin.js | 2 +- .../js/tiny_mce/plugins/xhtmlxtras/abbr.htm | 1 - .../tiny_mce/plugins/xhtmlxtras/acronym.htm | 1 - .../plugins/xhtmlxtras/attributes.htm | 5 +- .../js/tiny_mce/plugins/xhtmlxtras/cite.htm | 1 - .../js/tiny_mce/plugins/xhtmlxtras/del.htm | 3 +- .../plugins/xhtmlxtras/editor_plugin.js | 2 +- .../plugins/xhtmlxtras/editor_plugin_src.js | 18 +- .../js/tiny_mce/plugins/xhtmlxtras/ins.htm | 3 +- .../js/tiny_mce/plugins/xhtmlxtras/js/del.js | 12 +- .../plugins/xhtmlxtras/js/element_common.js | 30 +- .../js/tiny_mce/plugins/xhtmlxtras/js/ins.js | 12 +- program/js/tiny_mce/themes/advanced/about.htm | 2 +- .../js/tiny_mce/themes/advanced/anchor.htm | 3 +- .../js/tiny_mce/themes/advanced/charmap.htm | 3 +- .../tiny_mce/themes/advanced/color_picker.htm | 3 +- .../themes/advanced/editor_template.js | 2 +- .../themes/advanced/editor_template_src.js | 169 +- program/js/tiny_mce/themes/advanced/image.htm | 15 +- .../js/tiny_mce/themes/advanced/js/about.js | 2 +- .../js/tiny_mce/themes/advanced/js/link.js | 3 +- .../themes/advanced/js/source_editor.js | 4 +- program/js/tiny_mce/themes/advanced/link.htm | 5 +- .../themes/advanced/skins/default/dialog.css | 4 +- .../themes/advanced/skins/default/ui.css | 13 +- .../themes/advanced/skins/o2k7/dialog.css | 4 +- .../themes/advanced/skins/o2k7/ui.css | 12 +- .../themes/advanced/skins/o2k7/ui_black.css | 8 +- .../themes/advanced/skins/o2k7/ui_silver.css | 2 +- .../themes/advanced/source_editor.htm | 1 - .../tiny_mce/themes/simple/editor_template.js | 2 +- .../themes/simple/editor_template_src.js | 4 +- .../themes/simple/skins/default/ui.css | 2 +- .../tiny_mce/themes/simple/skins/o2k7/ui.css | 2 +- program/js/tiny_mce/tiny_mce.js | 2 +- program/js/tiny_mce/tiny_mce_popup.js | 280 +- program/js/tiny_mce/tiny_mce_src.js | 5115 +++++++---- program/js/tiny_mce/utils/form_utils.js | 6 +- program/lib/enriched.inc | 2 +- program/lib/icl_commons.inc | 81 - program/lib/imap.inc | 1016 +-- program/lib/mime.inc | 3 +- program/lib/tnef_decoder.inc | 8 +- program/lib/washtml.php | 32 +- program/localization/ar_SA/labels.inc | 3 +- program/localization/ast/labels.inc | 5 +- program/localization/az_AZ/labels.inc | 3 +- program/localization/bg_BG/labels.inc | 23 +- program/localization/bn_BD/labels.inc | 1 - program/localization/br/labels.inc | 182 + program/localization/br/messages.inc | 79 + program/localization/bs_BA/labels.inc | 3 +- program/localization/ca_ES/labels.inc | 20 +- program/localization/ca_ES/messages.inc | 14 +- program/localization/cs_CZ/labels.inc | 13 +- program/localization/cs_CZ/messages.inc | 14 +- program/localization/cy_GB/labels.inc | 14 +- program/localization/cy_GB/messages.inc | 10 + program/localization/da_DK/labels.inc | 4 +- program/localization/da_DK/messages.inc | 3 +- program/localization/de_CH/labels.inc | 20 +- program/localization/de_CH/messages.inc | 11 +- program/localization/de_DE/labels.inc | 20 +- program/localization/de_DE/messages.inc | 12 +- program/localization/el_GR/labels.inc | 3 +- program/localization/en_GB/labels.inc | 8 +- program/localization/en_GB/messages.inc | 11 +- program/localization/en_US/labels.inc | 22 +- program/localization/en_US/messages.inc | 12 +- program/localization/eo/labels.inc | 1 - program/localization/es_AR/labels.inc | 22 +- program/localization/es_AR/messages.inc | 157 +- program/localization/es_ES/labels.inc | 144 +- program/localization/es_ES/messages.inc | 33 +- program/localization/et_EE/labels.inc | 12 +- program/localization/et_EE/messages.inc | 10 + program/localization/eu_ES/labels.inc | 3 +- program/localization/fa/labels.inc | 3 +- program/localization/fa_AF/labels.inc | 306 + program/localization/fa_AF/messages.inc | 80 + program/localization/fi_FI/labels.inc | 17 +- program/localization/fi_FI/messages.inc | 6 +- program/localization/fr_FR/labels.inc | 12 +- program/localization/fr_FR/messages.inc | 12 +- program/localization/ga_IE/labels.inc | 1 - program/localization/gl_ES/labels.inc | 16 +- program/localization/gl_ES/messages.inc | 10 + program/localization/he_IL/labels.inc | 16 +- program/localization/he_IL/messages.inc | 12 +- program/localization/hi_IN/labels.inc | 1 - program/localization/hr_HR/labels.inc | 3 +- program/localization/hu_HU/labels.inc | 104 +- program/localization/hu_HU/messages.inc | 71 +- program/localization/hy_AM/labels.inc | 3 +- program/localization/hy_AM/messages.inc | 2 +- program/localization/id_ID/labels.inc | 14 +- program/localization/id_ID/messages.inc | 10 + program/localization/index.inc | 5 +- program/localization/is_IS/labels.inc | 3 +- program/localization/it_IT/labels.inc | 15 +- program/localization/it_IT/messages.inc | 12 +- program/localization/ja_JP/labels.inc | 29 +- program/localization/ja_JP/messages.inc | 45 +- program/localization/ka_GE/labels.inc | 1 - program/localization/ko_KR/labels.inc | 1 - program/localization/ku/labels.inc | 1 - program/localization/lt_LT/labels.inc | 20 +- program/localization/lt_LT/messages.inc | 12 +- program/localization/lv_LV/labels.inc | 5 +- program/localization/lv_LV/messages.inc | 2 +- program/localization/mk_MK/labels.inc | 1 - program/localization/mr_IN/labels.inc | 263 + program/localization/mr_IN/messages.inc | 95 + program/localization/ms_MY/labels.inc | 1 - program/localization/nb_NO/labels.inc | 16 +- program/localization/nb_NO/messages.inc | 12 +- program/localization/ne_NP/labels.inc | 355 +- program/localization/ne_NP/messages.inc | 150 +- program/localization/nl_BE/labels.inc | 3 +- program/localization/nl_NL/labels.inc | 17 +- program/localization/nl_NL/messages.inc | 15 +- program/localization/nn_NO/labels.inc | 3 +- program/localization/pl_PL/labels.inc | 28 +- program/localization/pl_PL/messages.inc | 10 + program/localization/ps/labels.inc | 306 + program/localization/ps/messages.inc | 80 + program/localization/pt_BR/labels.inc | 18 +- program/localization/pt_BR/messages.inc | 12 +- program/localization/pt_PT/labels.inc | 5 +- program/localization/ro_RO/labels.inc | 6 +- program/localization/ro_RO/messages.inc | 3 +- program/localization/ru_RU/labels.inc | 18 +- program/localization/ru_RU/messages.inc | 16 +- program/localization/si_LK/labels.inc | 379 +- program/localization/si_LK/messages.inc | 162 +- program/localization/sk_SK/labels.inc | 3 +- program/localization/sl_SI/labels.inc | 126 +- program/localization/sl_SI/messages.inc | 156 +- program/localization/sq_AL/labels.inc | 1 - program/localization/sr_CS/labels.inc | 383 +- program/localization/sr_CS/messages.inc | 164 +- program/localization/sv_SE/labels.inc | 20 +- program/localization/sv_SE/messages.inc | 42 +- program/localization/th_TH/labels.inc | 1 - program/localization/tr_TR/labels.inc | 53 +- program/localization/tr_TR/messages.inc | 18 +- program/localization/uk_UA/labels.inc | 1 - program/localization/vi_VN/labels.inc | 426 +- program/localization/vi_VN/messages.inc | 124 +- program/localization/zh_CN/labels.inc | 5 +- program/localization/zh_TW/labels.inc | 3 +- program/localization/zh_TW/messages.inc | 2 +- program/steps/addressbook/copy.inc | 15 +- program/steps/addressbook/delete.inc | 11 +- program/steps/addressbook/edit.inc | 35 +- program/steps/addressbook/export.inc | 2 +- program/steps/addressbook/func.inc | 65 +- program/steps/addressbook/import.inc | 24 +- program/steps/addressbook/save.inc | 43 +- program/steps/addressbook/search.inc | 2 +- program/steps/error.inc | 13 +- program/steps/mail/addcontact.inc | 18 +- program/steps/mail/attachments.inc | 83 +- program/steps/mail/autocomplete.inc | 6 +- program/steps/mail/check_recent.inc | 7 +- program/steps/mail/compose.inc | 267 +- program/steps/mail/folders.inc | 8 +- program/steps/mail/func.inc | 341 +- program/steps/mail/get.inc | 15 +- program/steps/mail/list.inc | 7 +- program/steps/mail/mark.inc | 85 +- program/steps/mail/move_del.inc | 83 +- program/steps/mail/rss.inc | 4 +- program/steps/mail/search.inc | 49 +- program/steps/mail/sendmail.inc | 168 +- program/steps/mail/sendmdn.inc | 8 +- program/steps/mail/show.inc | 88 +- program/steps/mail/spell_pspell.inc | 10 +- program/steps/mail/viewsource.inc | 19 +- program/steps/settings/delete_identity.inc | 23 +- program/steps/settings/edit_identity.inc | 4 +- program/steps/settings/edit_prefs.inc | 521 ++ program/steps/settings/func.inc | 466 +- program/steps/settings/manage_folders.inc | 44 +- program/steps/settings/save_identity.inc | 23 +- program/steps/settings/save_prefs.inc | 179 +- skins/default/addresses.css | 85 +- skins/default/common.css | 161 +- skins/default/editor_content.css | 9 + skins/default/functions.js | 97 +- skins/default/googiespell.css | 30 +- skins/default/ie6hacks.css | 55 + skins/default/iehacks.css | 253 + skins/default/images/abook_toolbar.gif | Bin 0 -> 6087 bytes skins/default/images/abook_toolbar.png | Bin 0 -> 13619 bytes skins/default/images/buttons/add_act.png | Bin 295 -> 179 bytes .../images/buttons/add_contact_act.png | Bin 1626 -> 0 bytes .../images/buttons/add_contact_pas.png | Bin 1599 -> 0 bytes .../images/buttons/add_contact_sel.png | Bin 1392 -> 0 bytes skins/default/images/buttons/add_pas.png | Bin 301 -> 197 bytes skins/default/images/buttons/add_sel.png | Bin 239 -> 148 bytes skins/default/images/buttons/addressbook.gif | Bin 788 -> 0 bytes skins/default/images/buttons/addressbook.png | Bin 1768 -> 0 bytes .../default/images/buttons/adr_import_act.png | Bin 1402 -> 0 bytes .../default/images/buttons/adr_import_pas.png | Bin 1387 -> 0 bytes .../default/images/buttons/adr_import_sel.png | Bin 1347 -> 0 bytes skins/default/images/buttons/attach_act.png | Bin 1776 -> 0 bytes skins/default/images/buttons/attach_pas.png | Bin 1750 -> 0 bytes skins/default/images/buttons/attach_sel.png | Bin 1797 -> 0 bytes skins/default/images/buttons/back_act.png | Bin 1321 -> 0 bytes skins/default/images/buttons/back_pas.png | Bin 1317 -> 0 bytes skins/default/images/buttons/back_sel.png | Bin 1169 -> 0 bytes skins/default/images/buttons/compose_act.png | Bin 1650 -> 0 bytes skins/default/images/buttons/compose_pas.png | Bin 1617 -> 0 bytes skins/default/images/buttons/compose_sel.png | Bin 1446 -> 0 bytes skins/default/images/buttons/contacts_act.png | Bin 1742 -> 0 bytes skins/default/images/buttons/contacts_pas.png | Bin 1721 -> 0 bytes skins/default/images/buttons/contacts_sel.png | Bin 1456 -> 0 bytes skins/default/images/buttons/delete_act.png | Bin 2107 -> 0 bytes skins/default/images/buttons/delete_pas.png | Bin 2070 -> 0 bytes skins/default/images/buttons/delete_sel.png | Bin 1882 -> 0 bytes skins/default/images/buttons/down_arrow.png | Bin 285 -> 0 bytes skins/default/images/buttons/download_act.png | Bin 2133 -> 0 bytes skins/default/images/buttons/download_pas.png | Bin 2107 -> 0 bytes skins/default/images/buttons/download_sel.png | Bin 1676 -> 0 bytes skins/default/images/buttons/drafts_sel.png | Bin 4465 -> 0 bytes .../images/buttons/edit_contact_act.png | Bin 2157 -> 0 bytes .../images/buttons/edit_contact_pas.png | Bin 2135 -> 0 bytes .../images/buttons/edit_contact_sel.png | Bin 1791 -> 0 bytes skins/default/images/buttons/first_act.png | Bin 248 -> 0 bytes skins/default/images/buttons/first_pas.png | Bin 262 -> 0 bytes skins/default/images/buttons/first_sel.png | Bin 249 -> 0 bytes skins/default/images/buttons/forward_act.png | Bin 1502 -> 0 bytes skins/default/images/buttons/forward_pas.png | Bin 1472 -> 0 bytes skins/default/images/buttons/forward_sel.png | Bin 1342 -> 0 bytes skins/default/images/buttons/inbox_act.png | Bin 1827 -> 0 bytes skins/default/images/buttons/inbox_pas.png | Bin 1804 -> 0 bytes skins/default/images/buttons/inbox_sel.png | Bin 1558 -> 0 bytes skins/default/images/buttons/last_act.png | Bin 255 -> 0 bytes skins/default/images/buttons/last_pas.png | Bin 252 -> 0 bytes skins/default/images/buttons/last_sel.png | Bin 256 -> 0 bytes skins/default/images/buttons/logout.gif | Bin 818 -> 0 bytes skins/default/images/buttons/logout.png | Bin 2036 -> 0 bytes skins/default/images/buttons/mail.gif | Bin 831 -> 0 bytes skins/default/images/buttons/mail.png | Bin 1827 -> 0 bytes skins/default/images/buttons/markread_act.png | Bin 1772 -> 0 bytes skins/default/images/buttons/next_act.png | Bin 267 -> 0 bytes skins/default/images/buttons/next_pas.png | Bin 260 -> 0 bytes skins/default/images/buttons/next_sel.png | Bin 284 -> 0 bytes skins/default/images/buttons/previous_act.png | Bin 262 -> 0 bytes skins/default/images/buttons/previous_pas.png | Bin 277 -> 0 bytes skins/default/images/buttons/previous_sel.png | Bin 279 -> 0 bytes skins/default/images/buttons/print_act.png | Bin 1468 -> 0 bytes skins/default/images/buttons/print_pas.png | Bin 1464 -> 0 bytes skins/default/images/buttons/print_sel.png | Bin 1247 -> 0 bytes skins/default/images/buttons/reply_act.png | Bin 1775 -> 0 bytes skins/default/images/buttons/reply_pas.png | Bin 1754 -> 0 bytes skins/default/images/buttons/reply_sel.png | Bin 1576 -> 0 bytes skins/default/images/buttons/replyall_act.png | Bin 1989 -> 0 bytes skins/default/images/buttons/replyall_pas.png | Bin 1987 -> 0 bytes skins/default/images/buttons/replyall_sel.png | Bin 1740 -> 0 bytes skins/default/images/buttons/send_act.png | Bin 1820 -> 0 bytes skins/default/images/buttons/send_pas.png | Bin 1836 -> 0 bytes skins/default/images/buttons/send_sel.png | Bin 1553 -> 0 bytes skins/default/images/buttons/settings.gif | Bin 823 -> 0 bytes skins/default/images/buttons/settings.png | Bin 1054 -> 0 bytes skins/default/images/buttons/source_act.png | Bin 1657 -> 0 bytes skins/default/images/buttons/source_pas.png | Bin 1632 -> 0 bytes skins/default/images/buttons/source_sel.png | Bin 1476 -> 0 bytes skins/default/images/buttons/spacer.gif | Bin 43 -> 0 bytes .../default/images/buttons/spellcheck_act.png | Bin 1457 -> 0 bytes .../default/images/buttons/spellcheck_pas.png | Bin 1463 -> 0 bytes .../default/images/buttons/spellcheck_sel.png | Bin 1407 -> 0 bytes skins/default/images/buttons/up_arrow.png | Bin 279 -> 0 bytes skins/default/images/display/confirm.png | Bin 2135 -> 0 bytes skins/default/images/display/icons.gif | Bin 0 -> 2329 bytes skins/default/images/display/icons.png | Bin 0 -> 4300 bytes skins/default/images/display/info.png | Bin 2162 -> 0 bytes skins/default/images/display/warning.png | Bin 1422 -> 0 bytes skins/default/images/favicon.ico | Bin 1406 -> 1150 bytes skins/default/images/icons/delete.png | Bin 0 -> 841 bytes skins/default/images/icons/folder-closed.png | Bin 662 -> 0 bytes skins/default/images/icons/folder-drafts.png | Bin 511 -> 0 bytes skins/default/images/icons/folder-inbox.png | Bin 586 -> 0 bytes skins/default/images/icons/folder-junk.png | Bin 800 -> 0 bytes skins/default/images/icons/folder-open.png | Bin 796 -> 0 bytes skins/default/images/icons/folder-sent.png | Bin 629 -> 0 bytes skins/default/images/icons/folder-trash.png | Bin 775 -> 0 bytes skins/default/images/icons/folders.gif | Bin 0 -> 1102 bytes skins/default/images/icons/folders.png | Bin 0 -> 3283 bytes skins/default/images/icons/glass.gif | Bin 0 -> 909 bytes skins/default/images/icons/glass.png | Bin 0 -> 535 bytes skins/default/images/icons/glass_roll.png | Bin 0 -> 577 bytes .../images/icons/remove-attachment.png | Bin 787 -> 0 bytes skins/default/images/icons/silhouette.png | Bin 261 -> 255 bytes skins/default/images/icons/sort.gif | Bin 0 -> 148 bytes skins/default/images/listheader.gif | Bin 0 -> 538 bytes skins/default/images/listheader_aqua.gif | Bin 270 -> 0 bytes skins/default/images/listheader_dark.gif | Bin 280 -> 0 bytes skins/default/images/listheader_light.gif | Bin 261 -> 0 bytes skins/default/images/mail_toolbar.gif | Bin 0 -> 11126 bytes skins/default/images/mail_toolbar.png | Bin 0 -> 34966 bytes skins/default/images/messageactions.gif | Bin 0 -> 1112 bytes skins/default/images/messageactions.png | Bin 0 -> 1637 bytes skins/default/images/pagenav.gif | Bin 0 -> 426 bytes skins/default/images/roundcube_logo.gif | Bin 2284 -> 0 bytes skins/default/images/roundcube_logo.png | Bin 4868 -> 6794 bytes skins/default/images/roundcube_logo_print.gif | Bin 2400 -> 0 bytes skins/default/images/searchfield.gif | Bin 484 -> 397 bytes skins/default/images/sort_asc.gif | Bin 121 -> 0 bytes skins/default/images/sort_desc.gif | Bin 123 -> 0 bytes skins/default/images/tab_act.gif | Bin 519 -> 0 bytes skins/default/images/tab_pas.gif | Bin 511 -> 0 bytes skins/default/images/tabs.gif | Bin 0 -> 821 bytes skins/default/images/taskicons.gif | Bin 0 -> 2207 bytes skins/default/images/taskicons.png | Bin 0 -> 3813 bytes skins/default/includes/links.html | 10 +- skins/default/includes/messagemenu.html | 10 + skins/default/includes/settingstabs.html | 2 + skins/default/includes/taskbar.html | 1 + skins/default/mail.css | 490 +- skins/default/print.css | 6 +- skins/default/safari.css | 16 + skins/default/settings.css | 162 +- skins/default/splitter.js | 54 +- skins/default/templates/addcontact.html | 1 - skins/default/templates/addressbook.html | 53 +- skins/default/templates/compose.html | 31 +- skins/default/templates/editcontact.html | 1 - skins/default/templates/editidentity.html | 3 +- skins/default/templates/error.html | 2 +- skins/default/templates/identities.html | 5 +- skins/default/templates/importcontacts.html | 3 +- skins/default/templates/login.html | 24 +- skins/default/templates/mail.html | 139 +- skins/default/templates/managefolders.html | 5 +- skins/default/templates/message.html | 49 +- skins/default/templates/messageerror.html | 14 + skins/default/templates/messagepart.html | 1 - skins/default/templates/messagepreview.html | 5 +- skins/default/templates/plugin.html | 23 + skins/default/templates/printmessage.html | 2 +- skins/default/templates/settings.html | 42 +- skins/default/templates/settingsedit.html | 29 + skins/default/templates/showcontact.html | 1 - skins/default/watermark.html | 8 +- 563 files changed, 31036 insertions(+), 20506 deletions(-) create mode 100644 INSTALL.orig create mode 100644 bin/decrypt.php create mode 100644 plugins/additional_message_headers/additional_message_headers.php create mode 100644 plugins/archive/archive.js create mode 100644 plugins/archive/archive.php rename skins/default/images/buttons/drafts_pas.png => plugins/archive/archive_act.png (60%) create mode 100644 plugins/archive/archive_pas.png rename skins/default/images/buttons/drafts_act.png => plugins/archive/foldericon.png (58%) create mode 100644 plugins/archive/localization/de_CH.inc create mode 100644 plugins/archive/localization/de_DE.inc create mode 100644 plugins/archive/localization/en_US.inc create mode 100644 plugins/archive/localization/fr_FR.inc create mode 100644 plugins/archive/localization/pl_PL.inc create mode 100644 plugins/autologon/autologon.php create mode 100644 plugins/database_attachments/database_attachments.php create mode 100644 plugins/debug_logger/debug_logger.php create mode 100644 plugins/debug_logger/runlog/runlog.php create mode 100644 plugins/emoticons/emoticons.php create mode 100644 plugins/example_addressbook/example_addressbook.php create mode 100644 plugins/example_addressbook/example_addressbook_backend.php create mode 100644 plugins/filesystem_attachments/filesystem_attachments.php create mode 100644 plugins/help/config.inc.php.dist create mode 100644 plugins/help/content/about.html create mode 100644 plugins/help/content/license.html create mode 100644 plugins/help/help.php create mode 100644 plugins/help/localization/en_GB.inc create mode 100644 plugins/help/localization/en_US.inc create mode 100644 plugins/help/localization/et_EE.inc create mode 100644 plugins/help/localization/pl_PL.inc create mode 100644 plugins/help/localization/sv_SE.inc create mode 100644 plugins/help/skins/default/help.css create mode 100644 plugins/help/skins/default/help.gif create mode 100644 plugins/help/skins/default/templates/help.html create mode 100644 plugins/http_authentication/http_authentication.php create mode 100644 plugins/managesieve/Changelog create mode 100644 plugins/managesieve/config.inc.php.dist create mode 100644 plugins/managesieve/lib/Net/Sieve.php create mode 100644 plugins/managesieve/lib/rcube_sieve.php create mode 100644 plugins/managesieve/localization/bg_BG.inc create mode 100644 plugins/managesieve/localization/de_CH.inc create mode 100644 plugins/managesieve/localization/de_DE.inc create mode 100644 plugins/managesieve/localization/en_US.inc create mode 100644 plugins/managesieve/localization/es_ES.inc create mode 100644 plugins/managesieve/localization/et_EE.inc create mode 100644 plugins/managesieve/localization/fi_FI.inc create mode 100644 plugins/managesieve/localization/fr_FR.inc create mode 100644 plugins/managesieve/localization/gb_GB.inc create mode 100644 plugins/managesieve/localization/hu_HU.inc create mode 100644 plugins/managesieve/localization/it_IT.inc create mode 100644 plugins/managesieve/localization/nl_NL.inc create mode 100644 plugins/managesieve/localization/pl_PL.inc create mode 100644 plugins/managesieve/localization/pt_BR.inc create mode 100644 plugins/managesieve/localization/ru_RU.inc create mode 100644 plugins/managesieve/localization/sl_SI.inc create mode 100644 plugins/managesieve/localization/sv_SE.inc create mode 100644 plugins/managesieve/localization/uk_UA.inc create mode 100644 plugins/managesieve/localization/zh_CN.inc create mode 100644 plugins/managesieve/managesieve.js create mode 100644 plugins/managesieve/managesieve.php create mode 100644 plugins/managesieve/skins/default/filter_add_act.png create mode 100644 plugins/managesieve/skins/default/filter_add_pas.png create mode 100644 plugins/managesieve/skins/default/filter_add_sel.png create mode 100644 plugins/managesieve/skins/default/filter_del_act.png create mode 100644 plugins/managesieve/skins/default/filter_del_pas.png create mode 100644 plugins/managesieve/skins/default/filter_del_sel.png create mode 100644 plugins/managesieve/skins/default/filter_down_act.png create mode 100644 plugins/managesieve/skins/default/filter_down_pas.png create mode 100644 plugins/managesieve/skins/default/filter_down_sel.png create mode 100644 plugins/managesieve/skins/default/filter_up_act.png create mode 100644 plugins/managesieve/skins/default/filter_up_pas.png create mode 100644 plugins/managesieve/skins/default/filter_up_sel.png create mode 100644 plugins/managesieve/skins/default/managesieve.css create mode 100644 plugins/managesieve/skins/default/templates/managesieve.html create mode 100644 plugins/managesieve/skins/default/templates/managesieveedit.html create mode 100644 plugins/markasjunk/junk_act.png create mode 100644 plugins/markasjunk/junk_pas.png create mode 100644 plugins/markasjunk/localization/en_US.inc create mode 100644 plugins/markasjunk/localization/et_EE.inc create mode 100644 plugins/markasjunk/localization/pl_PL.inc create mode 100644 plugins/markasjunk/localization/sv_SE.inc create mode 100644 plugins/markasjunk/markasjunk.js create mode 100644 plugins/markasjunk/markasjunk.php create mode 100644 plugins/new_user_dialog/localization/de_CH.inc create mode 100644 plugins/new_user_dialog/localization/de_DE.inc create mode 100644 plugins/new_user_dialog/localization/en_US.inc create mode 100644 plugins/new_user_dialog/localization/et_EE.inc create mode 100644 plugins/new_user_dialog/localization/pl_PL.inc create mode 100644 plugins/new_user_dialog/localization/sv_SE.inc create mode 100644 plugins/new_user_dialog/new_user_dialog.php create mode 100644 plugins/new_user_dialog/newuserdialog.css create mode 100644 plugins/new_user_identity/new_user_identity.php create mode 100644 plugins/password/README create mode 100644 plugins/password/config.inc.php.dist create mode 100644 plugins/password/drivers/chgsaslpasswd.c create mode 100644 plugins/password/drivers/directadmin.php create mode 100644 plugins/password/drivers/ldap.php create mode 100644 plugins/password/drivers/poppassd.php create mode 100644 plugins/password/drivers/sasl.php create mode 100644 plugins/password/drivers/sql.php create mode 100644 plugins/password/localization/de_CH.inc create mode 100644 plugins/password/localization/de_DE.inc create mode 100644 plugins/password/localization/en_US.inc create mode 100644 plugins/password/localization/et_EE.inc create mode 100644 plugins/password/localization/fr_FR.inc create mode 100644 plugins/password/localization/hu_HU.inc create mode 100644 plugins/password/localization/it_IT.inc create mode 100644 plugins/password/localization/nl_NL.inc create mode 100644 plugins/password/localization/pl_PL.inc create mode 100644 plugins/password/localization/pt_BR.inc create mode 100644 plugins/password/localization/pt_PT.inc create mode 100644 plugins/password/localization/sl_SI.inc create mode 100644 plugins/password/localization/sv_SE.inc create mode 100644 plugins/password/password.js create mode 100644 plugins/password/password.php create mode 100644 plugins/show_additional_headers/show_additional_headers.php create mode 100644 plugins/squirrelmail_usercopy/config.inc.php.dist create mode 100644 plugins/squirrelmail_usercopy/squirrelmail_usercopy.php create mode 100644 plugins/subscriptions_option/localization/en_US.inc create mode 100644 plugins/subscriptions_option/localization/et_EE.inc create mode 100644 plugins/subscriptions_option/localization/pl_PL.inc create mode 100644 plugins/subscriptions_option/localization/sv_SE.inc create mode 100644 plugins/subscriptions_option/subscriptions_option.php create mode 100644 plugins/userinfo/localization/de_CH.inc create mode 100644 plugins/userinfo/localization/en_US.inc create mode 100644 plugins/userinfo/localization/et_EE.inc create mode 100644 plugins/userinfo/localization/pl_PL.inc create mode 100644 plugins/userinfo/localization/pt_PT.inc create mode 100644 plugins/userinfo/localization/sv_SE.inc create mode 100644 plugins/userinfo/userinfo.js create mode 100644 plugins/userinfo/userinfo.php create mode 100644 plugins/vcard_attachments/vcard_add_contact.png create mode 100644 plugins/vcard_attachments/vcard_attachments.php create mode 100644 plugins/vcard_attachments/vcardattach.js create mode 100644 program/include/rcube_addressbook.php create mode 100644 program/include/rcube_plugin.php create mode 100644 program/include/rcube_plugin_api.php delete mode 100644 program/include/rcube_smtp.inc create mode 100644 program/include/rcube_smtp.php delete mode 100644 program/js/app.js.src delete mode 100644 program/js/common.js.src delete mode 100644 program/js/googiespell.js.src create mode 100644 program/js/jquery-1.3.min.js delete mode 100644 program/js/list.js.src delete mode 100644 program/lib/icl_commons.inc create mode 100644 program/localization/br/labels.inc create mode 100644 program/localization/br/messages.inc create mode 100644 program/localization/fa_AF/labels.inc create mode 100644 program/localization/fa_AF/messages.inc create mode 100755 program/localization/mr_IN/labels.inc create mode 100755 program/localization/mr_IN/messages.inc create mode 100755 program/localization/ps/labels.inc create mode 100755 program/localization/ps/messages.inc create mode 100644 program/steps/settings/edit_prefs.inc create mode 100644 skins/default/ie6hacks.css create mode 100644 skins/default/iehacks.css create mode 100644 skins/default/images/abook_toolbar.gif create mode 100644 skins/default/images/abook_toolbar.png delete mode 100644 skins/default/images/buttons/add_contact_act.png delete mode 100644 skins/default/images/buttons/add_contact_pas.png delete mode 100644 skins/default/images/buttons/add_contact_sel.png delete mode 100644 skins/default/images/buttons/addressbook.gif delete mode 100644 skins/default/images/buttons/addressbook.png delete mode 100644 skins/default/images/buttons/adr_import_act.png delete mode 100644 skins/default/images/buttons/adr_import_pas.png delete mode 100644 skins/default/images/buttons/adr_import_sel.png delete mode 100644 skins/default/images/buttons/attach_act.png delete mode 100644 skins/default/images/buttons/attach_pas.png delete mode 100644 skins/default/images/buttons/attach_sel.png delete mode 100644 skins/default/images/buttons/back_act.png delete mode 100644 skins/default/images/buttons/back_pas.png delete mode 100644 skins/default/images/buttons/back_sel.png delete mode 100644 skins/default/images/buttons/compose_act.png delete mode 100644 skins/default/images/buttons/compose_pas.png delete mode 100644 skins/default/images/buttons/compose_sel.png delete mode 100644 skins/default/images/buttons/contacts_act.png delete mode 100644 skins/default/images/buttons/contacts_pas.png delete mode 100644 skins/default/images/buttons/contacts_sel.png delete mode 100644 skins/default/images/buttons/delete_act.png delete mode 100644 skins/default/images/buttons/delete_pas.png delete mode 100644 skins/default/images/buttons/delete_sel.png delete mode 100644 skins/default/images/buttons/down_arrow.png delete mode 100644 skins/default/images/buttons/download_act.png delete mode 100644 skins/default/images/buttons/download_pas.png delete mode 100644 skins/default/images/buttons/download_sel.png delete mode 100644 skins/default/images/buttons/drafts_sel.png delete mode 100644 skins/default/images/buttons/edit_contact_act.png delete mode 100644 skins/default/images/buttons/edit_contact_pas.png delete mode 100644 skins/default/images/buttons/edit_contact_sel.png delete mode 100644 skins/default/images/buttons/first_act.png delete mode 100644 skins/default/images/buttons/first_pas.png delete mode 100644 skins/default/images/buttons/first_sel.png delete mode 100644 skins/default/images/buttons/forward_act.png delete mode 100644 skins/default/images/buttons/forward_pas.png delete mode 100644 skins/default/images/buttons/forward_sel.png delete mode 100644 skins/default/images/buttons/inbox_act.png delete mode 100644 skins/default/images/buttons/inbox_pas.png delete mode 100644 skins/default/images/buttons/inbox_sel.png delete mode 100644 skins/default/images/buttons/last_act.png delete mode 100644 skins/default/images/buttons/last_pas.png delete mode 100644 skins/default/images/buttons/last_sel.png delete mode 100644 skins/default/images/buttons/logout.gif delete mode 100644 skins/default/images/buttons/logout.png delete mode 100644 skins/default/images/buttons/mail.gif delete mode 100644 skins/default/images/buttons/mail.png delete mode 100644 skins/default/images/buttons/markread_act.png delete mode 100644 skins/default/images/buttons/next_act.png delete mode 100644 skins/default/images/buttons/next_pas.png delete mode 100644 skins/default/images/buttons/next_sel.png delete mode 100644 skins/default/images/buttons/previous_act.png delete mode 100644 skins/default/images/buttons/previous_pas.png delete mode 100644 skins/default/images/buttons/previous_sel.png delete mode 100644 skins/default/images/buttons/print_act.png delete mode 100644 skins/default/images/buttons/print_pas.png delete mode 100644 skins/default/images/buttons/print_sel.png delete mode 100644 skins/default/images/buttons/reply_act.png delete mode 100644 skins/default/images/buttons/reply_pas.png delete mode 100644 skins/default/images/buttons/reply_sel.png delete mode 100644 skins/default/images/buttons/replyall_act.png delete mode 100644 skins/default/images/buttons/replyall_pas.png delete mode 100644 skins/default/images/buttons/replyall_sel.png delete mode 100644 skins/default/images/buttons/send_act.png delete mode 100644 skins/default/images/buttons/send_pas.png delete mode 100644 skins/default/images/buttons/send_sel.png delete mode 100644 skins/default/images/buttons/settings.gif delete mode 100644 skins/default/images/buttons/settings.png delete mode 100644 skins/default/images/buttons/source_act.png delete mode 100644 skins/default/images/buttons/source_pas.png delete mode 100644 skins/default/images/buttons/source_sel.png delete mode 100644 skins/default/images/buttons/spacer.gif delete mode 100644 skins/default/images/buttons/spellcheck_act.png delete mode 100644 skins/default/images/buttons/spellcheck_pas.png delete mode 100644 skins/default/images/buttons/spellcheck_sel.png delete mode 100644 skins/default/images/buttons/up_arrow.png delete mode 100644 skins/default/images/display/confirm.png create mode 100644 skins/default/images/display/icons.gif create mode 100644 skins/default/images/display/icons.png delete mode 100644 skins/default/images/display/info.png delete mode 100644 skins/default/images/display/warning.png create mode 100644 skins/default/images/icons/delete.png delete mode 100644 skins/default/images/icons/folder-closed.png delete mode 100644 skins/default/images/icons/folder-drafts.png delete mode 100644 skins/default/images/icons/folder-inbox.png delete mode 100644 skins/default/images/icons/folder-junk.png delete mode 100644 skins/default/images/icons/folder-open.png delete mode 100644 skins/default/images/icons/folder-sent.png delete mode 100644 skins/default/images/icons/folder-trash.png create mode 100644 skins/default/images/icons/folders.gif create mode 100644 skins/default/images/icons/folders.png create mode 100644 skins/default/images/icons/glass.gif create mode 100644 skins/default/images/icons/glass.png create mode 100644 skins/default/images/icons/glass_roll.png delete mode 100644 skins/default/images/icons/remove-attachment.png create mode 100644 skins/default/images/icons/sort.gif create mode 100644 skins/default/images/listheader.gif delete mode 100644 skins/default/images/listheader_aqua.gif delete mode 100644 skins/default/images/listheader_dark.gif delete mode 100644 skins/default/images/listheader_light.gif create mode 100644 skins/default/images/mail_toolbar.gif create mode 100644 skins/default/images/mail_toolbar.png create mode 100644 skins/default/images/messageactions.gif create mode 100644 skins/default/images/messageactions.png create mode 100644 skins/default/images/pagenav.gif delete mode 100644 skins/default/images/roundcube_logo.gif delete mode 100644 skins/default/images/roundcube_logo_print.gif delete mode 100644 skins/default/images/sort_asc.gif delete mode 100644 skins/default/images/sort_desc.gif delete mode 100644 skins/default/images/tab_act.gif delete mode 100644 skins/default/images/tab_pas.gif create mode 100644 skins/default/images/tabs.gif create mode 100644 skins/default/images/taskicons.gif create mode 100644 skins/default/images/taskicons.png create mode 100644 skins/default/includes/messagemenu.html create mode 100644 skins/default/safari.css create mode 100644 skins/default/templates/messageerror.html create mode 100644 skins/default/templates/plugin.html create mode 100644 skins/default/templates/settingsedit.html diff --git a/.htaccess b/.htaccess index 36d5748..19064fd 100644 --- a/.htaccess +++ b/.htaccess @@ -4,15 +4,16 @@ AddType text/x-component .htc php_flag display_errors Off php_flag log_errors On -php_value error_log logs/errors +# php_value error_log logs/errors php_value upload_max_filesize 5M -php_value post_max_size 6M -php_value memory_limit 64M +php_value post_max_size 6M +php_value memory_limit 64M -php_value zlib.output_compression 0 -php_value magic_quotes_gpc 0 +php_value zlib.output_compression Off +php_value magic_quotes_gpc 0 php_value zend.ze1_compatibility_mode 0 +php_value suhosin.session.encrypt Off php_value session.auto_start 0 php_value session.gc_maxlifetime 21600 @@ -27,6 +28,21 @@ php_value mbstring.func_overload 0 RewriteEngine On RewriteRule ^favicon.ico$ skins/default/images/favicon.ico - -Order deny,allow -Allow from all + + +SetOutputFilter DEFLATE + + + +# replace 'append' with 'merge' for Apache version 2.2.9 and later +Header append Cache-Control public env=!NO_CACHE + + + +ExpiresActive On +ExpiresDefault "access plus 1 month" + + +FileETag MTime Size + + diff --git a/CHANGELOG b/CHANGELOG index ddf2027..b5d57b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,71 +1,36 @@ -CHANGELOG RoundCube Webmail +CHANGELOG Roundcube Webmail (release 0.3-stable) =========================== -- Fix quicksearchbox look in Chrome and Konqueror (#1484841) -- Fix UTF-8 byte-order mark removing (#1485514) -- Fix folders subscribtions on Konqueror (#1484841) -- Fix debug console on Konqueror and Safari -- Fix messagelist focus issue when modifying status of selected messages (#1485807) -- Support STARTTLS in IMAP connection (#1485284) -- Fix DEL key problem in search boxes (#1485528) -- Support several e-mail addresses per user from virtuser_file (#1485678) -- Fix drag&drop with scrolling on IE (#1485786) -- Fix adding signature separator in html mode (#1485350) -- Fix opening attachment marks message as read (#1485803) -- Fix 'temp_dir' does not support relative path under Windows (#1484529) -- Fix "Initialize Database" button missing from installer (#1485802) -- Fix compose window doesn't fit 1024x768 window (#1485396) -- Fix service not available error when pressing back from compose dialog (#1485552) -- Fix using mail() on Windows (#1485779) -- Fix word wrapping in message-part's
s for printing (#1485787)
-- Fix incorrect word wrapping in outgoing plaintext multibyte messages (#1485714)
-- Fix double footer in HTML message with embedded images
-- Fix TNEF implementation bug (#1485773)
-- Fix incorrect row id parsing for LDAP contacts list (#1485784) 
-- Fix 'mode' parameter in sqlite DSN (#1485772)
+- Fix gn and givenName should be synonymous in LDAP addressbook (#1485892)
+- Add mail_domain to LDAP email entries without @ sign (#1485201)
+- Fix saving empty values in LDAP contact data (#1485781)
+- Fix LDAP contact update when RDN field is changed (#1485788)
+- Fix LDAP attributes case senitivity problems (#1485830)
+- Fix LDAP addressbook browsing when only one directory is used (#1486022)
+- Fix endless loop on error response for APPEND command (#1486060)
+- Don't require date.timezone setting in installer (#1485989)
+- Fix date sorting problem with Courier IMAP server (#1486065)
+- Unselect pressed buttons on mouse up (#1485987)
+- Don't set php_value error_log in .htaccess but mention in INSTALL (#1485924)
+- Fix too small status/flag/attachment columns in Safari 4 (#1486063)
+- Fix selection disabling while dragging splitter in webkit browsers (#1486056)
+- Added 'new_messages' plugin hook (#1486005)
+- Added 'logout_after' plugin hook (#1486042)
+- Added 'message_compose' hook
+- Added 'imap_connect' hook (#1485956)
+- Fix vcard_attachments plugin (#1486035)
+- Updated PEAR::Auth_SASL to 1.0.3 version
+- Use sequence names only with PostgreSQL (#1486018)
+- Re-designed User Preferences interface 
+- Fix MS SQL DDL (#1486020)
+- Fix rcube_mdb2.php: call to setCharset not implemented in mssql driver (#1486019)
+- Added 'display_next' option
+- Fix rcube_mdb2::unixtimestamp for MS SQL (#1486015)
+- Fix HTML washing to respect character encoding
+- Fix endless loop in iil_C_Login() with Courier IMAP (#1486010)
+- Fix #messagemenu display on IE (#1486006)
+- Speedup UI by using sprites for (toolbar) buttons
+- Fix charset names with X- prefix handling
+- Fix displaying of HTML messages with unknown/malformed tags (#1486003)
+
 
-RELEASE 0.2.1
-------------------
-- Use US-ASCII as failover when Unicode searching fails (#1485762)
-- Fix errors handling in IMAP command continuations (#1485762)
-- Fix FETCH result parsing for servers returning flags at the end of result (#1485763)
-- Fix datetime columns defaults in mysql's DDL (#1485641)
-- Fix attaching more than nine inline images (#1485759)
-- Support 'UNICODE-1-1-UTF-7' alias for UTF-7 encoding (#1485758)
-- Fix mime-type detection using a hard-coded map (#1485311)
-- Don't return empty string if charset conversion failed (#1485757)
-- Disable concurrent autocomplete query results display (#1485743)
-- Fix new lines stripped from message footer (#1485751)
-- Fix IE problem with mouse click autocomplete (#1485739)
-- Fix html body washing on reply/forward + fix attachments handling (#1485676)
-- Fix multiple recipients input parsing (#1485733)
-- Fix replying to message with html attachment (#1485676)
-- Use default_charset for messages without specified charset (#1485661, #1484961)
-- Support non-standard "GMT-XXXX" literal in date header (#1485729)
-- Added TNEF support to decode MS Outlook attachments (winmail.dat)
-- Fix "value continuation" MIME headers by adding required semicolon (#1485727)
-- Fix pressing select all/unread multiple times (#1485723)
-- Fix selecting all unread does not honor new messages (#1485724)
-- Fix some base64 encoded attachments handling (#1485725)
-- Support NGINX as IMAP backend: better BAD response handling (#1485720)
-- Performance fix: don't fetch attachment parts headers twice to parse filename
-- Fix checking for recent messages on various IMAP servers (#1485702)
-- Performance fix: Don't fetch quota and recent messages in "message view" mode
-- Fix displaying of alternative-inside-alternative messages (#1485713)
-- Fix MDNSent flag checking, use arbitrary keywords (asterisk) flag (#1485706)
-- Fix creation of folders with '&' sign in name
-- Fix parsing of email addresses without angle brackets (#1485693)
-- Save spellcheck corrections when switching from plain to html editor (and spellchecking is on)
-- Fix large search results on server without SORT capability (#1485668)
-- Get rid of preg_replace() with eval modifier and create_function usage (#1485686)
-- Bring back  and  tags in HTML messages
-- Fix XSS vulnerability through background attributes as reported by Julien Cayssol
-- Fix problems with backslash as IMAP hierarchy delimiter (#1484467)
-- Secure vcard export by getting rid of preg's 'e' modifier use (#1485689)
-- Fix authentication when submitting form with existing session (#1485679)
-- Allow absolute URLs to images in HTML messages/sigs (#1485666)
-- Fix message body which contains both inline attachments and emotions
-- Fix SQL query execution errors handling in rcube_mdb2 class (#1485509)
-- Fix address names with '@' sign handling (#1485654)
-- Improve messages display performance
-- Fix messages searching with 'to:' modifier
diff --git a/INSTALL b/INSTALL
index fe71cdf..ff905bd 100644
--- a/INSTALL
+++ b/INSTALL
@@ -44,11 +44,20 @@ INSTALLATION
    - /logs
 3. Create a new database and a database user for RoundCube (see DATABASE SETUP)
 4. Point your browser to http://url-to-roundcube/installer/
-5. Follow the instructions of the install script (or see MANUAL CONFINGURATION)
+5. Follow the instructions of the install script (or see MANUAL CONFIGURATION)
 6. After creating and testing the configuration, remove the installer directory
 7. Done!
 
 
+CONFIGURATION HINTS
+===================
+
+RoundCube writes internal errors to the 'errors' log file located in the logs
+directory which can be configured in config/main.inc.php. If you want ordinary
+PHP errors to be logged there as well, enable the 'php_value error_log' line
+in the .htaccess file and set the path to the log file accordingly.
+
+
 DATABASE SETUP
 ==============
 
@@ -76,12 +85,16 @@ RoundCube with utf-8 charset.
 
 * SQLite
 --------
-Sqlite requires specifically php5 (sqlite in php4 currently doesn't
-work with roundcube), and you need sqlite 2 (preferably 2.8) to setup
-the sqlite db (sqlite 3.x also doesn't work at the moment). Here is
+You need sqlite 2 (preferably 2.8) to setup the sqlite db 
+(sqlite 3.x also doesn't work at the moment). Here is
 an example how you can setup the sqlite.db for roundcube:
 
 # sqlite -init SQL/sqlite.initial.sql sqlite.db
+Loading resources from SQL/sqlite.initial.sql
+SQLite version 2.8.16
+Enter ".help" for instructions
+sqlite> .exit
+# chmod o+rw sqlite.db
 
 Make sure your configuration points to the sqlite.db file and that the
 webserver can write to the file and the directory containing the file.
@@ -126,3 +139,80 @@ If you already have a previous version of RoundCube installed,
 please refer to the instructions in UPGRADING guide.
 
 
+OPTIMISING
+==========
+
+There are two forms of optimisation here, compression and caching, both aimed
+at increasing an end user's experience using RoundCube Webmail. Compression
+allows the static web pages to be delivered with less bandwidth. The index.php
+of RoundCube Webmail already enables compression on its output. The settings
+below allow compression to occur for all static files. Caching sets HTTP 
+response headers that enable a user's web client to understand what is static
+and how to cache it.
+
+The caching directives used are:
+ * Etags - sets at tag so the client can request is the page has changed
+ * Cache-control - defines the age of the page and that the page is 'public'
+   This enables clients to cache javascript files that don't have private 
+   information between sessions even if using HTTPS. It also allows proxies
+   to share the same cached page between users.
+ * Expires - provides another hint to increase the lifetime of static pages.
+
+For more information refer to RFC 2616.
+
+Side effects:
+-------------
+These directives are designed for production use. If you are using this in
+a development environment you may get horribly confused if your webclient
+is caching stuff that you changed on the server. Disabling the expires 
+parts below should save you some grief.
+
+If you are changing the skins, it is recommended that you copy content to 
+a different directory apart from 'default'.
+
+Apache:
+-------
+To enable these features in apache the following modules need to be enabled:
+ * mod_deflate
+ * mod_expires
+ * mod_headers
+
+The optimisation is already included in the .htaccess file in the top 
+directory of your installation.
+
+If you are using Apache version 2.2.9 and later, in the .htaccess file
+change the 'append' word to 'merge' for a more correct response. Keeping
+as 'append' shouldn't cause any problems though changing to merge will 
+eliminate the possibility of duplicate 'public' headers in Cache-control.
+
+Lighttpd:
+---------
+With Lightty the addition of Expire: tags by mod_expire is incompatible with
+the addition of "Cache-control: public". Using Cache-control 'public' is 
+used below as it is assumed to give a better caching result.
+
+Enable modules in server.modules:
+    "mod_setenv"
+    "mod_compress"
+
+Mod_compress is a server side cache of compressed files to improve its performance.
+
+$HTTP["host"] == "www.example.com" {
+
+    static-file.etags = "enable"
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Etag.use-mtimeDetails
+    etag.use-mtime = "enable"
+
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModSetEnv
+    $HTTP["url"] =~ "^/roundcubemail/(plugins|skins|program)" {
+        setenv.add-response-header  = ( "Cache-Control" => "public, max-age=2592000")
+    }
+
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModCompress
+    # set compress.cache-dir to somewhere outside the docroot.
+    compress.cache-dir   = var.statedir + "/cache/compress"
+
+    compress.filetype = ("text/plain", "text/html", "text/javascript", "text/css", "text/xml", "image/gif", "image/png")
+}
+
+
diff --git a/INSTALL.orig b/INSTALL.orig
new file mode 100644
index 0000000..67fbce9
--- /dev/null
+++ b/INSTALL.orig
@@ -0,0 +1,212 @@
+INTRODUCTION
+============
+
+This file describes the basic steps to install RoundCube Webmail on your
+web server. For additional information, please also consult the project's
+wiki page at http://trac.roundcube.net/wiki
+
+
+REQUIREMENTS
+============
+
+* The Apache or Lighttpd Webserver
+* .htaccess support allowing overrides for DirectoryIndex
+* PHP Version 5.2 or greater including
+   - PCRE (perl compatible regular expression)
+   - DOM (xml document object model)
+   - libiconv (recommended)
+   - mbstring (optional)
+* php.ini options:
+   - error_reporting E_ALL & ~E_NOTICE (or lower)
+   - memory_limit (increase as suitable to support large attachments)
+   - file_uploads enabled (for attachment upload features)
+   - session.auto_start disabled
+   - zend.ze1_compatibility_mode disabled
+* PHP compiled with OpenSSL to connect to IMAPS and to use the spell checker
+* A MySQL or PostgreSQL database engine or the SQLite extension for PHP
+* One of the above databases with permission to create tables
+* An SMTP server or PHP configured for mail delivery
+
+
+INSTALLATION
+============
+
+1. Decompress and put this folder somewhere inside your document root
+2. Make sure that the following directories (and the files within)
+   are writable by the webserver
+   - /temp
+   - /logs
+3. Create a new database and a database user for RoundCube (see DATABASE SETUP)
+4. Point your browser to http://url-to-roundcube/installer/
+5. Follow the instructions of the install script (or see MANUAL CONFIGURATION)
+6. After creating and testing the configuration, remove the installer directory
+7. Done!
+
+
+CONFIGURATION HINTS
+===================
+
+RoundCube writes internal errors to the 'errors' log file located in the logs
+directory which can be configured in config/main.inc.php. If you want ordinary
+PHP errors to be logged there as well, enable the 'php_value error_log' line
+in the .htaccess file and set the path to the log file accordingly.
+
+
+DATABASE SETUP
+==============
+
+* MySQL
+-------
+Setting up the mysql database can be done by creating an empty database,
+importing the table layout and granting the proper permissions to the
+roundcube user. Here is an example of that procedure:
+
+# mysql
+> CREATE DATABASE roundcubemail /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+> GRANT ALL PRIVILEGES ON roundcubemail.* TO roundcube@localhost
+    IDENTIFIED BY 'password';
+> quit
+
+# mysql roundcubemail < SQL/mysql.initial.sql
+
+Note 1: 'password' is the master password for the roundcube user. It is strongly
+recommended you replace this with a more secure password. Please keep in
+mind: You need to specify this password later in 'config/db.inc.php'.
+
+Note 2: For MySQL version 4.1 and up, it's recommended to create the database for
+RoundCube with utf-8 charset.
+
+
+* SQLite
+--------
+You need sqlite 2 (preferably 2.8) to setup the sqlite db 
+(sqlite 3.x also doesn't work at the moment). Here is
+an example how you can setup the sqlite.db for roundcube:
+
+# sqlite -init SQL/sqlite.initial.sql sqlite.db
+Loading resources from SQL/sqlite.initial.sql
+SQLite version 2.8.16
+Enter ".help" for instructions
+sqlite> .exit
+# chmod o+rw sqlite.db
+
+Make sure your configuration points to the sqlite.db file and that the
+webserver can write to the file and the directory containing the file.
+
+
+* PostgreSQL
+------------
+To use RoundCube with PostgreSQL support you have to follow these
+simple steps, which have to be done as the postgres system user (or
+which ever is the database superuser):
+
+$ createuser roundcube
+$ createdb -O roundcube -E UNICODE roundcubemail
+$ psql roundcubemail
+
+roundcubemail =# ALTER USER roundcube WITH PASSWORD 'the_new_password';
+roundcubemail =# \c - roundcube
+roundcubemail => \i SQL/postgres.initial.sql
+
+All this has been tested with PostgreSQL 8.x and 7.4.x. Older
+versions don't have a -O option for the createdb, so if you are
+using that version you'll have to change ownership of the DB later.
+
+
+MANUAL CONFIGURATION
+====================
+
+First of all, rename the files config/*.inc.php.dist to config/*.inc.php.
+You can then change these files according to your environment and your needs.
+Details about the config parameters can be found in the config files.
+See http://trac.roundcube.net/wiki/Howto_Install for even more guidance.
+
+You can also modify the default .htaccess file. This is necessary to
+increase the allowed size of file attachments, for example:
+	php_value       upload_max_filesize     2M
+
+
+UPGRADING
+=========
+
+If you already have a previous version of RoundCube installed,
+please refer to the instructions in UPGRADING guide.
+
+
+OPTIMISING
+==========
+
+There are two forms of optimisation here, compression and caching, both aimed
+at increasing an end user's experience using RoundCube Webmail. Compression
+allows the static web pages to be delivered with less bandwidth. The index.php
+of RoundCube Webmail already enables compression on its output. The settings
+below allow compression to occur for all static files. Caching sets HTTP 
+response headers that enable a user's web client to understand what is static
+and how to cache it.
+
+The caching directives used are:
+ * Etags - sets at tag so the client can request is the page has changed
+ * Cache-control - defines the age of the page and that the page is 'public'
+   This enables clients to cache javascript files that don't have private 
+   information between sessions even if using HTTPS. It also allows proxies
+   to share the same cached page between users.
+ * Expires - provides another hint to increase the lifetime of static pages.
+
+For more information refer to RFC 2616.
+
+Side effects:
+-------------
+These directives are designed for production use. If you are using this in
+a development environment you may get horribly confused if your webclient
+is caching stuff that you changed on the server. Disabling the expires 
+parts below should save you some grief.
+
+If you are changing the skins, it is recommended that you copy content to 
+a different directory apart from 'default'.
+
+Apache:
+-------
+To enable these features in apache the following modules need to be enabled:
+ * mod_deflate
+ * mod_expires
+ * mod_headers
+
+The optimisation is already included in the .htaccess file in the top 
+directory of your installation.
+
+If you are using Apache version 2.2.9 and later, in the .htaccess file
+change the 'append' word to 'merge' for a more correct response. Keeping
+as 'append' shouldn't cause any problems though changing to merge will 
+eliminate the possibility of duplicate 'public' headers in Cache-control.
+
+Lighttpd:
+---------
+With Lightty the addition of Expire: tags by mod_expire is incompatible with
+the addition of "Cache-control: public". Using Cache-control 'public' is 
+used below as it is assumed to give a better caching result.
+
+Enable modules in server.modules:
+    "mod_setenv"
+    "mod_compress"
+
+Mod_compress is a server side cache of compressed files to improve its performance.
+
+$HTTP["host"] == "www.example.com" {
+
+    static-file.etags = "enable"
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Etag.use-mtimeDetails
+    etag.use-mtime = "enable"
+
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModSetEnv
+    $HTTP["url"] =~ "^/roundcubemail/(plugins|skins|program)" {
+        setenv.add-response-header  = ( "Cache-Control" => "public, max-age=2592000")
+    }
+
+    # http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModCompress
+    # set compress.cache-dir to somewhere outside the docroot.
+    compress.cache-dir   = var.statedir + "/cache/compress"
+
+    compress.filetype = ("text/plain", "text/html", "text/javascript", "text/css", "text/xml", "image/gif", "image/png")
+}
+
+
diff --git a/README b/README
index d7a13f7..ff975de 100644
--- a/README
+++ b/README
@@ -1,9 +1,9 @@
-RoundCube Webmail (http://roundcube.net)
+Roundcube Webmail (http://roundcube.net)
 
 
 Introduction:
 -------------
-RoundCube Webmail is a browser-based multilingual IMAP client with an
+Roundcube Webmail is a browser-based multilingual IMAP client with an
 application-like user interface. It provides full functionality you expect
 from an e-mail client, including MIME support, address book, folder management,
 message searching and spell checking. RoundCube Webmail is written in PHP and
@@ -35,7 +35,7 @@ LICENSE for more information about our license.
 
 Contribution:
 -------------
-Want to help make RoundCube the best webmail solution ever?
+Want to help make Roundcube the best webmail solution ever?
 RoundCube is open source software. Our developers and contributors all
 are volunteers and we're always looking for new additions and resources.
 For more information visit http://roundcube.net/contribute
diff --git a/SQL/mssql.initial.sql b/SQL/mssql.initial.sql
index 534e327..e97a9a7 100644
--- a/SQL/mssql.initial.sql
+++ b/SQL/mssql.initial.sql
@@ -30,7 +30,7 @@ CREATE TABLE [dbo].[identities] (
 	[email] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[reply-to] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[bcc] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
-	[signature] [text] COLLATE Latin1_General_CI_AI NOT NULL, 
+	[signature] [text] COLLATE Latin1_General_CI_AI NULL, 
 	[html_signature] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL
 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
 GO
@@ -69,9 +69,9 @@ CREATE TABLE [dbo].[users] (
 	[mail_host] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[alias] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
 	[created] [datetime] NOT NULL ,
-	[last_login] [datetime] NULL ,
+	[last_login] [datetime] NOT NULL ,
 	[language] [varchar] (5) COLLATE Latin1_General_CI_AI NULL ,
-	[preferences] [text] COLLATE Latin1_General_CI_AI NOT NULL 
+	[preferences] [text] COLLATE Latin1_General_CI_AI NULL 
 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
 GO
 
@@ -155,6 +155,7 @@ ALTER TABLE [dbo].[identities] ADD
 	CONSTRAINT [DF_identities_email] DEFAULT ('') FOR [email],
 	CONSTRAINT [DF_identities_reply] DEFAULT ('') FOR [reply-to],
 	CONSTRAINT [DF_identities_bcc] DEFAULT ('') FOR [bcc],
+	CONSTRAINT [DF_identities_html_signature] DEFAULT ('0') FOR [html_signature],
 	 CHECK ([standard] = '1' or [standard] = '0'),
 	 CHECK ([del] = '1' or [del] = '0')
 GO
@@ -202,7 +203,7 @@ ALTER TABLE [dbo].[users] ADD
 	CONSTRAINT [DF_users_username] DEFAULT ('') FOR [username],
 	CONSTRAINT [DF_users_mail_host] DEFAULT ('') FOR [mail_host],
 	CONSTRAINT [DF_users_alias] DEFAULT ('') FOR [alias],
-	CONSTRAINT [DF_users_created] DEFAULT (getdate()) FOR [created],
+	CONSTRAINT [DF_users_created] DEFAULT (getdate()) FOR [created]
 GO
 
  CREATE  INDEX [IX_users_username] ON [dbo].[users]([username]) ON [PRIMARY]
diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql
index 9815282..9464dd7 100644
--- a/SQL/mysql.initial.sql
+++ b/SQL/mysql.initial.sql
@@ -1,11 +1,8 @@
 -- RoundCube Webmail initial database structure
--- Version 0.2
 
--- --------------------------------------------------------
 
 /*!40014  SET FOREIGN_KEY_CHECKS=0 */;
 
-
 -- Table structure for table `session`
 
 CREATE TABLE `session` (
diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql
index eb53332..1a21ee2 100644
--- a/SQL/postgres.initial.sql
+++ b/SQL/postgres.initial.sql
@@ -1,3 +1,5 @@
+-- RoundCube Webmail initial database structure
+
 --
 -- Sequence "user_ids"
 -- Name: user_ids; Type: SEQUENCE; Schema: public; Owner: postgres
diff --git a/SQL/sqlite.initial.sql b/SQL/sqlite.initial.sql
index 5120ea0..ef7cb43 100644
--- a/SQL/sqlite.initial.sql
+++ b/SQL/sqlite.initial.sql
@@ -1,8 +1,4 @@
 -- RoundCube Webmail initial database structure
--- Version 0.1
--- 
-
--- --------------------------------------------------------
 
 -- 
 -- Table structure for table `cache`
diff --git a/UPGRADING b/UPGRADING
index b2c1977..6362e0c 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -14,6 +14,7 @@ of RoundCube Webmail.
    - ./program/
    - ./installer/
    - ./skins/default/
+   - ./plugins/
 2. Run ./bin/update.sh from the commandline OR
    open http://url-to-roundcube/installer/ in a browser. To enable
    the latter one, you have to temporary set 'enable_installer' to true
@@ -24,6 +25,7 @@ of RoundCube Webmail.
    ./SQL/[yourdbtype].update.sql that are superscribed with the
    currently installed version number.
 5. Make sure 'enable_installer' is set to false again.
+6. Check .htaccess settings (some php settings could become required)
 
 
 For manually upgrading your RoundCube installation follow the instructions
diff --git a/bin/decrypt.php b/bin/decrypt.php
new file mode 100644
index 0000000..7ef5a47
--- /dev/null
+++ b/bin/decrypt.php
@@ -0,0 +1,70 @@
+#!/usr/bin/env php
+                                 |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+*/
+
+/*-
+ * 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 sensitve, 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_received.php '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.
+ */
+
+if (php_sapi_name() != 'cli') {
+	die("Not on the 'shell' (php-cli).\n");
+}
+
+define('INSTALL_PATH', realpath(dirname(__FILE__).'/..') . '/');
+require INSTALL_PATH . 'program/include/iniset.php';
+
+if ($argc < 2) {
+	die("Usage: " . basename($argv[0]) . " encrypted-hdr-part [encrypted-hdr-part ...]\n");
+}
+
+$RCMAIL = rcmail::get_instance();
+
+for ($i = 1; $i < $argc; $i++) {
+	printf("%s\n", $RCMAIL->decrypt($argv[$i]));
+};
diff --git a/bin/modcss.php b/bin/modcss.php
index 093add6..e6fb3f6 100644
--- a/bin/modcss.php
+++ b/bin/modcss.php
@@ -15,7 +15,7 @@
  | Author: Thomas Bruederli                         |
  +-----------------------------------------------------------------------+
 
- $Id: modcss.php 2249 2009-01-22 11:23:00Z thomasb $
+ $Id: modcss.php 2853 2009-08-12 10:44:46Z thomasb $
 
 */
 
@@ -33,7 +33,7 @@ if (empty($RCMAIL->user->ID)) {
     exit;
 }
 
-$url = preg_replace('/[^a-z0-9.-_\?\$&=%]/i', '', $_GET['u']);
+$url = preg_replace('![^a-z0-9:./\-_?$&=%]!i', '', $_GET['u']);
 if ($url === null) {
     header('HTTP/1.1 403 Forbidden');
     echo $error;
@@ -45,42 +45,63 @@ $port  = $a_uri['port'] ? $a_uri['port'] : 80;
 $host  = $a_uri['host'];
 $path  = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '');
 
-if (!($fp = fsockopen($host, $port, $errno, $errstr, 30))) {
+// don't allow any other connections than http(s)
+if (strtolower(substr($a_uri['scheme'], 0, 4)) != 'http') {
+    header('HTTP/1.1 403 Forbidden');
+    echo "Invalid URL";
+    exit;
+}
+
+// try to open socket connection
+if (!($fp = fsockopen($host, $port, $errno, $error, 15))) {
     header('HTTP/1.1 500 Internal Server Error');
     echo $error;
     exit;
 }
 
+// set timeout for socket
+stream_set_timeout($fp, 30);
+
+// send request
 $out  = "GET $path HTTP/1.0\r\n";
 $out .= "Host: $host\r\n";
 $out .= "Connection: Close\r\n\r\n";
 fwrite($fp, $out);
 
+// read response
 $header = true;
+$headers = array();
 while (!feof($fp)) {
     $line = trim(fgets($fp, 4048));
 
-    if ($header
-        && preg_match('/^HTTP\/1\..\s+(\d+)/', $line, $regs)
-        && intval($regs[1]) != 200) {
-        break;
-    } else if (empty($line) && $header) {
-        $header = false;
-    } else if (!$header) {
+    if ($header) {
+        if (preg_match('/^HTTP\/1\..\s+(\d+)/', $line, $regs)
+            && intval($regs[1]) != 200) {
+            break;
+        }
+        else if (empty($line)) {
+            $header = false;
+        }
+        else {
+            list($key, $value) = explode(': ', $line);
+            $headers[strtolower($key)] = $value;
+        }
+    }
+    else {
         $source .= "$line\n";
     }
 }
 fclose($fp);
 
-if (!empty($source)) {
+// check content-type header and mod styles
+$mimetype = strtolower($headers['content-type']);
+if (!empty($source) && in_array($mimetype, array('text/css','text/plain'))) {
     header('Content-Type: text/css');
-    echo rcmail_mod_css_styles(
-        $source,
-        preg_replace('/[^a-z0-9]/i', '', $_GET['c']),
-        $url
-    );
+    echo rcmail_mod_css_styles($source, preg_replace('/[^a-z0-9]/i', '', $_GET['c']));
     exit;
 }
+else
+    $error = "Invalid response returned by server";
 
 header('HTTP/1.0 404 Not Found');
 echo $error;
diff --git a/bin/quotaimg.php b/bin/quotaimg.php
index ed6ce81..7111b89 100644
--- a/bin/quotaimg.php
+++ b/bin/quotaimg.php
@@ -14,7 +14,7 @@
  | Author: Brett Patterson                              |
  +-----------------------------------------------------------------------+
 
- $Id: quotaimg.php 2237 2009-01-17 01:55:39Z till $
+ $Id: quotaimg.php 2453 2009-05-04 08:31:55Z alec $
 
 */
 
@@ -102,7 +102,7 @@ function genQuota($used, $total, $width, $height)
 	 ***********************************/
 
 	// @todo: Set to "??" instead?
-	if (ereg("^[^0-9?]*$", $used) || ereg("^[^0-9?]*$", $total)) {
+	if (preg_match('/^[^0-9?]*$/', $used) || preg_match('/^[^0-9?]*$/', $total)) {
 		return false; 
 	}
 
@@ -172,7 +172,8 @@ function genQuota($used, $total, $width, $height)
 		}
 
 		$quota_width = $quota / 100 * $width;
-		imagefilledrectangle($im, $border, 0, $quota_width, $height-2*$border, $fill);
+		if ($quota_width)
+			imagefilledrectangle($im, $border, 0, $quota_width, $height-2*$border, $fill);
 
 		$string = $quota . '%';
 		$mid    = floor(($width-(strlen($string)*imagefontwidth($font)))/2)+1;
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 42af34d..42881df 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -14,13 +14,16 @@
 
 $rcmail_config = array();
 
-
 // system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
 $rcmail_config['debug_level'] = 1;
 
 // log driver:  'syslog' or 'file'.
 $rcmail_config['log_driver'] = 'file';
 
+// date format for log entries
+// (read http://php.net/manual/en/function.date.php for all format characters)  
+$rcmail_config['log_date_format'] = 'd-M-Y H:i:s O';
+
 // Syslog ident string to use, if using the 'syslog' log driver.
 $rcmail_config['syslog_id'] = 'roundcube';
 
@@ -35,6 +38,9 @@ $rcmail_config['log_dir'] = 'logs/';
 // use this folder to store temp files (must be writeable for apache user)
 $rcmail_config['temp_dir'] = 'temp/';
 
+// List of active plugins (in plugins/ directory)
+$rcmail_config['plugins'] = array();
+
 // enable caching of messages and mailbox data in the local database.
 // this is recommended if the IMAP server does not run on the same machine
 $rcmail_config['enable_caching'] = TRUE;
@@ -43,6 +49,10 @@ $rcmail_config['enable_caching'] = TRUE;
 // possible units: s, m, h, d, w
 $rcmail_config['message_cache_lifetime'] = '10d';
 
+// enforce connections over https
+// with this option enabled, all non-secure connections will be redirected
+$rcmail_config['force_https'] = FALSE;
+
 // automatically create a new RoundCube user when log-in the first time.
 // a new user will be created once the IMAP login succeeds.
 // set to false if only registered users can use this service
@@ -80,12 +90,14 @@ $rcmail_config['virtuser_file'] = '';
 
 // Query to resolve user names and e-mail addresses from the database
 // %u will be replaced with the current username for login.
-// The query should select the user's e-mail address as first col
+// The query should select the user's e-mail address as first column
+// and optional identity name as second column
 $rcmail_config['virtuser_query'] = '';
 
 // use this host for sending mails.
 // to use SSL connection, set ssl://smtp.host.com
 // if left blank, the PHP mail() function is used
+// Use %h variable as replacement for user's IMAP hostname
 $rcmail_config['smtp_server'] = '';
 
 // SMTP port (default is 25; 465 for SSL)
@@ -112,6 +124,15 @@ $rcmail_config['smtp_helo_host'] = '';
 // Log sent messages
 $rcmail_config['smtp_log'] = TRUE;
 
+// Log SQL queries to /sql or to syslog
+$rcmail_config['sql_debug'] = false;
+
+// Log IMAP conversation to /imap or to syslog
+$rcmail_config['imap_debug'] = false;
+
+// Log SMTP conversation to /smtp or to syslog
+$rcmail_config['smtp_debug'] = false;
+
 // How many seconds must pass between emails sent by a user
 $rcmail_config['sendmail_delay'] = 0;
 
@@ -142,17 +163,17 @@ $rcmail_config['des_key'] = 'rcmail-!24ByteDESkey*Str';
 // RFC1766 formatted language name like en_US, de_DE, de_CH, fr_FR, pt_BR
 $rcmail_config['language'] = null;
 
-// use this format for short date display
+// use this format for short date display (date or strftime format)
 $rcmail_config['date_short'] = 'D H:i';
 
-// use this format for detailed date/time formatting
+// use this format for detailed date/time formatting (date or strftime format)
 $rcmail_config['date_long'] = 'd.m.Y H:i';
 
-// use this format for today's date display
+// use this format for today's date display (date or strftime format)
 $rcmail_config['date_today'] = 'H:i';
 
 // add this user-agent to message headers when sending
-$rcmail_config['useragent'] = 'RoundCube Webmail/0.2.2';
+$rcmail_config['useragent'] = 'RoundCube Webmail/'.RCMAIL_VERSION;
 
 // use this name to compose page titles
 $rcmail_config['product_name'] = 'RoundCube Webmail';
@@ -209,8 +230,7 @@ $rcmail_config['spellcheck_uri'] = '';
 
 // These languages can be selected for spell checking.
 // Configure as a PHP style hash array: array('en'=>'English', 'de'=>'Deutsch');
-// Leave empty for default set of Google spell check languages, should be defined
-// when using local Pspell extension
+// Leave empty for default set of available language.
 $rcmail_config['spellcheck_languages'] = NULL;
 
 // path to a text file which will be added to each sent message
@@ -220,6 +240,12 @@ $rcmail_config['generic_message_footer'] = '';
 // add a received header to outgoing mails containing the creators IP and hostname
 $rcmail_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.
+$rcmail_config['http_received_header_encrypt'] = false;
+
 // this string is used as a delimiter for message headers when sending
 // leave empty for auto-detection
 $rcmail_config['mail_header_delimiter'] = NULL;
@@ -390,7 +416,7 @@ $rcmail_config['inline_images'] = TRUE;
 // 0 - Full RFC 2231 compatible
 // 1 - RFC 2047 for 'name' and RFC 2231 for 'filename' parameter (Thunderbird's default)
 // 2 - Full 2047 compatible
-$rcmail_config['mime_param_folding'] = 0;
+$rcmail_config['mime_param_folding'] = 1;
 
 // Set TRUE if deleted messages should not be displayed
 // This will make the application run slower
@@ -400,9 +426,8 @@ $rcmail_config['skip_deleted'] = FALSE;
 // False means that a message's read status is not affected by marking it as deleted
 $rcmail_config['read_when_deleted'] = TRUE;
 
-// When a Trash folder is not present and a message is deleted, flag 
-// the message for deletion rather than deleting it immediately.  Setting this to 
-// false causes deleted messages to be permanantly removed if there is no Trash folder
+// Set to TRUE to newer delete messages immediately
+// Use 'Purge' to remove messages marked as deleted 
 $rcmail_config['flag_for_deletion'] = FALSE;
 
 // Default interval for keep-alive/check-recent requests (in seconds)
@@ -412,5 +437,8 @@ $rcmail_config['keep_alive'] = 60;
 // If true all folders will be checked for recent messages
 $rcmail_config['check_all_folders'] = FALSE;
 
+// If true, after message delete/move, the next message will be displayed
+$rcmail_config['display_next'] = FALSE;
+
 // end of config file
 ?>
diff --git a/config/mimetypes.php b/config/mimetypes.php
index 2e95a29..159fce0 100644
--- a/config/mimetypes.php
+++ b/config/mimetypes.php
@@ -41,6 +41,7 @@ return array(
   'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
   'xps' => 'application/vnd.ms-xpsdocument',
   'rar' => 'application/x-rar-compressed',
+  'vcf' => 'text/vcard',
 );
 
 ?>
\ No newline at end of file
diff --git a/index.php b/index.php
index a61ca2f..624d898 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
 /*
  +-------------------------------------------------------------------------+
  | RoundCube Webmail IMAP Client                                           |
- | Version 0.2.2                                                           |
+ | Version 0.3-stable                                                      |
  |                                                                         |
  | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                   |
  |                                                                         |
@@ -23,7 +23,7 @@
  | Author: Thomas Bruederli                           |
  +-------------------------------------------------------------------------+
 
- $Id: index.php 2484 2009-05-15 10:24:09Z thomasb $
+ $Id: index.php 2916 2009-09-04 10:58:29Z thomasb $
 
 */
 
@@ -36,18 +36,11 @@ $RCMAIL = rcmail::get_instance();
 // init output class
 $OUTPUT = !empty($_REQUEST['_remote']) ? $RCMAIL->init_json() : $RCMAIL->load_gui(!empty($_REQUEST['_framed']));
 
-// set output buffering
-if ($RCMAIL->action != 'get' && $RCMAIL->action != 'viewsource') {
-  // use gzip compression if supported
-  if (function_exists('ob_gzhandler')
-      && !ini_get('zlib.output_compression')
-      && ini_get('output_handler') != 'ob_gzhandler') {
-    ob_start('ob_gzhandler');
-  }
-  else {
-    ob_start();
-  }
-}
+// init plugin API
+$RCMAIL->plugins->init();
+
+// turn on output buffering
+ob_start();
 
 // check if config files had errors
 if ($err_str = $RCMAIL->config->get_error()) {
@@ -70,23 +63,38 @@ if ($RCMAIL->action=='error' && !empty($_GET['_code'])) {
   raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
 }
 
+// check if https is required (for login) and redirect if necessary
+if ($RCMAIL->config->get('force_https', false) && empty($_SESSION['user_id']) && !(isset($_SERVER['HTTPS']) || $_SERVER['SERVER_PORT'] == 443)) {
+  header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
+  exit;
+}
+
+// trigger startup plugin hook
+$startup = $RCMAIL->plugins->exec_hook('startup', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
+$RCMAIL->set_task($startup['task']);
+$RCMAIL->action = $startup['action'];
+
+
 // try to log in
 if ($RCMAIL->action=='login' && $RCMAIL->task=='mail') {
   // purge the session in case of new login when a session already exists 
-  $RCMAIL->kill_session(); 
-  
-  // set IMAP host
-  $host = $RCMAIL->autoselect_host();
+  $RCMAIL->kill_session();
   
+  $auth = $RCMAIL->plugins->exec_hook('authenticate', array(
+    'host' => $RCMAIL->autoselect_host(),
+    'user' => trim(get_input_value('_user', RCUBE_INPUT_POST)),
+    'cookiecheck' => true,
+  )) + array('pass' => get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'));
+
   // check if client supports cookies
-  if (empty($_COOKIE)) {
+  if ($auth['cookiecheck'] && empty($_COOKIE)) {
     $OUTPUT->show_message("cookiesdisabled", 'warning');
   }
-  else if ($_SESSION['temp'] && !empty($_POST['_user']) && !empty($_POST['_pass']) &&
-           $RCMAIL->login(trim(get_input_value('_user', RCUBE_INPUT_POST), ' '),
-              get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'), $host)) {
+  else if ($_SESSION['temp'] && !$auth['abort'] && !empty($auth['host']) &&
+            !empty($auth['user']) && isset($auth['pass']) && 
+            $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'])) {
     // create new session ID
-    unset($_SESSION['temp']);
+    rcube_sess_unset('temp');
     rcube_sess_regenerate_id();
 
     // send auth cookie if necessary
@@ -99,21 +107,33 @@ if ($RCMAIL->action=='login' && $RCMAIL->task=='mail') {
         $RCMAIL->user->ID,
         $_SERVER['REMOTE_ADDR']));
     }
+    
+    // restore original request parameters
+    $query = array();
+    if ($url = get_input_value('_url', RCUBE_INPUT_POST))
+      parse_str($url, $query);
+
+    // allow plugins to control the redirect url after login success
+    $redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('task' => $RCMAIL->task));
+    unset($redir['abort']);
 
     // send redirect
-    $OUTPUT->redirect();
+    $OUTPUT->redirect($redir);
   }
   else {
     $OUTPUT->show_message($IMAP->error_code < -1 ? 'imaperror' : 'loginfailed', 'warning');
+    $RCMAIL->plugins->exec_hook('login_failed', array('code' => $IMAP->error_code, 'host' => $auth['host'], 'user' => $auth['user']));
     $RCMAIL->kill_session();
   }
 }
 
 // end session
-else if (($RCMAIL->task=='logout' || $RCMAIL->action=='logout') && isset($_SESSION['user_id'])) {
+else if ($RCMAIL->task=='logout' && isset($_SESSION['user_id'])) {
+  $userdata = array('user' => $_SESSION['username'], 'host' => $_SESSION['imap_host'], 'lang' => $RCMAIL->user->language);
   $OUTPUT->show_message('loggedout');
   $RCMAIL->logout_actions();
   $RCMAIL->kill_session();
+  $RCMAIL->plugins->exec_hook('logout_after', $userdata);
 }
 
 // check session and auth cookie
@@ -124,14 +144,21 @@ else if ($RCMAIL->action != 'login' && $_SESSION['user_id'] && $RCMAIL->action !
   }
 }
 
+// don't check for valid request tokens in these actions
+$request_check_whitelist = array('login'=>1, 'spell'=>1);
 
 // check client X-header to verify request origin
 if ($OUTPUT->ajax_call) {
-  if (!$RCMAIL->config->get('devel_mode') && !rc_request_header('X-RoundCube-Referer')) {
+  if (!$RCMAIL->config->get('devel_mode') && rc_request_header('X-RoundCube-Request') != $RCMAIL->get_request_token()) {
     header('HTTP/1.1 404 Not Found');
     die("Invalid Request");
   }
 }
+// check request token in POST form submissions
+else if (!empty($_POST) && !$request_check_whitelist[$RCMAIL->action] && !$RCMAIL->check_request()) {
+  $OUTPUT->show_message('invalidrequest', 'error');
+  $OUTPUT->send($RCMAIL->task);
+}
 
 
 // not logged in -> show login page
@@ -201,16 +228,22 @@ $action_map = array(
 );
 
 // include task specific functions
-include_once 'program/steps/'.$RCMAIL->task.'/func.inc';
+if (is_file($incfile = 'program/steps/'.$RCMAIL->task.'/func.inc'))
+  include_once($incfile);
 
 // allow 5 "redirects" to another action
 $redirects = 0; $incstep = null;
 while ($redirects < 5) {
   $stepfile = !empty($action_map[$RCMAIL->task][$RCMAIL->action]) ?
     $action_map[$RCMAIL->task][$RCMAIL->action] : strtr($RCMAIL->action, '-', '_') . '.inc';
-    
+
+  // execute a plugin action
+  if (preg_match('/^plugin\./', $RCMAIL->action)) {
+    $RCMAIL->plugins->exec_action($RCMAIL->action);
+    break;
+  }
   // try to include the step file
-  if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) {
+  else if (is_file($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile)) {
     include($incfile);
     $redirects++;
   }
diff --git a/installer/check.php b/installer/check.php
index 4c34dd6..9bdb41b 100644
--- a/installer/check.php
+++ b/installer/check.php
@@ -1,7 +1,8 @@
 
'pcre', 'DOM' => 'dom', 'Session' => 'session'); +$required_php_exts = array('PCRE' => 'pcre', 'DOM' => 'dom', + 'Session' => 'session', 'XML' => 'xml'); $optional_php_exts = array('FileInfo' => 'fileinfo', 'Libiconv' => 'iconv', 'Multibyte' => 'mbstring', 'OpenSSL' => 'openssl', 'Mcrypt' => 'mcrypt', @@ -15,7 +16,10 @@ $supported_dbs = array('MySQL' => 'mysql', 'MySQLi' => 'mysqli', 'PostgreSQL' => 'pgsql', 'SQLite (v2)' => 'sqlite'); $ini_checks = array('file_uploads' => 1, 'session.auto_start' => 0, - 'zend.ze1_compatibility_mode' => 0, 'mbstring.func_overload' => 0); + 'zend.ze1_compatibility_mode' => 0, 'mbstring.func_overload' => 0, + 'suhosin.session.encrypt' => 0); + +$optional_checks = array('date.timezone' => '-NOTEMPTY-'); $source_urls = array( 'Sockets' => 'http://www.php.net/manual/en/ref.sockets.php', @@ -66,7 +70,7 @@ foreach ($required_php_exts AS $name => $ext) { ?> -

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

+

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

$ext) { @@ -125,21 +129,54 @@ foreach ($required_libs as $classname => $file) { ?>

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, "cannot be empty and needs to be set"); + } else { + $RCI->pass($var); + } + echo '
'; + continue; + } if ($status == $val) { $RCI->pass($var); - } - else { + } 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 ($status == $val) { + $RCI->pass($var); + } else { + $RCI->optfail($var, "is '$status', could be '$val'"); + } + echo '
'; +} +?> + failures) { diff --git a/installer/config.php b/installer/config.php index fef222d..e51b4bf 100644 --- a/installer/config.php +++ b/installer/config.php @@ -112,14 +112,26 @@ echo $check_caching->show(intval($RCI->getprop('enable_caching')), array('value'
enable_spellcheck
'_enable_spellcheck', 'id' => "cfgspellcheck")); +echo $check_spell->show(intval($RCI->getprop('enable_spellcheck')), array('value' => 1)); +?> +
+
+ +
spellcheck_engine
+
+ '_spellcheck_engine', 'id' => "cfgspellcheckengine")); +if (extension_loaded('pspell')) + $select_spell->add('pspell', 'pspell'); +$select_spell->add('Googlie', 'googlie'); -$check_caching = new html_checkbox(array('name' => '_enable_spellcheck', 'id' => "cfgspellcheck")); -echo $check_caching->show(intval($RCI->getprop('enable_spellcheck')), array('value' => 1)); +echo $select_spell->show($RCI->is_post ? $_POST['_spellcheck_engine'] : 'pspell'); ?> -
+
-

It is based on GoogieSpell what implies that the message content will be sent to Google in order to check the spelling.

+

GoogieSpell implies that the message content will be sent to Google in order to check the spelling.

identities_level
@@ -255,13 +267,13 @@ $dsnw = MDB2::parseDSN($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 '
'; echo $input_dbname->show($RCI->is_post ? $_POST['_dbname'] : $dsnw['database']); -echo '
'; +echo '
'; echo $input_dbuser->show($RCI->is_post ? $_POST['_dbuser'] : $dsnw['username']); -echo '
'; +echo '
'; echo $input_dbpass->show($RCI->is_post ? $_POST['_dbpass'] : $dsnw['password']); -echo '
'; +echo '
'; ?> @@ -417,7 +429,7 @@ $text_smtpport = new html_inputfield(array('name' => '_smtp_port', 'size' => 6, echo $text_smtpport->show($RCI->getprop('smtp_port')); ?> -
SMTP port (default is 25; 465 for SSL)
+
SMTP port (default is 25; 465 for SSL; 587 for submission)
smtp_user/smtp_pass
@@ -473,7 +485,7 @@ echo $check_smtplog->show(intval($RCI->getprop('smtp_log')), array('value' => 1) Display settings & user prefs
-
language
+
language *
show(intval($RCI->getprop('htmleditor'))); $select_autosave = new html_select(array('name' => '_draft_autosave', 'id' => 'cfgautosave')); $select_autosave->add('never', 0); -foreach (array(3, 5, 10) as $i => $min) +foreach (array(1, 3, 5, 10) as $i => $min) $select_autosave->add("$min min", $min*60); echo $select_autosave->show(intval($RCI->getprop('draft_autosave'))); diff --git a/installer/index.php b/installer/index.php index 549b6f0..d1c55e4 100644 --- a/installer/index.php +++ b/installer/index.php @@ -13,8 +13,8 @@ $include_path .= ini_get('include_path'); set_include_path($include_path); -require_once 'rcube_shared.inc'; require_once 'utils.php'; +require_once 'main.inc'; session_start(); @@ -47,9 +47,9 @@ if ($RCI->configured && ($RCI->getprop('enable_installer') || $_SESSION['allowin exit; } -// go to 'test' step if we have a local configuration +// go to 'check env' step if we have a local configuration if ($RCI->configured && empty($_REQUEST['_step'])) { - header("Location: ./?_step=3"); + header("Location: ./?_step=1"); exit; } diff --git a/installer/rcube_install.php b/installer/rcube_install.php index b2b8257..f30c008 100644 --- a/installer/rcube_install.php +++ b/installer/rcube_install.php @@ -136,10 +136,10 @@ class rcube_install */ function create_config($which, $force = false) { - $out = file_get_contents(RCMAIL_CONFIG_DIR . "/{$which}.inc.php.dist"); + $out = @file_get_contents(RCMAIL_CONFIG_DIR . "/{$which}.inc.php.dist"); if (!$out) - return '[Warning: could not read the template file]'; + return '[Warning: could not read the config template file]'; foreach ($this->config as $prop => $default) { $value = (isset($_POST["_$prop"]) || $this->bool_config_props[$prop]) ? $_POST["_$prop"] : $default; @@ -176,6 +176,18 @@ class rcube_install else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) { $value = '%p'; } + else if ($prop == 'default_imap_folders'){ + $value = Array(); + foreach($this->config['default_imap_folders'] as $_folder){ + switch($_folder) { + case 'Drafts': $_folder = $this->config['drafts_mbox']; break; + case 'Sent': $_folder = $this->config['sent_mbox']; break; + case 'Junk': $_folder = $this->config['junk_mbox']; break; + case 'Trash': $_folder = $this->config['trash_mbox']; break; + } + if (!in_array($_folder, $value)) $value[] = $_folder; + } + } else if (is_bool($default)) { $value = (bool)$value; } @@ -244,9 +256,11 @@ class rcube_install $out['dependencies'][] = array('prop' => 'spellcheck_engine', 'explain' => 'This requires the pspell extension which could not be loaded.'); } - if (empty($this->config['spellcheck_languages'])) { - $out['dependencies'][] = array('prop' => 'spellcheck_languages', - 'explain' => 'You should specify the list of languages supported by your local pspell installation.'); + if (!empty($this->config['spellcheck_languages'])) { + foreach ($this->config['spellcheck_languages'] as $lang => $descr) + if (!pspell_new($lang)) + $out['dependencies'][] = array('prop' => 'spellcheck_languages', + 'explain' => "You are missing pspell support for language $lang ($descr)"); } } @@ -453,6 +467,20 @@ class rcube_install echo Q($name) . ':  NOT OK'; $this->_showhint($message, $url); } + + + /** + * Display an error status for optional settings/features + * + * @param string Test name + * @param string Error message + * @param string URL for details + */ + function optfail($name, $message = '', $url = '') + { + echo Q($name) . ':  NOT OK'; + $this->_showhint($message, $url); + } /** @@ -537,11 +565,11 @@ class rcube_install if ($lines = @file($fname, FILE_SKIP_EMPTY_LINES)) { $buff = ''; foreach ($lines as $i => $line) { - if (eregi('^--', $line)) + if (preg_match('/^--/', $line)) continue; $buff .= $line . "\n"; - if (eregi(';$', trim($line))) { + if (preg_match('/;$/', trim($line))) { $DB->query($buff); $buff = ''; if ($this->get_error()) diff --git a/installer/test.php b/installer/test.php index 5740a64..90d089f 100644 --- a/installer/test.php +++ b/installer/test.php @@ -248,13 +248,11 @@ $to_field = new html_inputfield(array('name' => '_to', 'id' => 'sendmailto')); if (isset($_POST['sendmail']) && !empty($_POST['_from']) && !empty($_POST['_to'])) { - require_once 'rcube_smtp.inc'; - echo '

Trying to send email...
'; if (preg_match('/^' . $RCI->email_pattern . '$/i', trim($_POST['_from'])) && preg_match('/^' . $RCI->email_pattern . '$/i', trim($_POST['_to']))) { - + $headers = array( 'From' => trim($_POST['_from']), 'To' => trim($_POST['_to']), @@ -267,7 +265,7 @@ if (isset($_POST['sendmail']) && !empty($_POST['_from']) && !empty($_POST['_to'] // send mail using configured SMTP server if ($RCI->getprop('smtp_server')) { $CONFIG = $RCI->config; - + if (!empty($_POST['_smtp_user'])) { $CONFIG['smtp_user'] = $_POST['_smtp_user']; } @@ -277,10 +275,14 @@ if (isset($_POST['sendmail']) && !empty($_POST['_from']) && !empty($_POST['_to'] $mail_object = new rcube_mail_mime(); $send_headers = $mail_object->headers($headers); - - $status = smtp_mail($headers['From'], $headers['To'], - ($foo = $mail_object->txtHeaders($send_headers)), - $body, $smtp_response); + + $SMTP = new rcube_smtp(); + $SMTP->connect(); + + $status = $SMTP->send_mail($headers['From'], $headers['To'], + ($foo = $mail_object->txtHeaders($send_headers)), $body); + + $smtp_response = $SMTP->get_response(); } else { // use mail() $header_str = 'From: ' . $headers['From']; diff --git a/installer/utils.php b/installer/utils.php index c1775f2..1c10105 100644 --- a/installer/utils.php +++ b/installer/utils.php @@ -1,15 +1,41 @@ | + +-------------------------------------------------------------------------+ + + $Id: index.php 2696 2009-07-02 06:38:26Z thomasb $ + +*/ /** * Use PHP5 autoload for dynamic class loading - * (copy from program/incllude/iniset.php) + * (copy from program/include/iniset.php) */ function __autoload($classname) { $filename = preg_replace( - array('/MDB2_(.+)/', '/Mail_(.+)/', '/^html_.+/', '/^utf8$/'), - array('MDB2/\\1', 'Mail/\\1', 'html', 'utf8.class'), + array('/MDB2_(.+)/', '/Mail_(.+)/', '/Net_(.+)/', '/^html_.+/', '/^utf8$/'), + array('MDB2/\\1', 'Mail/\\1', 'Net/\\1', 'html', 'utf8.class'), $classname ); include_once $filename. '.php'; @@ -17,19 +43,7 @@ function __autoload($classname) /** - * Shortcut function for htmlentities() - * - * @param string String to quote - * @return string The html-encoded string - */ -function Q($string) -{ - return htmlentities($string, ENT_COMPAT, 'UTF-8'); -} - - -/** - * Fake rinternal error handler to catch errors + * Fake internal error handler to catch errors */ function raise_error($p) { @@ -37,4 +51,3 @@ function raise_error($p) $rci->raise_error($p); } - diff --git a/plugins/additional_message_headers/additional_message_headers.php b/plugins/additional_message_headers/additional_message_headers.php new file mode 100644 index 0000000..9247138 --- /dev/null +++ b/plugins/additional_message_headers/additional_message_headers.php @@ -0,0 +1,42 @@ +add_hook('outgoing_message_headers', array($this, 'message_headers')); + } + + function message_headers($args){ + + // additional email headers + $additional_headers = rcmail::get_instance()->config->get('additional_message_headers',array()); + foreach($additional_headers as $header=>$value){ + $args['headers'][$header] = $value; + } + + return $args; + } +} diff --git a/plugins/archive/archive.js b/plugins/archive/archive.js new file mode 100644 index 0000000..d771fb6 --- /dev/null +++ b/plugins/archive/archive.js @@ -0,0 +1,36 @@ +/* + * Archive plugin script + * @version 1.2 + */ + +function rcmail_archive(prop) +{ + if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length)) + return; + + var uids = rcmail.env.uid ? rcmail.env.uid : rcmail.message_list.get_selection().join(','); + + rcmail.set_busy(true, 'loading'); + rcmail.http_post('plugin.archive', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), true); +} + +// callback for app-onload event +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + + // register command (directly enable in message view mode) + rcmail.register_command('plugin.archive', rcmail_archive, (rcmail.env.uid && rcmail.env.mailbox != rcmail.env.archive_folder)); + + // add event-listener to message list + if (rcmail.message_list) + rcmail.message_list.addEventListener('select', function(list){ + rcmail.enable_command('plugin.archive', (list.get_selection().length > 0 && rcmail.env.mailbox != rcmail.env.archive_folder)); + }); + + // set css style for archive folder + var li; + if (rcmail.env.archive_folder && (li = rcmail.get_folder_li(rcmail.env.archive_folder))) + $(li).css('background-image', 'url(plugins/archive/foldericon.png)'); + }) +} + diff --git a/plugins/archive/archive.php b/plugins/archive/archive.php new file mode 100644 index 0000000..9df7f8b --- /dev/null +++ b/plugins/archive/archive.php @@ -0,0 +1,133 @@ +register_action('plugin.archive', array($this, 'request_action')); + + # There is no "Archived flags" + # $GLOBALS['IMAP_FLAGS']['ARCHIVED'] = 'Archive'; + + $rcmail = rcmail::get_instance(); + if ($rcmail->task == 'mail' && ($rcmail->action == '' || $rcmail->action == 'show') && ($archive_folder = $rcmail->config->get('archive_mbox'))) { + $this->include_script('archive.js'); + $this->add_texts('localization', true); + $this->add_button( + array( + 'command' => 'plugin.archive', + 'imagepas' => 'archive_pas.png', + 'imageact' => 'archive_act.png', + 'title' => 'buttontitle', + 'domain' => $this->ID, + ), + 'toolbar'); + + // register hook to localize the archive folder + $this->add_hook('render_mailboxlist', array($this, 'render_mailboxlist')); + + // set env variable for client + $rcmail->output->set_env('archive_folder', $archive_folder); + + // add archive folder to the list of default mailboxes + if (($default_folders = $rcmail->config->get('default_imap_folders')) && !in_array($archive_folder, $default_folders)) { + $default_folders[] = $archive_folder; + $rcmail->config->set('default_imap_folders', $default_folders); + } + + } + else if ($rcmail->task == 'settings') { + $dont_override = $rcmail->config->get('dont_override', array()); + if (!in_array('archive_mbox', $dont_override)) { + $this->add_hook('user_preferences', array($this, 'prefs_table')); + $this->add_hook('save_preferences', array($this, 'save_prefs')); + } + } + } + + function render_mailboxlist($p) + { + $rcmail = rcmail::get_instance(); + $archive_folder = $rcmail->config->get('archive_mbox'); + + // set localized name for the configured archive folder + if ($archive_folder) { + if (isset($p['list'][$archive_folder])) + $p['list'][$archive_folder]['name'] = $this->gettext('archivefolder'); + else // search in subfolders + $this->_mod_folder_name($p['list'], $archive_folder, $this->gettext('archivefolder')); + } + + return $p; + } + + function _mod_folder_name(&$list, $folder, $new_name) + { + foreach ($list as $idx => $item) { + if ($item['id'] == $folder) { + $list[$idx]['name'] = $new_name; + return true; + } else if (!empty($item['folders'])) + if ($this->_mod_folder_name($list[$idx]['folders'], $folder, $new_name)) + return true; + } + return false; + } + + function request_action() + { + $this->add_texts('localization'); + + $uids = get_input_value('_uid', RCUBE_INPUT_POST); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); + + $rcmail = rcmail::get_instance(); + + // There is no "Archive flags", but I left this line in case it may be useful + // $rcmail->imap->set_flag($uids, 'ARCHIVE'); + + if (($archive_mbox = $rcmail->config->get('archive_mbox')) && $mbox != $archive_mbox) { + $rcmail->output->command('move_messages', $archive_mbox); + $rcmail->output->command('display_message', $this->gettext('archived'), 'confirmation'); + } + + $rcmail->output->send(); + } + + function prefs_table($args) + { + if ($args['section'] == 'folders') { + $this->add_texts('localization'); + + $rcmail = rcmail::get_instance(); + $select = rcmail_mailbox_select(array('noselection' => '---', 'realnames' => true, 'maxlength' => 30)); + + $args['blocks']['main']['options']['archive_mbox'] = array( + 'title' => $this->gettext('archivefolder'), + 'content' => $select->show($rcmail->config->get('archive_mbox'), array('name' => "_archive_mbox")) + ); + } + + return $args; + } + + function save_prefs($args) + { + if ($args['section'] == 'folders') { + $args['prefs']['archive_mbox'] = get_input_value('_archive_mbox', RCUBE_INPUT_POST); + return $args; + } + } + +} diff --git a/skins/default/images/buttons/drafts_pas.png b/plugins/archive/archive_act.png similarity index 60% rename from skins/default/images/buttons/drafts_pas.png rename to plugins/archive/archive_act.png index 424709e7721a3ddd66c94d017ae8905e90d6fec8..2a173586832e8f8c6dedafd79a93611dec8b5a61 100644 GIT binary patch delta 931 zcmV;U16=&TBF`MKlnRri3M+pDdPzhd=u zoSb%ZVpr0fQv*;Dj4H)) zn$m8Faowo^h`5}1ss&@hHx>XI0nli9tSr}Yrc{l;9;7kJJV<{qn-HnmPRbp|44{@# zcg}bK5)DnYX-y@ZEd;6&qp0`$t$?uI)t11zJoB_iDs4uFat0DcV`8VAINsRrS1KOu zMq(%c)!?AuOy&Urg}Jm(q2S^5y#q?<`TPM8ci!KDbv;bLy0V*|Q)3lt2vmyqQ{vnvnrNhuRPsp(hW~fdG{w zC|duGG_(8vbulnvydI)REfaOlm+2PDfW}f z`f3+Iv6w(Y|H!1+-`S}w?bU!wk1ulfk2byKVk@-Vx}E9i8|Je~YN6NNJe30501XgP z4>$&tpj(;^1K?+3brsAG$7O~Abh$W1~16JaH?(D^O7=Sq- z69jg_O{9PDkH3lrB|tjYvr62eZf&Om#5bo3ihx+0L=IilpPzm4eeu|QqAFk{CSxBZ;*RzZOq|YOZ~7YjSqkOW{kW`-;x@J_&I3!aH{2t5 zK}GSwYCr*~P4X^C#IEw9n*ddPQ*G6Q)DAQrt-bFTD{2QCkJeT_NbSw6Q<)2M>eQ+GFJHcV zzcB`D?L5ete#@3E=eBR({xUF-C{%2PWY&uAzg56IfQgBTZG(e@Y~H+i(ZOS5W1Km2 zW*hLbQM1&lrD`i!wHP%3t^dgh4<0zz;Qqp;zp?e^9gI$1WJ%3({^DPFY1K>p?~6J3#*G_kwOWe}P)Ney~UN2OAU3js``nF}ma768Q39HI~fM4;7~rfwrie?6m~ zL^|CDnO9uYLU05piZAbdRI0|%?K+Z*mBj$0?Q+(X0<{F05&Xp?dpL3W+5Yei_uk2X zRjO6b605AeXMnw*t@Xb@@cv!w`p_@&p@7JP5{)HoXH;w@EC3Laa5M9@dsGPm%u&Br zII#CuTsZp<-ukhWuYBb5eL?l4e@dMyKYH@Zg)=|*B!B-|g&hwaBloe`eNaMF)ERp1 zj3lvP!C|Dv#;ME^q;Q^8luVQ~Dp^#nO-%Cj$DZfXGnQ|jK1huUFXShv*=o^K;oC1B zV)$v{6MKJ8 z7fx{YXov6q`7pccyQ$lRUG?33@9bgzbg0STN1tJ0D$q)6jE}du^3MiWUYp{|Ymjhc9jz*|6^y%j~{he`+4F8aw)!iI@^~e0- zTi1B#;bX+GluCUySr*9Ce^BItnUpRNeU3HOEfR*-O=mfIX#!PnDs+O91|juA6i8OC zK~m2npZqPy9^Z|A=_!8m#0Z1CeokW6QeD1=7z`345)~=e5`_dq@Q!5JA^2EVA?CKjC*z4DrF8N2!_()R(Wpe@8=zvzarBpk?S9 zl|ln{Nxl$3t!uY(MzcUC4M-V^XpvZo0|i1*d@$6PucMv{5AQie@K9T}ny3~LMWPTx zxqG2lO9TWYc#K`@7XVmevi4L;Cs%q-P>N7g(INpwMPi|a;1!96>XOyujVbC&??5yu zbABX*VyuW1pj1WbO`J#wK5I_wQ9Ck4PgGsY)PM#|+m%if^AX@B7l^AAWLR2Iup;WmK5Q7q< z%mPR0n*l&#z*w`;2SSuAOEc0`>1AcA);TteMIZYMLli-xf8c%LAB9lqo2oE@_Jsqe zcYtQ|@_;eMj{W0&-komXy&*Wo=SuDhb9g5>FE}T7S7bCF1fL7el{G>r&i}sfD5xkZ z<;hl-XnEFTeB`g*7-Rc2p)scVmQ7n%uGsXRJp;>EgPB`X74x<9N-dQmDzo2Yw)!c~ zkQt$6B+Z!FeSqsWaFfHoo_KwvrK)}gzye9xOsSc<{oM9%i`@C>3z`5C2tZU-nO7B$ zjEvMeoerH&$IKL_y+id{4-L5v9O|mT0EX$afn9z2+O|#k5;o)JfUcGv&|2&;v hr_Kt!?f-`V8UO@%Tc=HaAEE#N002ovPDHLkV1f)YF}?r* diff --git a/plugins/archive/archive_pas.png b/plugins/archive/archive_pas.png new file mode 100644 index 0000000000000000000000000000000000000000..8de2085836b64805f5088a30526af04630d966aa GIT binary patch literal 977 zcmV;?11|iDP)$NJ8^Kp->#%7j>qAeAv8z2g9q}bKY&1$RFoCzl4(Gcryt=30}z4lo+&AD zItu|6A!rTY%c>1d8CT~vmf_?H6$}&khO&bsbOT~XVRX1yooBNul#5DB0GvaRk0;xB zphCGlrVh!Z460Pi2irxE&1=s2Cp>^%k--F)ps>Ee{I2=G%e|1>yQXV4Xexi$sRu|U z$##Cmz_rq#$l=a|&4LMIV@P35VNPY=hYdl=8?K>?oZ001HWMD+QS&C0T-xNOTXIHE%|*21Y=5ALW69A7HdH3}nx zH2|rU!g5VNFe;X|wzAr84|wQEA8dRrLw&nwIT%*gB6V{Fy)g~)j#^W07|imbAS2e` zaM$QwUq^!g@E46trl=EK7F(KV9ZpRSG9pq#oUCa(Fy{<*A&~v zimVu*4_{^mq{Xo;j%Pv7q?*~{m=!GNot*4HxwmR$eznk zw@ly-$IAhCKi>t&10cb{T)5tPUjTpl`X#^sjQ-yh3OoIh00000NkvXXu0mjfuQbF0 literal 0 HcmV?d00001 diff --git a/skins/default/images/buttons/drafts_act.png b/plugins/archive/foldericon.png similarity index 58% rename from skins/default/images/buttons/drafts_act.png rename to plugins/archive/foldericon.png index 3fc718a7f8f5d33728e9286838e203c7cd266921..ec0853c4420efb6317e4fc9c34ca17e9e451ac32 100644 GIT binary patch delta 612 zcmV-q0-OEcBJdd@iBL{Q4GJ0x0000DNk~Le0000G0000G2nGNE03Y-JVX+}|3X`M? zD}MqTNkl%&i;y9N7FM)T+=%@Ffq#Mp z?IH;xXjdQwl6@?S7PV;=HsU66Q7x=&W{j9--g}-F_jTrN*|WNE&v~BbJm+4M#M7Gx zkWn;a+@1XZmVg}000YDxwt-hT^$XWOQ-1((DB_$S8M`(&aPm?~QtXz%6uT8bV(a7b zYWdB*0>(?AUI0LhpBS5;8yKA`?G;AJ5akt+9ON7xo-C0zS%3Q=1y+&7%3}YF{u3)V z`e!7?5hU=+a;6kODJI9&L<*P!@)Sss2aJF`29gm7d4o@zUqLD096h^0LK_?J7Jon~ zQef~<>JH{EIRW9 ycM4@$KU z6$C-)gXw6JMy)SJ2m}VvAgFjLKBcsIkfK%*W7=p`q()j%BbAy9w0%gaq+m_M#5zev zCTS+gT+W<1XPSF);6kt%B_4XxA^%#!2d45 z-~(ph;OyRk4?VYL;Na}ufx!pNz<;t%y!hqRsZ)<#zI^$BF$QZF*ybZeRoS*}+kqWB zc03NOj}&4X6D9q!H=3vRxLd?#;V1r z!5DM-UkTKA#JyR6{C?=rAsrqbrrmC1j6p%8wHcQJAE z8XGrk;?0*Y^8T&&FVUDW24hU;+x+|f{rkpJDZrRT37sd!$P7Vq|!K)Gk zyeK}D_2|v()Fop=X4w!j-nw~{W(^;>V*?xS=;hl7KSu%-Kn`R9au0=o6NqDY z^Fqd{!@uy$m>}|b;iBGJAF7~$=t2n!ULdIA1a8dUz*EqU=LlMGvwnlI>q_R8B0v!! z52aui$~jRlv>OF+9OvBtCJI^MS*dgZgvuPE5ClYjV6HVw%?6@cMlA}ok~*1FilC+7 z0EH;7YP+EH7(7iY-zw|w z>|@^-dprI2e|R^$KmHufmk_yEg0ZB{jM&C$7l06D)0yK=RlER;)bA9Y*!OGBpM5Xy z{&dQJflq$5Bd8YjP@{*RKK0GgnO}H{zy302=Yz+{T_|_&6(1BW4DDt{6xnvyS)j(I zGg*O`(s@!*GEvf?WI?$$ImNdgeUZ`UE#EnPh+bk|%1_X1d&-)a@4bAOk!OX^?)wAv z>1hftI4=}lD4gP)P^39FvT65WBsOj4j$9#s4N4jy4bZM2`fgi?M7=!rwG*5@+TsU) zdWPM#J=AQ(?%E!Hc=j1yJ=|dEQ_nLwF-Z)WaKC8>e<85w|>SSzk7`bA38>V z@GFRGgJhW}PkosS=2N;ra5>i4xn;u8HfFP&yfT3*6e_g5l6oO^LJ)}7tVL4CBYS_# zv4eZiZ#>O!p1jD=?q3m^b@Z%SOYjB>0SSs!Yl%XH!MlQJrEPQpsF-@vD##UP@<7uI zP85-nTtx|@6dwB5tS8L^yFT-Ce)r^mFdy4>lpeE*+N!m0CfeR zlS}e|F(w}W+qv0fwvKZKUnnkD@}e|{D}=%cMIpGN%xKOFE*FZTx+D1V{O<@4f{LP2 zoov+=EzcTETzsQxjEPIlmu2O`wpuNdB#8lNx7()KYyyxZi8LAwvMkdq%XD&bQku=CHX02b w85xnQSFh?)-7RVBVzBug>$$nrzvurL08udrdiBG?e*gdg07*qoM6N<$g7BSO4*&oF diff --git a/plugins/archive/localization/de_CH.inc b/plugins/archive/localization/de_CH.inc new file mode 100644 index 0000000..2ed0f5a --- /dev/null +++ b/plugins/archive/localization/de_CH.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/archive/localization/de_DE.inc b/plugins/archive/localization/de_DE.inc new file mode 100644 index 0000000..2ed0f5a --- /dev/null +++ b/plugins/archive/localization/de_DE.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/archive/localization/en_US.inc b/plugins/archive/localization/en_US.inc new file mode 100644 index 0000000..fce31a0 --- /dev/null +++ b/plugins/archive/localization/en_US.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/archive/localization/fr_FR.inc b/plugins/archive/localization/fr_FR.inc new file mode 100644 index 0000000..422d45d --- /dev/null +++ b/plugins/archive/localization/fr_FR.inc @@ -0,0 +1,9 @@ + + diff --git a/plugins/archive/localization/pl_PL.inc b/plugins/archive/localization/pl_PL.inc new file mode 100644 index 0000000..2ecc779 --- /dev/null +++ b/plugins/archive/localization/pl_PL.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/autologon/autologon.php b/plugins/autologon/autologon.php new file mode 100644 index 0000000..c40f2d4 --- /dev/null +++ b/plugins/autologon/autologon.php @@ -0,0 +1,44 @@ +add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function startup($args) + { + $rcmail = rcmail::get_instance(); + + // change action to login + if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) && !empty($_GET['_autologin']) && $this->is_localhost()) + $args['action'] = 'login'; + + return $args; + } + + function authenticate($args) + { + if (!empty($_GET['_autologin']) && $this->is_localhost()) { + $args['user'] = 'me'; + $args['pass'] = '******'; + $args['host'] = 'localhost'; + } + + return $args; + } + + function is_localhost() + { + return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1'; + } + +} + diff --git a/plugins/database_attachments/database_attachments.php b/plugins/database_attachments/database_attachments.php new file mode 100644 index 0000000..a8ac62e --- /dev/null +++ b/plugins/database_attachments/database_attachments.php @@ -0,0 +1,156 @@ + + * + */ +require_once('plugins/filesystem_attachments/filesystem_attachments.php'); +class database_attachments extends filesystem_attachments +{ + + // A prefix for the cache key used in the session and in the key field of the cache table + private $cache_prefix = "db_attach"; + + /** + * Helper method to generate a unique key for the given attachment file + */ + private function _key($filepath) + { + return $this->cache_prefix.md5(mktime().$filepath.$_SESSION['user_id']); + } + + /** + * Save a newly uploaded attachment + */ + function upload($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + $key = $this->_key($args['path']); + $data = base64_encode(file_get_contents($args['path'])); + + $status = $rcmail->db->query( + "INSERT INTO ".get_table_name('cache')." + (created, user_id, cache_key, data) + VALUES (".$rcmail->db->now().", ?, ?, ?)", + $_SESSION['user_id'], + $key, + $data); + + if ($status) { + $args['id'] = $key; + $args['status'] = true; + unset($args['path']); + } + + return $args; + } + + /** + * Save an attachment from a non-upload source (draft or forward) + */ + function save($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + + $key = $this->_key($args['name']); + + if ($args['path']) + $args['data'] = file_get_contents($args['path']); + + $data = base64_encode($args['data']); + + $status = $rcmail->db->query( + "INSERT INTO ".get_table_name('cache')." + (created, user_id, cache_key, data) + VALUES (".$rcmail->db->now().", ?, ?, ?)", + $_SESSION['user_id'], + $key, + $data); + + if ($status) { + $args['id'] = $key; + $args['status'] = true; + } + + return $args; + } + + /** + * Remove an attachment from storage + * This is triggered by the remove attachment button on the compose screen + */ + function remove($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + $status = $rcmail->db->query( + "DELETE FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $args['id']); + + if ($status) { + $args['status'] = true; + } + + return $args; + } + + /** + * When composing an html message, image attachments may be shown + * For this plugin, $this->get_attachment will check the file and + * return it's contents + */ + function display($args) + { + return $this->get_attachment($args); + } + + /** + * When displaying or sending the attachment the file contents are fetched + * using this method. This is also called by the display_attachment hook. + */ + function get_attachment($args) + { + $rcmail = rcmail::get_instance(); + + $sql_result = $rcmail->db->query( + "SELECT cache_id, data + FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $args['id']); + + if ($sql_arr = $rcmail->db->fetch_assoc($sql_result)) { + $args['data'] = base64_decode($sql_arr['data']); + $args['status'] = true; + } + + return $args; + } + + /** + * Delete all temp files associated with this user + */ + function cleanup($args) + { + $rcmail = rcmail::get_instance(); + $rcmail->db->query( + "DELETE FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key like '{$this->cache_prefix}%'", + $_SESSION['user_id']); + } +} diff --git a/plugins/debug_logger/debug_logger.php b/plugins/debug_logger/debug_logger.php new file mode 100644 index 0000000..f04ba6a --- /dev/null +++ b/plugins/debug_logger/debug_logger.php @@ -0,0 +1,146 @@ +plugins->init()): + * + * console("my test","start"); + * console("my message"); + * console("my sql calls","start"); + * console("cp -r * /dev/null","shell exec"); + * console("select * from example","sql"); + * console("select * from example","sql"); + * console("select * from example","sql"); + * console("end"); + * console("end"); + * + * + * logs/master (after reloading the main page): + * + * [17-Feb-2009 16:51:37 -0500] start: Task: mail. + * [17-Feb-2009 16:51:37 -0500] start: my test + * [17-Feb-2009 16:51:37 -0500] my message + * [17-Feb-2009 16:51:37 -0500] shell exec: cp -r * /dev/null + * [17-Feb-2009 16:51:37 -0500] start: my sql calls + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] end: my sql calls - 0.0018 seconds shell exec: 1, sql: 3, + * [17-Feb-2009 16:51:37 -0500] end: my test - 0.0055 seconds shell exec: 1, sql: 3, + * [17-Feb-2009 16:51:38 -0500] end: Task: mail. - 0.8854 seconds shell exec: 1, sql: 3, + * + * logs/sql (after reloading the main page): + * + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + * [17-Feb-2009 16:51:37 -0500] sql: select * from example + */ +class debug_logger extends rcube_plugin +{ + function init() + { + require_once(dirname(__FILE__).'/runlog/runlog.php'); + $this->runlog = new runlog(); + + if(!rcmail::get_instance()->config->get('log_dir')){ + rcmail::get_instance()->config->set('log_dir',INSTALL_PATH.'logs'); + } + + $log_config = rcmail::get_instance()->config->get('debug_logger',array()); + + foreach($log_config as $type=>$file){ + $this->runlog->set_file(rcmail::get_instance()->config->get('log_dir').'/'.$file, $type); + } + + $start_string = ""; + $action = rcmail::get_instance()->action; + $task = rcmail::get_instance()->task; + if($action){ + $start_string .= "Action: ".$action.". "; + } + if($task){ + $start_string .= "Task: ".$task.". "; + } + $this->runlog->start($start_string); + + $this->add_hook('console', array($this, 'console')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function authenticate($args){ + $this->runlog->note('Authenticating '.$args['user'].'@'.$args['host']); + return $args; + } + + function console($args){ + $note = $args[0]; + $type = $args[1]; + + + if(!isset($args[1])){ + // This could be extended to detect types based on the + // file which called console. For now only rcube_imap.inc is supported + $bt = debug_backtrace(); + $file = $bt[3]['file']; + switch(basename($file)){ + case 'rcube_imap.php': + $type = 'imap'; + break; + default: + $type = FALSE; + break; + } + } + switch($note){ + case 'end': + $type = 'end'; + break; + } + + + switch($type){ + case 'start': + $this->runlog->start($note); + break; + case 'end': + $this->runlog->end(); + break; + default: + $this->runlog->note($note, $type); + break; + } + return $args; + } + + function __destruct(){ + $this->runlog->end(); + } +} +?> diff --git a/plugins/debug_logger/runlog/runlog.php b/plugins/debug_logger/runlog/runlog.php new file mode 100644 index 0000000..c9f6726 --- /dev/null +++ b/plugins/debug_logger/runlog/runlog.php @@ -0,0 +1,227 @@ + + */ +class runlog { + + private $start_time = FALSE; + + private $parent_stack = array(); + + public $print_to_console = FALSE; + + private $file_handles = array(); + + private $indent = 0; + + public $threshold = 0; + + public $tag_count = array(); + + public $timestamp = "d-M-Y H:i:s O"; + + public $max_line_size = 150; + + private $run_log = array(); + + function runlog() + { + $this->start_time = microtime( TRUE ); + } + + public function start( $name, $tag = FALSE ) + { + $this->run_log[] = array( 'type' => 'start', + 'tag' => $tag, + 'index' => count($this->run_log), + 'value' => $name, + 'time' => microtime( TRUE ), + 'parents' => $this->parent_stack, + 'ended' => false, + ); + $this->parent_stack[] = $name; + + $this->print_to_console("start: ".$name, $tag, 'start'); + $this->print_to_file("start: ".$name, $tag, 'start'); + $this->indent++; + } + + public function end() + { + $name = array_pop( $this->parent_stack ); + foreach ( $this->run_log as $k => $entry ) { + if ( $entry['value'] == $name && $entry['type'] == 'start' && $entry['ended'] == false) { + $lastk = $k; + } + } + $start = $this->run_log[$lastk]['time']; + $this->run_log[$lastk]['duration'] = microtime( TRUE ) - $start; + $this->run_log[$lastk]['ended'] = true; + + $this->run_log[] = array( 'type' => 'end', + 'tag' => $this->run_log[$lastk]['tag'], + 'index' => $lastk, + 'value' => $name, + 'time' => microtime( TRUE ), + 'duration' => microtime( TRUE ) - $start, + 'parents' => $this->parent_stack, + ); + $this->indent--; + if($this->run_log[$lastk]['duration'] >= $this->threshold){ + $tag_report = ""; + foreach($this->tag_count as $tag=>$count){ + $tag_report .= "$tag: $count, "; + } + if(!empty($tag_report)){ +// $tag_report = "\n$tag_report\n"; + } + $end_txt = sprintf("end: $name - %0.4f seconds $tag_report", $this->run_log[$lastk]['duration'] ); + $this->print_to_console($end_txt, $this->run_log[$lastk]['tag'] , 'end'); + $this->print_to_file($end_txt, $this->run_log[$lastk]['tag'], 'end'); + } + } + + public function increase_tag_count($tag){ + if(!isset($this->tag_count[$tag])){ + $this->tag_count[$tag] = 0; + } + $this->tag_count[$tag]++; + } + + public function get_text(){ + $text = ""; + foreach($this->run_log as $entry){ + $text .= str_repeat(" ",count($entry['parents'])); + if($entry['tag'] != 'text'){ + $text .= $entry['tag'].': '; + } + $text .= $entry['value']; + + if($entry['tag'] == 'end'){ + $text .= sprintf(" - %0.4f seconds", $entry['duration'] ); + } + + $text .= "\n"; + } + return $text; + } + + public function set_file($filename, $tag = 'master'){ + if(!isset($this->file_handle[$tag])){ + $this->file_handles[$tag] = fopen($filename, 'a'); + if(!$this->file_handles[$tag]){ + trigger_error('Could not open file for writing: '.$filename); + } + } + } + + public function note( $msg, $tag = FALSE ) + { + if($tag){ + $this->increase_tag_count($tag); + } + if ( is_array( $msg )) { + $msg = '

' . print_r( $msg, TRUE ) . '
'; + } + $this->debug_messages[] = $msg; + $this->run_log[] = array( 'type' => 'note', + 'tag' => $tag ? $tag:"text", + 'value' => htmlentities($msg), + 'time' => microtime( TRUE ), + 'parents' => $this->parent_stack, + ); + + $this->print_to_file($msg, $tag); + $this->print_to_console($msg, $tag); + + } + + public function print_to_file($msg, $tag = FALSE, $type = FALSE){ + if(!$tag){ + $file_handle_tag = 'master'; + } + else{ + $file_handle_tag = $tag; + } + if($file_handle_tag != 'master' && isset($this->file_handles[$file_handle_tag])){ + $buffer = $this->get_indent(); + $buffer .= "$msg\n"; + if(!empty($this->timestamp)){ + $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer); + } + fwrite($this->file_handles[$file_handle_tag], wordwrap($buffer, $this->max_line_size, "\n ")); + } + if(isset($this->file_handles['master']) && $this->file_handles['master']){ + $buffer = $this->get_indent(); + if($tag){ + $buffer .= "$tag: "; + } + $msg = str_replace("\n","",$msg); + $buffer .= "$msg"; + if(!empty($this->timestamp)){ + $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer); + } + if(strlen($buffer) > $this->max_line_size){ + $buffer = substr($buffer,0,$this->max_line_size - 3)."..."; + } + fwrite($this->file_handles['master'], $buffer."\n"); + } + } + + public function print_to_console($msg, $tag=FALSE){ + if($this->print_to_console){ + if(is_array($this->print_to_console)){ + if(in_array($tag, $this->print_to_console)){ + echo $this->get_indent(); + if($tag){ + echo "$tag: "; + } + echo "$msg\n"; + } + } + else{ + echo $this->get_indent(); + if($tag){ + echo "$tag: "; + } + echo "$msg\n"; + } + } + } + + public function print_totals(){ + $totals = array(); + foreach ( $this->run_log as $k => $entry ) { + if ( $entry['type'] == 'start' && $entry['ended'] == true) { + $totals[$entry['value']]['duration'] += $entry['duration']; + $totals[$entry['value']]['count'] += 1; + } + } + if($this->file_handle){ + foreach($totals as $name=>$details){ + fwrite($this->file_handle,$name.": ".number_format($details['duration'],4)."sec, ".$details['count']." calls \n"); + } + } + } + + private function get_indent(){ + $buf = ""; + for($i = 0; $i < $this->indent; $i++){ + $buf .= " "; + } + return $buf; + } + + + function __destruct(){ + foreach($this->file_handles as $handle){ + fclose($handle); + } + } + +} + +?> diff --git a/plugins/emoticons/emoticons.php b/plugins/emoticons/emoticons.php new file mode 100644 index 0000000..be736b6 --- /dev/null +++ b/plugins/emoticons/emoticons.php @@ -0,0 +1,39 @@ +task = 'mail'; + $this->add_hook('message_part_after', array($this, 'replace')); + + $this->map = array( + ':)' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':)')), + ':-)' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':-)')), + ':(' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':(')), + ':-(' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':-(')), + ); + } + + function replace($args) + { + if ($args['type'] == 'plain') + return array('body' => strtr($args['body'], $this->map)); + + return null; + } + +} + diff --git a/plugins/example_addressbook/example_addressbook.php b/plugins/example_addressbook/example_addressbook.php new file mode 100644 index 0000000..081efcb --- /dev/null +++ b/plugins/example_addressbook/example_addressbook.php @@ -0,0 +1,42 @@ +add_hook('address_sources', array($this, 'address_sources')); + $this->add_hook('get_address_book', array($this, 'get_address_book')); + + // use this address book for autocompletion queries + // (maybe this should be configurable by the user?) + $config = rcmail::get_instance()->config; + $sources = $config->get('autocomplete_addressbooks', array('sql')); + if (!in_array($this->abook_id, $sources)) { + $sources[] = $this->abook_id; + $config->set('autocomplete_addressbooks', $sources); + } + } + + public function address_sources($p) + { + $p['sources'][$this->abook_id] = array('id' => $this->abook_id, 'name' => 'Static List', 'readonly' => true); + return $p; + } + + public function get_address_book($p) + { + if ($p['id'] == $this->abook_id) { + require_once(dirname(__FILE__) . '/example_addressbook_backend.php'); + $p['instance'] = new example_addressbook_backend; + } + + return $p; + } + +} diff --git a/plugins/example_addressbook/example_addressbook_backend.php b/plugins/example_addressbook/example_addressbook_backend.php new file mode 100644 index 0000000..ad6b89d --- /dev/null +++ b/plugins/example_addressbook/example_addressbook_backend.php @@ -0,0 +1,72 @@ +ready = true; + } + + public function set_search_set($filter) + { + $this->filter = $filter; + } + + public function get_search_set() + { + return $this->filter; + } + + public function reset() + { + $this->result = null; + $this->filter = null; + } + + public function list_records($cols=null, $subset=0) + { + $this->result = $this->count(); + $this->result->add(array('ID' => '111', 'name' => "Example Contact", 'firstname' => "Example", 'surname' => "Contact", 'email' => "example@roundcube.net")); + + return $this->result; + } + + public function search($fields, $value, $strict=false, $select=true) + { + // no search implemented, just list all records + return $this->list_records(); + } + + public function count() + { + return new rcube_result_set(1, ($this->list_page-1) * $this->page_size); + } + + public function get_result() + { + return $this->result; + } + + public function get_record($id, $assoc=false) + { + $this->list_records(); + $first = $this->result->first(); + $sql_arr = $first['ID'] == $id ? $first : null; + + return $assoc && $sql_arr ? $sql_arr : $this->result; + } + +} diff --git a/plugins/filesystem_attachments/filesystem_attachments.php b/plugins/filesystem_attachments/filesystem_attachments.php new file mode 100644 index 0000000..fcdcea7 --- /dev/null +++ b/plugins/filesystem_attachments/filesystem_attachments.php @@ -0,0 +1,149 @@ + + * @author Thomas Bruederli + * + */ +class filesystem_attachments extends rcube_plugin +{ + public $task = 'mail'; + + function init() + { + // Save a newly uploaded attachment + $this->add_hook('upload_attachment', array($this, 'upload')); + + // Save an attachment from a non-upload source (draft or forward) + $this->add_hook('save_attachment', array($this, 'save')); + + // Remove an attachment from storage + $this->add_hook('remove_attachment', array($this, 'remove')); + + // When composing an html message, image attachments may be shown + $this->add_hook('display_attachment', array($this, 'display')); + + // Get the attachment from storage and place it on disk to be sent + $this->add_hook('get_attachment', array($this, 'get_attachment')); + + // Delete all temp files associated with this user + $this->add_hook('cleanup_attachments', array($this, 'cleanup')); + $this->add_hook('kill_session', array($this, 'cleanup')); + } + + /** + * Save a newly uploaded attachment + */ + function upload($args) + { + $args['status'] = false; + $rcmail = rcmail::get_instance(); + + // use common temp dir for file uploads + // #1484529: we need absolute path on Windows for move_uploaded_file() + $temp_dir = realpath($rcmail->config->get('temp_dir')); + $tmpfname = tempnam($temp_dir, 'rcmAttmnt'); + + if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) { + $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1; + $args['path'] = $tmpfname; + $args['status'] = true; + + // Note the file for later cleanup + $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $tmpfname; + } + + return $args; + } + + /** + * Save an attachment from a non-upload source (draft or forward) + */ + function save($args) + { + $args['status'] = false; + + if (!$args['path']) { + $rcmail = rcmail::get_instance(); + $temp_dir = unslashify($rcmail->config->get('temp_dir')); + $tmp_path = tempnam($temp_dir, 'rcmAttmnt'); + + if ($fp = fopen($tmp_path, 'w')) { + fwrite($fp, $args['data']); + fclose($fp); + $args['path'] = $tmp_path; + } else + return $args; + } + + $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1; + $args['status'] = true; + + // Note the file for later cleanup + $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $args['path']; + + return $args; + } + + /** + * Remove an attachment from storage + * This is triggered by the remove attachment button on the compose screen + */ + function remove($args) + { + $args['status'] = @unlink($args['path']); + return $args; + } + + /** + * When composing an html message, image attachments may be shown + * For this plugin, the file is already in place, just check for + * the existance of the proper metadata + */ + function display($args) + { + $args['status'] = file_exists($args['path']); + return $args; + } + + /** + * This attachment plugin doesn't require any steps to put the file + * on disk for use. This stub function is kept here to make this + * class handy as a parent class for other plugins which may need it. + */ + function get_attachment($args) + { + return $args; + } + + /** + * Delete all temp files associated with this user + */ + function cleanup($args) + { + // $_SESSION['compose']['attachments'] is not a complete record of + // temporary files because loading a draft or starting a forward copies + // the file to disk, but does not make an entry in that array + if (is_array($_SESSION['plugins']['filesystem_attachments']['tmp_files'])){ + foreach ($_SESSION['plugins']['filesystem_attachments']['tmp_files'] as $filename){ + if(file_exists($filename)){ + unlink($filename); + } + } + unset($_SESSION['plugins']['filesystem_attachments']['tmp_files']); + } + return $args; + } +} diff --git a/plugins/help/config.inc.php.dist b/plugins/help/config.inc.php.dist new file mode 100644 index 0000000..6b27227 --- /dev/null +++ b/plugins/help/config.inc.php.dist @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/plugins/help/content/about.html b/plugins/help/content/about.html new file mode 100644 index 0000000..afaded6 --- /dev/null +++ b/plugins/help/content/about.html @@ -0,0 +1,37 @@ +
+

Copyright © 2005-2009, RoundCube Dev. - Switzerland

+ +

This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. +

+

+This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +

+

+You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +

+ +
+

Project management and administration

+Thomas Bruederli (thomasb) - Project leader and head developer
+Till Klampäckel (till) - Co-leader
+Brett Patterson - Forum administrator
+Adam Grelck - Trac administrator
+Jason Fesler - Mailing list administrator
+Brennan Stehling - Mentor, Coordinator + +

Developers

+Eric Stadtherr (estadtherr)
+Robin Elfrink (robin, wobin)
+Rich Sandberg (richs)
+Tomasz Pajor (tomekp)
+Fourat Zouari (fourat.zouari)
+Aleksander Machniak (alec) +
+
diff --git a/plugins/help/content/license.html b/plugins/help/content/license.html new file mode 100644 index 0000000..2d83c60 --- /dev/null +++ b/plugins/help/content/license.html @@ -0,0 +1,387 @@ +
+

GNU GENERAL PUBLIC LICENSE

+

+Version 2, June 1991 +

+ +
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.  
+51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+ +

Preamble

+ +

+The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. +

+ +

+When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. +

+ +

+To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. +

+ +

+For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. +

+ +

+We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. +

+ +

+Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. +

+ +

+Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. +

+ +

+ The precise terms and conditions for copying, distribution and +modification follow. +

+ + +

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

+ + +

+0. +This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". +

+ +

+Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. +

+ +

+1. +You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. +

+ +

+You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. +

+ +

+2. +You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: +

+ +
+
+
+ a) + You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. +
+
+
+ b) + You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. +
+
+
+ c) + If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) +
+
+ +

+These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. +

+ +

+Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. +

+ +

+In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. +

+ +

+3. +You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: +

+ +
+
+
+ a) + Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, +
+
+
+ b) + Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, +
+
+
+ c) + Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) +
+
+ +

+The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. +

+ +

+If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. +

+ +

+4. +You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. +

+ +

+5. +You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. +

+ +

+6. +Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. +

+ +

+7. +If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. +

+ +

+If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. +

+ +

+It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. +

+ +

+This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. +

+ +

+8. +If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. +

+ +

+9. +The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. +

+ +

+Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. +

+ +

+10. +If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. +

+ +

NO WARRANTY

+ +

+11. +BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. +

+ +

+12. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. +

+
diff --git a/plugins/help/help.php b/plugins/help/help.php new file mode 100644 index 0000000..c02b7e9 --- /dev/null +++ b/plugins/help/help.php @@ -0,0 +1,91 @@ +add_texts('localization/', false); + + // register actions + $this->register_action('plugin.help', array($this, 'action')); + $this->register_action('plugin.helpabout', array($this, 'action')); + $this->register_action('plugin.helplicense', array($this, 'action')); + + // add taskbar button + $this->add_button(array( + 'name' => 'helptask', + 'class' => 'button-help', + 'label' => 'help.help', + 'href' => './?_task=dummy&_action=plugin.help', + ), 'taskbar'); + + $skin = rcmail::get_instance()->config->get('skin'); + if (!file_exists($this->home."/skins/$skin/help.css")) + $skin = 'default'; + + // add style for taskbar button (must be here) and Help UI + $this->include_stylesheet("skins/$skin/help.css"); + } + + function action() + { + $rcmail = rcmail::get_instance(); + + $this->load_config(); + + // register UI objects + $rcmail->output->add_handlers(array( + 'helpcontent' => array($this, 'content'), + )); + + if ($rcmail->action == 'plugin.helpabout') + $rcmail->output->set_pagetitle($this->gettext('about')); + else if ($rcmail->action == 'plugin.helplicense') + $rcmail->output->set_pagetitle($this->gettext('license')); + else + $rcmail->output->set_pagetitle($this->gettext('help')); + + $rcmail->output->send('help.help'); + } + + function content($attrib) + { + $rcmail = rcmail::get_instance(); + + if ($rcmail->action == 'plugin.helpabout') { + return @file_get_contents($this->home.'/content/about.html'); + } + else if ($rcmail->action == 'plugin.helplicense') { + return @file_get_contents($this->home.'/content/license.html'); + } + + // default content: iframe + + if ($src = $rcmail->config->get('help_source')) + $attrib['src'] = $src; + + if (empty($attrib['id'])) + $attrib['id'] = 'rcmailhelpcontent'; + + // allow the following attributes to be added to the '."\n", $framename, $attrib_str); + + return $out; + } + +} + +?> diff --git a/plugins/help/localization/en_GB.inc b/plugins/help/localization/en_GB.inc new file mode 100644 index 0000000..8c2d151 --- /dev/null +++ b/plugins/help/localization/en_GB.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/help/localization/en_US.inc b/plugins/help/localization/en_US.inc new file mode 100644 index 0000000..8c2d151 --- /dev/null +++ b/plugins/help/localization/en_US.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/help/localization/et_EE.inc b/plugins/help/localization/et_EE.inc new file mode 100644 index 0000000..f95f098 --- /dev/null +++ b/plugins/help/localization/et_EE.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/help/localization/pl_PL.inc b/plugins/help/localization/pl_PL.inc new file mode 100644 index 0000000..087bc07 --- /dev/null +++ b/plugins/help/localization/pl_PL.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/help/localization/sv_SE.inc b/plugins/help/localization/sv_SE.inc new file mode 100644 index 0000000..8b0d487 --- /dev/null +++ b/plugins/help/localization/sv_SE.inc @@ -0,0 +1,8 @@ + diff --git a/plugins/help/skins/default/help.css b/plugins/help/skins/default/help.css new file mode 100644 index 0000000..7ccc671 --- /dev/null +++ b/plugins/help/skins/default/help.css @@ -0,0 +1,26 @@ +/***** RoundCube|Mail Help task styles *****/ + +#taskbar a.button-help +{ + background-image: url('help.gif'); +} + +#help-box +{ + position: absolute; + bottom: 30px; + top: 95px; + left: 20px; + right: 20px; + border: 1px solid #999999; + overflow: auto; + background-color: #F2F2F2; + /* IE hack */ + height: expression((parseInt(document.documentElement.clientHeight)-125)+'px'); + width: expression((parseInt(document.documentElement.clientWight)-40)+'px'); +} + +#helplicense, #helpabout +{ + padding: 20px; +} diff --git a/plugins/help/skins/default/help.gif b/plugins/help/skins/default/help.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ae90320c9b00d1cde9140965c479a5da1b2c4ab GIT binary patch literal 1146 zcmZ?wbhEHblwgoxc>a)qgN0d`gH1p{KvqaVho8fQpTkgqOF}~2T#!>oM8HZ?NI^l~ zOPt3_TGT{N(oj)0SdvdoRYhH0)kaB1UrR$*M>|?kR9{y^PfyR#z`)p0J3vd(#7L(? zMJPo}E?8gP+{~QTz$#;hEiVc zcBxM0KAsNCjigo>ORq7OUTP-q@8dPaM$6yNCosTgr@8D_3;B>h_u!y_1D0|TAzp{9 z+Q`-s_@s#a{7> zy;5Rq$TbJ0>kdkn9aSa`$=QWh)*H+}!RTcD><<(ae z)K*tCR2MbW6s;{xX{swZmK)jHP=25|xxK04UQTFxb7gBwLu*TOXM0Oed+mdw=+4g0 z$$c$TC$vwTIC1)vwG1a%kJ8%eyum zKCt`zsiRj;9(j3W_qj92ADldJ>B6~77tcI9cjC(B3s){*ym{r^_3PJf-MDi5#^t*= zuid_N^X{#y?{8gtasT#=MP`LeE9MG>#rZ5{`~s&=l734zkmJx^ZVbwKmY#y{rB(R|NlTlL!kJR zg^`QlKZ6bf5P9}xprfjpBVMuBL_d&ra4Zgq4&WbOo zU(CSy^Lo4BL2c&1o`+v`=a}c+nsu_uaRa}jH`D41otHbLOj6Izk=?AE+qR7>-*aTFAFVL&~CCi%eJfFL(q=wT{~oYw&jGYaP}UKe0}t}f<@X64qXe2 z<yx@W@_Jx91F3*qRv|T#T(mDn953AN12>t2(%t z^Qv~lQVu3YMrNK64#$N8PukfO?ijG}tA%`M=VI{;5>T3y@X5`otH5Z2G>_sF57o8< N0*^X1)I + + +<roundcube:object name="pagetitle" /> + + + + + + + + + + +
+ + + + + +
+ +
+ +
+ + + diff --git a/plugins/http_authentication/http_authentication.php b/plugins/http_authentication/http_authentication.php new file mode 100644 index 0000000..57422a7 --- /dev/null +++ b/plugins/http_authentication/http_authentication.php @@ -0,0 +1,41 @@ +add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + } + + function startup($args) + { + // change action to login + if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) + && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) + $args['action'] = 'login'; + + return $args; + } + + function authenticate($args) + { + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $args['user'] = $_SERVER['PHP_AUTH_USER']; + $args['pass'] = $_SERVER['PHP_AUTH_PW']; + } + + return $args; + } + +} + diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog new file mode 100644 index 0000000..358836f --- /dev/null +++ b/plugins/managesieve/Changelog @@ -0,0 +1,79 @@ +* version 1.4 [2009-07-29] +----------------------------------------------------------- +- Updated PEAR::Net_Sieve to 1.1.7 + +* version 1.3 [2009-07-24] +----------------------------------------------------------- +- support more languages +- support config.inc.php file + +* version 1.2 [2009-06-28] +----------------------------------------------------------- +- Support IMAP namespaces in fileinto (#1485943) +- Added it_IT localization + +* version 1.1 [2009-05-27] +----------------------------------------------------------- +- Added new icons +- Added support for headers lists (coma-separated) in rules +- Added de_CH localization + +* version 1.0 [2009-05-21] +----------------------------------------------------------- +- Rewritten using plugin API +- Added hu_HU localization (Tamas Tevesz) + +* version beta7 (svn-r2300) [2009-03-01] +----------------------------------------------------------- +- Added SquirrelMail script auto-import (Jonathan Ernst) +- Added 'vacation' support (Jonathan Ernst & alec) +- Added 'stop' support (Jonathan Ernst) +- Added option for extensions disabling (Jonathan Ernst & alec) +- Added fi_FI, nl_NL, bg_BG localization +- Small style fixes + +* version 0.2-stable1 (svn-r2205) [2009-01-03] +----------------------------------------------------------- +- Fix moving down filter row +- Fixes for compressed js files in stable release package +- Created patch for svn version r2205 + +* version 0.2-stable [2008-12-31] +----------------------------------------------------------- +- Added ru_RU, fr_FR, zh_CN translation +- Fixes for Roundcube 0.2-stable + +* version rc0.2beta [2008-09-21] +----------------------------------------------------------- +- Small css fixes for IE +- Fixes for Roundcube 0.2-beta + +* version beta6 [2008-08-08] +----------------------------------------------------------- +- Added de_DE translation +- Fix for Roundcube r1634 + +* version beta5 [2008-06-10] +----------------------------------------------------------- +- Fixed 'exists' operators +- Fixed 'not*' operators for custom headers +- Fixed filters deleting + +* version beta4 [2008-06-09] +----------------------------------------------------------- +- Fix for Roundcube r1490 + +* version beta3 [2008-05-22] +----------------------------------------------------------- +- Fixed textarea error class setting +- Added pagetitle setting +- Added option 'managesieve_replace_delimiter' +- Fixed errors on IE (still need some css fixes) + +* version beta2 [2008-05-20] +----------------------------------------------------------- +- Use 'if' only for first filter and 'elsif' for the rest + +* version beta1 [2008-05-15] +----------------------------------------------------------- +- Initial version for Roundcube r1388. diff --git a/plugins/managesieve/config.inc.php.dist b/plugins/managesieve/config.inc.php.dist new file mode 100644 index 0000000..d8e949a --- /dev/null +++ b/plugins/managesieve/config.inc.php.dist @@ -0,0 +1,28 @@ + diff --git a/plugins/managesieve/lib/Net/Sieve.php b/plugins/managesieve/lib/Net/Sieve.php new file mode 100644 index 0000000..072905d --- /dev/null +++ b/plugins/managesieve/lib/Net/Sieve.php @@ -0,0 +1,1188 @@ + | +// | Co-Author: Damian Fernandez Sosa | +// | Co-Author: Anish Mistry | +// +-----------------------------------------------------------------------+ + +require_once('Net/Socket.php'); + +/** +* TODO +* +* o supportsAuthMech() +*/ + +/** +* Disconnected state +* @const NET_SIEVE_STATE_DISCONNECTED +*/ +define('NET_SIEVE_STATE_DISCONNECTED', 1, true); + +/** +* Authorisation state +* @const NET_SIEVE_STATE_AUTHORISATION +*/ +define('NET_SIEVE_STATE_AUTHORISATION', 2, true); + +/** +* Transaction state +* @const NET_SIEVE_STATE_TRANSACTION +*/ +define('NET_SIEVE_STATE_TRANSACTION', 3, true); + +/** +* A class for talking to the timsieved server which +* comes with Cyrus IMAP. +* +* SIEVE: RFC3028 http://www.ietf.org/rfc/rfc3028.txt +* MANAGE-SIEVE: http://www.ietf.org/internet-drafts/draft-martin-managesieve-07.txt +* +* @author Richard Heyes +* @author Damian Fernandez Sosa +* @author Anish Mistry +* @access public +* @version 1.2.0 +* @package Net_Sieve +*/ + +class Net_Sieve +{ + /** + * The socket object + * @var object + */ + var $_sock; + + /** + * Info about the connect + * @var array + */ + var $_data; + + /** + * Current state of the connection + * @var integer + */ + var $_state; + + /** + * Constructor error is any + * @var object + */ + var $_error; + + /** + * To allow class debuging + * @var boolean + */ + var $_debug = false; + + /** + * Allows picking up of an already established connection + * @var boolean + */ + var $_bypassAuth = false; + + /** + * Whether to use TLS if available + * @var boolean + */ + var $_useTLS = true; + + /** + * Additional options for stream_context_create() + * @var array + */ + var $_options = null; + + /** + * The auth methods this class support + * @var array + */ + var $supportedAuthMethods=array('DIGEST-MD5', 'CRAM-MD5', 'EXTERNAL', 'PLAIN' , 'LOGIN'); + //if you have problems using DIGEST-MD5 authentication please comment the line above and uncomment the following line + //var $supportedAuthMethods=array( 'CRAM-MD5', 'PLAIN' , 'LOGIN'); + + //var $supportedAuthMethods=array( 'PLAIN' , 'LOGIN'); + + /** + * The auth methods this class support + * @var array + */ + var $supportedSASLAuthMethods=array('DIGEST-MD5', 'CRAM-MD5'); + + /** + * Handles posible referral loops + * @var array + */ + var $_maxReferralCount = 15; + + /** + * Constructor + * Sets up the object, connects to the server and logs in. stores + * any generated error in $this->_error, which can be retrieved + * using the getError() method. + * + * @param string $user Login username + * @param string $pass Login password + * @param string $host Hostname of server + * @param string $port Port of server + * @param string $logintype Type of login to perform + * @param string $euser Effective User (if $user=admin, login as $euser) + * @param string $bypassAuth Skip the authentication phase. Useful if the socket + is already open. + * @param boolean $useTLS Use TLS if available + * @param array $options options for stream_context_create() + */ + function Net_Sieve($user = null , $pass = null , $host = 'localhost', $port = 2000, $logintype = '', $euser = '', $debug = false, $bypassAuth = false, $useTLS = true, $options = null) + { + $this->_state = NET_SIEVE_STATE_DISCONNECTED; + $this->_data['user'] = $user; + $this->_data['pass'] = $pass; + $this->_data['host'] = $host; + $this->_data['port'] = $port; + $this->_data['logintype'] = $logintype; + $this->_data['euser'] = $euser; + $this->_sock = &new Net_Socket(); + $this->_debug = $debug; + $this->_bypassAuth = $bypassAuth; + $this->_useTLS = $useTLS; + $this->_options = $options; + /* + * Include the Auth_SASL package. If the package is not available, + * we disable the authentication methods that depend upon it. + */ + if ((@include_once 'Auth/SASL.php') === false) { + if($this->_debug){ + echo "AUTH_SASL NOT PRESENT!\n"; + } + foreach($this->supportedSASLAuthMethods as $SASLMethod){ + $pos = array_search( $SASLMethod, $this->supportedAuthMethods ); + if($this->_debug){ + echo "DISABLING METHOD $SASLMethod\n"; + } + unset($this->supportedAuthMethods[$pos]); + } + } + if( ($user != null) && ($pass != null) ){ + $this->_error = $this->_handleConnectAndLogin(); + } + } + + /** + * Handles the errors the class can find + * on the server + * + * @access private + * @param mixed $msg Text error message or PEAR error object + * @param integer $code Numeric error code + * @return PEAR_Error + */ + function _raiseError($msg, $code) + { + include_once 'PEAR.php'; + return PEAR::raiseError($msg, $code); + } + + /** + * Handles connect and login. + * on the server + * + * @access private + * @return mixed Indexed array of scriptnames or PEAR_Error on failure + */ + function _handleConnectAndLogin() + { + if (PEAR::isError($res = $this->connect($this->_data['host'] , $this->_data['port'], $this->_options, $this->_useTLS ))) { + return $res; + } + if($this->_bypassAuth === false) { + if (PEAR::isError($res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'] , $this->_data['euser'] , $this->_bypassAuth) ) ) { + return $res; + } + } + return true; + } + + /** + * Returns an indexed array of scripts currently + * on the server + * + * @return mixed Indexed array of scriptnames or PEAR_Error on failure + */ + function listScripts() + { + if (is_array($scripts = $this->_cmdListScripts())) { + $this->_active = $scripts[1]; + return $scripts[0]; + } else { + return $scripts; + } + } + + /** + * Returns the active script + * + * @return mixed The active scriptname or PEAR_Error on failure + */ + function getActive() + { + if (!empty($this->_active)) { + return $this->_active; + + } elseif (is_array($scripts = $this->_cmdListScripts())) { + $this->_active = $scripts[1]; + return $scripts[1]; + } + } + + /** + * Sets the active script + * + * @param string $scriptname The name of the script to be set as active + * @return mixed true on success, PEAR_Error on failure + */ + function setActive($scriptname) + { + return $this->_cmdSetActive($scriptname); + } + + /** + * Retrieves a script + * + * @param string $scriptname The name of the script to be retrieved + * @return mixed The script on success, PEAR_Error on failure + */ + function getScript($scriptname) + { + return $this->_cmdGetScript($scriptname); + } + + /** + * Adds a script to the server + * + * @param string $scriptname Name of the script + * @param string $script The script + * @param boolean $makeactive Whether to make this the active script + * @return mixed true on success, PEAR_Error on failure + */ + function installScript($scriptname, $script, $makeactive = false) + { + if (PEAR::isError($res = $this->_cmdPutScript($scriptname, $script))) { + return $res; + + } elseif ($makeactive) { + return $this->_cmdSetActive($scriptname); + + } else { + return true; + } + } + + /** + * Removes a script from the server + * + * @param string $scriptname Name of the script + * @return mixed True on success, PEAR_Error on failure + */ + function removeScript($scriptname) + { + return $this->_cmdDeleteScript($scriptname); + } + + /** + * Returns any error that may have been generated in the + * constructor + * + * @return mixed False if no error, PEAR_Error otherwise + */ + function getError() + { + return PEAR::isError($this->_error) ? $this->_error : false; + } + + /** + * Handles connecting to the server and checking the + * response is valid. + * + * @access private + * @param string $host Hostname of server + * @param string $port Port of server + * @param array $options List of options to pass to connect + * @param boolean $useTLS Use TLS if available + * @return mixed True on success, PEAR_Error otherwise + */ + function connect($host, $port, $options = null, $useTLS = true) + { + if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) { + $msg='Not currently in DISCONNECTED state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if (PEAR::isError($res = $this->_sock->connect($host, $port, false, 5, $options))) { + return $res; + } + + if($this->_bypassAuth === false) { + $this->_state = NET_SIEVE_STATE_AUTHORISATION; + if (PEAR::isError($res = $this->_doCmd())) { + return $res; + } + } else { + $this->_state = NET_SIEVE_STATE_TRANSACTION; + } + + // Explicitly ask for the capabilities in case the connection + // is picked up from an existing connection. + if(PEAR::isError($res = $this->_cmdCapability() )) { + $msg='Failed to connect, server said: ' . $res->getMessage(); + $code=2; + return $this->_raiseError($msg,$code); + } + + if($useTLS === true) { + // check if we can enable TLS via STARTTLS + if(isset($this->_capability['starttls']) && function_exists('stream_socket_enable_crypto') === true) { + if (PEAR::isError($res = $this->_startTLS())) { + return $res; + } + } + } + + return true; + } + + /** + * Logs into server. + * + * @param string $user Login username + * @param string $pass Login password + * @param string $logintype Type of login method to use + * @param string $euser Effective UID (perform on behalf of $euser) + * @param boolean $bypassAuth Do not perform authentication + * @return mixed True on success, PEAR_Error otherwise + */ + function login($user, $pass, $logintype = null , $euser = '', $bypassAuth = false) + { + if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) { + $msg='Not currently in AUTHORISATION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if( $bypassAuth === false ){ + if(PEAR::isError($res=$this->_cmdAuthenticate($user , $pass , $logintype, $euser ) ) ){ + return $res; + } + } + $this->_state = NET_SIEVE_STATE_TRANSACTION; + return true; + } + + /** + * Handles the authentication using any known method + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $userMethod The method to use ( if $userMethod == '' then the class chooses the best method (the stronger is the best ) ) + * @param string $euser The effective uid to authenticate as. + * + * @return mixed string or PEAR_Error + * + * @access private + * @since 1.0 + */ + function _cmdAuthenticate($uid , $pwd , $userMethod = null , $euser = '' ) + { + if ( PEAR::isError( $method = $this->_getBestAuthMethod($userMethod) ) ) { + return $method; + } + switch ($method) { + case 'DIGEST-MD5': + $result = $this->_authDigest_MD5( $uid , $pwd , $euser ); + return $result; + break; + case 'CRAM-MD5': + $result = $this->_authCRAM_MD5( $uid , $pwd, $euser); + break; + case 'LOGIN': + $result = $this->_authLOGIN( $uid , $pwd , $euser ); + break; + case 'PLAIN': + $result = $this->_authPLAIN( $uid , $pwd , $euser ); + break; + case 'EXTERNAL': + $result = $this->_authEXTERNAL( $uid , $pwd , $euser ); + break; + default : + $result = new PEAR_Error( "$method is not a supported authentication method" ); + break; + } + + if (PEAR::isError($res = $this->_doCmd() )) { + return $res; + } + return $result; + } + + /** + * Authenticates the user using the PLAIN method. + * + * @param string $user The userid to authenticate as. + * @param string $pass The password to authenticate with. + * @param string $euser The effective uid to authenticate as. + * + * @return array Returns an array containing the response + * + * @access private + * @since 1.0 + */ + function _authPLAIN($user, $pass , $euser ) + { + if ($euser != '') { + $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode($euser . chr(0) . $user . chr(0) . $pass ) ) ; + } else { + $cmd=sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode( chr(0) . $user . chr(0) . $pass ) ); + } + return $this->_sendCmd( $cmd ) ; + } + + /** + * Authenticates the user using the PLAIN method. + * + * @param string $user The userid to authenticate as. + * @param string $pass The password to authenticate with. + * @param string $euser The effective uid to authenticate as. + * + * @return array Returns an array containing the response + * + * @access private + * @since 1.0 + */ + function _authLOGIN($user, $pass , $euser ) + { + $this->_sendCmd('AUTHENTICATE "LOGIN"'); + $this->_doCmd(sprintf('"%s"', base64_encode($user))); + $this->_doCmd(sprintf('"%s"', base64_encode($pass))); + } + + /** + * Authenticates the user using the CRAM-MD5 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $euser The effective uid to authenticate as. + * + * @return array Returns an array containing the response + * + * @access private + * @since 1.0 + */ + function _authCRAM_MD5($uid, $pwd, $euser) + { + if ( PEAR::isError( $challenge = $this->_doCmd( 'AUTHENTICATE "CRAM-MD5"' ) ) ) { + $this->_error=$challenge; + return $challenge; + } + $challenge=trim($challenge); + $challenge = base64_decode( trim($challenge) ); + $cram = &Auth_SASL::factory('crammd5'); + if ( PEAR::isError($resp=$cram->getResponse( $uid , $pwd , $challenge ) ) ) { + $this->_error=$resp; + return $resp; + } + $auth_str = base64_encode( $resp ); + if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str ) ) ) { + $this->_error=$error; + return $error; + } + + } + + /** + * Authenticates the user using the DIGEST-MD5 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $euser The effective uid to authenticate as. + * + * @return array Returns an array containing the response + * + * @access private + * @since 1.0 + */ + function _authDigest_MD5($uid, $pwd, $euser) + { + if ( PEAR::isError( $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"') ) ) { + $this->_error= $challenge; + return $challenge; + } + $challenge = base64_decode( $challenge ); + $digest = &Auth_SASL::factory('digestmd5'); + + if(PEAR::isError($param=$digest->getResponse($uid, $pwd, $challenge, "localhost", "sieve" , $euser) )) { + return $param; + } + $auth_str = base64_encode($param); + + if ( PEAR::isError($error = $this->_sendStringResponse( $auth_str ) ) ) { + $this->_error=$error; + return $error; + } + + if ( PEAR::isError( $challenge = $this->_doCmd() ) ) { + $this->_error=$challenge ; + return $challenge ; + } + + if( strtoupper(substr($challenge,0,2))== 'OK' ){ + return true; + } + + /** + * We don't use the protocol's third step because SIEVE doesn't allow + * subsequent authentication, so we just silently ignore it. + */ + if ( PEAR::isError($error = $this->_sendStringResponse( '' ) ) ) { + $this->_error=$error; + return $error; + } + + if (PEAR::isError($res = $this->_doCmd() )) { + return $res; + } + } + + /** + * Authenticates the user using the EXTERNAL method. + * + * @param string $user The userid to authenticate as. + * @param string $pass The password to authenticate with. + * @param string $euser The effective uid to authenticate as. + * + * @return array Returns an array containing the response + * + * @access private + * @since 1.1.7 + */ + function _authEXTERNAL($user, $pass, $euser) + { + if ($euser != '') { + $cmd=sprintf('AUTHENTICATE "EXTERNAL" "%s"', base64_encode($euser) ) ; + } else { + $cmd=sprintf('AUTHENTICATE "EXTERNAL" "%s"', base64_encode($user) ); + } + return $this->_sendCmd( $cmd ) ; + } + + /** + * Removes a script from the server + * + * @access private + * @param string $scriptname Name of the script to delete + * @return mixed True on success, PEAR_Error otherwise + */ + function _cmdDeleteScript($scriptname) + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in AUTHORISATION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname) ) )) { + return $res; + } + return true; + } + + /** + * Retrieves the contents of the named script + * + * @access private + * @param string $scriptname Name of the script to retrieve + * @return mixed The script if successful, PEAR_Error otherwise + */ + function _cmdGetScript($scriptname) + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in AUTHORISATION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname) ) ) ) { + return $res; + } + + return preg_replace('/{[0-9]+}\r\n/', '', $res); + } + + /** + * Sets the ACTIVE script, ie the one that gets run on new mail + * by the server + * + * @access private + * @param string $scriptname The name of the script to mark as active + * @return mixed True on success, PEAR_Error otherwise + */ + function _cmdSetActive($scriptname) + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in AUTHORISATION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname) ) ) ) { + return $res; + } + + $this->_activeScript = $scriptname; + return true; + } + + /** + * Sends the LISTSCRIPTS command + * + * @access private + * @return mixed Two item array of scripts, and active script on success, + * PEAR_Error otherwise. + */ + function _cmdListScripts() + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in AUTHORISATION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + $scripts = array(); + $activescript = null; + + if (PEAR::isError($res = $this->_doCmd('LISTSCRIPTS'))) { + return $res; + } + + $res = explode("\r\n", $res); + + foreach ($res as $value) { + if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) { + $scripts[] = $matches[1]; + if (!empty($matches[2])) { + $activescript = $matches[1]; + } + } + } + + return array($scripts, $activescript); + } + + /** + * Sends the PUTSCRIPT command to add a script to + * the server. + * + * @access private + * @param string $scriptname Name of the new script + * @param string $scriptdata The new script + * @return mixed True on success, PEAR_Error otherwise + */ + function _cmdPutScript($scriptname, $scriptdata) + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in TRANSACTION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + $stringLength = $this->_getLineLength($scriptdata); + + if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, $stringLength, $scriptdata) ))) { + return $res; + } + + return true; + } + + /** + * Sends the LOGOUT command and terminates the connection + * + * @access private + * @param boolean $sendLogoutCMD True to send LOGOUT command before disconnecting + * @return mixed True on success, PEAR_Error otherwise + */ + function _cmdLogout($sendLogoutCMD=true) + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=1; + return $this->_raiseError($msg,$code); + //return PEAR::raiseError('Not currently connected'); + } + + if($sendLogoutCMD){ + if (PEAR::isError($res = $this->_doCmd('LOGOUT'))) { + return $res; + } + } + + $this->_sock->disconnect(); + $this->_state = NET_SIEVE_STATE_DISCONNECTED; + return true; + } + + /** + * Sends the CAPABILITY command + * + * @access private + * @return mixed True on success, PEAR_Error otherwise + */ + function _cmdCapability() + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if (PEAR::isError($res = $this->_doCmd('CAPABILITY'))) { + return $res; + } + $this->_parseCapability($res); + return true; + } + + /** + * Checks if the server has space to store the script + * by the server + * + * @param string $scriptname The name of the script to mark as active + * @param integer $size The size of the script + * @return mixed True on success, PEAR_Error otherwise + */ + function haveSpace($scriptname,$size) + { + if (NET_SIEVE_STATE_TRANSACTION != $this->_state) { + $msg='Not currently in TRANSACTION state'; + $code=1; + return $this->_raiseError($msg,$code); + } + + if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %d', $scriptname, $size) ) ) ) { + return $res; + } + + return true; + } + + /** + * Parses the response from the capability command. Stores + * the result in $this->_capability + * + * @access private + * @param string $data The response from the capability command + */ + function _parseCapability($data) + { + // clear the cached capabilities + $this->_capability = array(); + + $data = preg_split('/\r?\n/', $data, -1, PREG_SPLIT_NO_EMPTY); + + for ($i = 0; $i < count($data); $i++) { + if (preg_match('/^"([a-z]+)"( "(.*)")?$/i', $data[$i], $matches)) { + switch (strtolower($matches[1])) { + case 'implementation': + $this->_capability['implementation'] = $matches[3]; + break; + + case 'sasl': + $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]); + break; + + case 'sieve': + $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]); + break; + + case 'starttls': + $this->_capability['starttls'] = true; + break; + } + } + } + } + + /** + * Sends a command to the server + * + * @access private + * @param string $cmd The command to send + */ + function _sendCmd($cmd) + { + $status = $this->_sock->getStatus(); + if (PEAR::isError($status) || $status['eof']) { + return new PEAR_Error( 'Failed to write to socket: (connection lost!) ' ); + } + if ( PEAR::isError( $error = $this->_sock->write( $cmd . "\r\n" ) ) ) { + return new PEAR_Error( 'Failed to write to socket: ' . $error->getMessage() ); + } + + if( $this->_debug ){ + // C: means this data was sent by the client (this class) + echo "C:$cmd\n"; + } + return true; + } + + /** + * Sends a string response to the server + * + * @access private + * @param string $cmd The command to send + */ + function _sendStringResponse($str) + { + $response='{' . $this->_getLineLength($str) . "+}\r\n" . $str ; + return $this->_sendCmd($response); + } + + function _recvLn() + { + $lastline=''; + if (PEAR::isError( $lastline = $this->_sock->gets( 8192 ) ) ) { + return new PEAR_Error( 'Failed to write to socket: ' . $lastline->getMessage() ); + } + $lastline=rtrim($lastline); + if($this->_debug){ + // S: means this data was sent by the IMAP Server + echo "S:$lastline\n" ; + } + + if( $lastline === '' ) { + return new PEAR_Error( 'Failed to receive from the socket' ); + } + + return $lastline; + } + + /** + * Send a command and retrieves a response from the server. + * + * + * @access private + * @param string $cmd The command to send + * @return mixed Reponse string if an OK response, PEAR_Error if a NO response + */ + function _doCmd($cmd = '' ) + { + $referralCount=0; + while($referralCount < $this->_maxReferralCount ){ + + if($cmd != '' ){ + if(PEAR::isError($error = $this->_sendCmd($cmd) )) { + return $error; + } + } + $response = ''; + + while (true) { + if(PEAR::isError( $line=$this->_recvLn() )){ + return $line; + } + if ('ok' === strtolower(substr($line, 0, 2))) { + $response .= $line; + return rtrim($response); + + } elseif ('no' === strtolower(substr($line, 0, 2))) { + // Check for string literal error message + if (preg_match('/^no {([0-9]+)\+?}/i', $line, $matches)) { + $line .= str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 )); + if($this->_debug){ + echo "S:$line\n"; + } + } + $msg=trim($response . substr($line, 2)); + $code=3; + return $this->_raiseError($msg,$code); + } elseif ('bye' === strtolower(substr($line, 0, 3))) { + + if(PEAR::isError($error = $this->disconnect(false) ) ){ + $msg="Can't handle bye, The error was= " . $error->getMessage() ; + $code=4; + return $this->_raiseError($msg,$code); + } + //if (preg_match('/^bye \(referral "([^"]+)/i', $line, $matches)) { + if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) { + // Check for referral, then follow it. Otherwise, carp an error. + // Replace the old host with the referral host preserving any protocol prefix + $this->_data['host'] = preg_replace('/\w+(?!(\w|\:\/\/)).*/',$matches[2],$this->_data['host']); + if (PEAR::isError($error = $this->_handleConnectAndLogin() ) ){ + $msg="Can't follow referral to " . $this->_data['host'] . ", The error was= " . $error->getMessage() ; + $code=5; + return $this->_raiseError($msg,$code); + } + break; + // Retry the command + if(PEAR::isError($error = $this->_sendCmd($cmd) )) { + return $error; + } + continue; + } + $msg=trim($response . $line); + $code=6; + return $this->_raiseError($msg,$code); + } elseif (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) { + // Matches String Responses. + //$line = str_replace("\r\n", ' ', $this->_sock->read($matches[1] + 2 )); + $str_size = $matches[1] + 2; + $line = ''; + $line_length = 0; + while ($line_length < $str_size) { + $line .= $this->_sock->read($str_size - $line_length); + $line_length = $this->_getLineLength($line); + } + if($this->_debug){ + echo "S:$line\n"; + } + if($this->_state != NET_SIEVE_STATE_AUTHORISATION) { + // receive the pending OK only if we aren't authenticating + // since string responses during authentication don't need an + // OK. + $this->_recvLn(); + } + return $line; + } + $response .= $line . "\r\n"; + $referralCount++; + } + } + $msg="Max referral count reached ($referralCount times) Cyrus murder loop error?"; + $code=7; + return $this->_raiseError($msg,$code); + } + + /** + * Sets the debug state + * + * @param boolean $debug + * @return void + */ + function setDebug($debug = true) + { + $this->_debug = $debug; + } + + /** + * Disconnect from the Sieve server + * + * @param string $scriptname The name of the script to be set as active + * @return mixed true on success, PEAR_Error on failure + */ + function disconnect($sendLogoutCMD=true) + { + return $this->_cmdLogout($sendLogoutCMD); + } + + /** + * Returns the name of the best authentication method that the server + * has advertised. + * + * @param string if !=null,authenticate with this method ($userMethod). + * + * @return mixed Returns a string containing the name of the best + * supported authentication method or a PEAR_Error object + * if a failure condition is encountered. + * @access private + * @since 1.0 + */ + function _getBestAuthMethod($userMethod = null) + { + if( isset($this->_capability['sasl']) ){ + $serverMethods=$this->_capability['sasl']; + }else{ + // if the server don't send an sasl capability fallback to login auth + //return 'LOGIN'; + return new PEAR_Error("This server don't support any Auth methods SASL problem?"); + } + + if($userMethod != null ){ + $methods = array(); + $methods[] = $userMethod; + }else{ + + $methods = $this->supportedAuthMethods; + } + if( ($methods != null) && ($serverMethods != null)){ + foreach ( $methods as $method ) { + if ( in_array( $method , $serverMethods ) ) { + return $method; + } + } + $serverMethods=implode(',' , $serverMethods ); + $myMethods=implode(',' ,$this->supportedAuthMethods); + return new PEAR_Error("$method NOT supported authentication method!. This server " . + "supports these methods= $serverMethods, but I support $myMethods"); + }else{ + return new PEAR_Error("This server don't support any Auth methods"); + } + } + + /** + * Return the list of extensions the server supports + * + * @return mixed array on success, PEAR_Error on failure + */ + function getExtensions() + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=7; + return $this->_raiseError($msg,$code); + } + + return $this->_capability['extensions']; + } + + /** + * Return true if tyhe server has that extension + * + * @param string the extension to compare + * @return mixed array on success, PEAR_Error on failure + */ + function hasExtension($extension) + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=7; + return $this->_raiseError($msg,$code); + } + + if(is_array($this->_capability['extensions'] ) ){ + foreach( $this->_capability['extensions'] as $ext){ + if( trim( strtolower( $ext ) ) === trim( strtolower( $extension ) ) ) + return true; + } + } + return false; + } + + /** + * Return the list of auth methods the server supports + * + * @return mixed array on success, PEAR_Error on failure + */ + function getAuthMechs() + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=7; + return $this->_raiseError($msg,$code); + } + if(!isset($this->_capability['sasl']) ){ + $this->_capability['sasl']=array(); + } + return $this->_capability['sasl']; + } + + /** + * Return true if the server has that extension + * + * @param string the extension to compare + * @return mixed array on success, PEAR_Error on failure + */ + function hasAuthMech($method) + { + if (NET_SIEVE_STATE_DISCONNECTED === $this->_state) { + $msg='Not currently connected'; + $code=7; + return $this->_raiseError($msg,$code); + //return PEAR::raiseError('Not currently connected'); + } + + if(is_array($this->_capability['sasl'] ) ){ + foreach( $this->_capability['sasl'] as $ext){ + if( trim( strtolower( $ext ) ) === trim( strtolower( $method ) ) ) + return true; + } + } + return false; + } + + /** + * Return true if the TLS negotiation was successful + * + * @access private + * @return mixed true on success, PEAR_Error on failure + */ + function _startTLS() + { + if (PEAR::isError($res = $this->_doCmd("STARTTLS"))) { + return $res; + } + + if(stream_socket_enable_crypto($this->_sock->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT) == false) { + $msg='Failed to establish TLS connection'; + $code=2; + return $this->_raiseError($msg,$code); + } + + if($this->_debug === true) { + echo "STARTTLS Negotiation Successful\n"; + } + + // The server should be sending a CAPABILITY response after + // negotiating TLS. Read it, and ignore if it doesn't. + $this->_doCmd(); + + // RFC says we need to query the server capabilities again now that + // we are under encryption + if(PEAR::isError($res = $this->_cmdCapability() )) { + $msg='Failed to connect, server said: ' . $res->getMessage(); + $code=2; + return $this->_raiseError($msg,$code); + } + + return true; + } + + function _getLineLength($string) { + if (extension_loaded('mbstring') || @dl(PHP_SHLIB_PREFIX.'mbstring.'.PHP_SHLIB_SUFFIX)) { + return mb_strlen($string,'latin1'); + } else { + return strlen($string); + } + } +} +?> diff --git a/plugins/managesieve/lib/rcube_sieve.php b/plugins/managesieve/lib/rcube_sieve.php new file mode 100644 index 0000000..3bb150e --- /dev/null +++ b/plugins/managesieve/lib/rcube_sieve.php @@ -0,0 +1,727 @@ + + + $Id$ + +*/ + +// Sieve Language Basics: http://www.ietf.org/rfc/rfc5228.txt + +define('SIEVE_ERROR_CONNECTION', 1); +define('SIEVE_ERROR_LOGIN', 2); +define('SIEVE_ERROR_NOT_EXISTS', 3); // script not exists +define('SIEVE_ERROR_INSTALL', 4); // script installation +define('SIEVE_ERROR_ACTIVATE', 5); // script activation +define('SIEVE_ERROR_OTHER', 255); // other/unknown error + + +class rcube_sieve +{ + var $sieve; // Net_Sieve object + var $error = false; // error flag + var $list = array(); // scripts list + + public $script; // rcube_sieve_script object + private $disabled; // array of disabled extensions + + /** + * Object constructor + * + * @param string Username (to managesieve login) + * @param string Password (to managesieve login) + * @param string Managesieve server hostname/address + * @param string Managesieve server port number + * @param string Enable/disable TLS use + * @param array Disabled extensions + */ + public function __construct($username, $password='', $host='localhost', $port=2000, $usetls=true, $disabled=array()) + { + $this->sieve = new Net_Sieve(); + +// $this->sieve->setDebug(); + if (PEAR::isError($this->sieve->connect($host, $port, NULL, $usetls))) + return $this->_set_error(SIEVE_ERROR_CONNECTION); + + if (PEAR::isError($this->sieve->login($username, $password))) + return $this->_set_error(SIEVE_ERROR_LOGIN); + + $this->disabled = $disabled; + $this->_get_script(); + } + + /** + * Getter for error code + */ + public function error() + { + return $this->error ? $this->error : false; + } + + public function save() + { + $script = $this->script->as_text(); + + if (!$script) + $script = '/* empty script */'; + + if (PEAR::isError($this->sieve->installScript('roundcube', $script))) + return $this->_set_error(SIEVE_ERROR_INSTALL); + + if (PEAR::isError($this->sieve->setActive('roundcube'))) + return $this->_set_error(SIEVE_ERROR_ACTIVATE); + + return true; + } + + public function get_extensions() + { + if ($this->sieve) { + $ext = $this->sieve->getExtensions(); + + if ($this->script) { + $supported = $this->script->get_extensions(); + foreach ($ext as $idx => $ext_name) + if (!in_array($ext_name, $supported)) + unset($ext[$idx]); + } + + return array_values($ext); + } + } + + private function _get_script() + { + if (!$this->sieve) + return false; + + $this->list = $this->sieve->listScripts(); + + if (PEAR::isError($this->list)) + return $this->_set_error(SIEVE_ERROR_OTHER); + + if (in_array('roundcube', $this->list)) + { + $script = $this->sieve->getScript('roundcube'); + + if (PEAR::isError($script)) + return $this->_set_error(SIEVE_ERROR_OTHER); + } + // import scripts from squirrelmail + elseif (in_array('phpscript', $this->list)) + { + $script = $this->sieve->getScript('phpscript'); + + $script = $this->_convert_from_squirrel_rules($script); + + $this->script = new rcube_sieve_script($script); + + $this->save(); + + $script = $this->sieve->getScript('roundcube'); + + if (PEAR::isError($script)) + return $this->_set_error(SIEVE_ERROR_OTHER); + } + else + { + $this->_set_error(SIEVE_ERROR_NOT_EXISTS); + $script = ''; + } + + $this->script = new rcube_sieve_script($script, $this->disabled); + } + + private function _convert_from_squirrel_rules($script) + { + $i = 0; + $name = array(); + // tokenize rules + if ($tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) + foreach($tokens as $token) + { + if (preg_match('/^#START_SIEVE_RULE.*/', $token, $matches)) + { + $name[$i] = "unnamed rule ".($i+1); + $content .= "# rule:[".$name[$i]."]\n"; + } + elseif (isset($name[$i])) + { + $content .= "if ".$token."\n"; + $i++; + } + } + + return $content; + } + + + private function _set_error($error) + { + $this->error = $error; + return false; + } +} + +class rcube_sieve_script +{ + var $content = array(); // script rules array + + private $supported = array( // extensions supported by class + 'fileinto', + 'reject', + 'ereject', + 'vacation', // RFC5230 + // TODO: (most wanted first) body, imapflags, notify, regex + ); + + /** + * Object constructor + * + * @param string Script's text content + * @param array Disabled extensions + */ + public function __construct($script, $disabled) + { + if (!empty($disabled)) + foreach ($disabled as $ext) + if (($idx = array_search($ext, $this->supported)) !== false) + unset($this->supported[$idx]); + + $this->content = $this->_parse_text($script); + } + + /** + * Adds script contents as text to the script array (at the end) + * + * @param string Text script contents + */ + public function add_text($script) + { + $content = $this->_parse_text($script); + $result = false; + + // check existsing script rules names + foreach ($this->content as $idx => $elem) + $names[$elem['name']] = $idx; + + foreach ($content as $elem) + if (!isset($names[$elem['name']])) + { + array_push($this->content, $elem); + $result = true; + } + + return $result; + } + + /** + * Adds rule to the script (at the end) + * + * @param string Rule name + * @param array Rule content (as array) + */ + public function add_rule($content) + { + // TODO: check this->supported + array_push($this->content, $content); + return sizeof($this->content)-1; + } + + public function delete_rule($index) + { + if(isset($this->content[$index])) + { + unset($this->content[$index]); + return true; + } + return false; + } + + public function size() + { + return sizeof($this->content); + } + + public function update_rule($index, $content) + { + // TODO: check this->supported + if ($this->content[$index]) + { + $this->content[$index] = $content; + return $index; + } + return false; + } + + /** + * Returns script as text + */ + public function as_text() + { + $script = ''; + $exts = array(); + + // rules + foreach ($this->content as $idx => $rule) + { + $extension = ''; + $tests = array(); + $i = 0; + + // header + $script .= '# rule:[' . $rule['name'] . "]\n"; + + // constraints expressions + foreach ($rule['tests'] as $test) + { + $tests[$i] = ''; + switch ($test['test']) + { + case 'size': + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg']; + break; + case 'true': + $tests[$i] .= ($test['not'] ? 'not true' : 'true'); + break; + case 'exists': + $tests[$i] .= ($test['not'] ? 'not ' : ''); + if (is_array($test['arg'])) + $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]'; + else + $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"'; + break; + case 'header': + $tests[$i] .= ($test['not'] ? 'not ' : ''); + $tests[$i] .= 'header :' . $test['type']; + if (is_array($test['arg1'])) + $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]'; + else + $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"'; + if (is_array($test['arg2'])) + $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]'; + else + $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"'; + break; + } + $i++; + } + + $script .= ($idx>0 ? 'els' : '').($rule['join'] ? 'if allof (' : 'if anyof ('); + if (sizeof($tests) > 1) + $script .= implode(",\n\t", $tests); + elseif (sizeof($tests)) + $script .= $tests[0]; + else + $script .= 'true'; + $script .= ")\n{\n"; + + // action(s) + foreach ($rule['actions'] as $action) + switch ($action['type']) + { + case 'fileinto': + $extension = 'fileinto'; + $script .= "\tfileinto \"" . $this->_escape_string($action['target']) . "\";\n"; + break; + case 'redirect': + $script .= "\tredirect \"" . $this->_escape_string($action['target']) . "\";\n"; + break; + case 'reject': + case 'ereject': + $extension = $action['type']; + if (strpos($action['target'], "\n")!==false) + $script .= "\t".$action['type']." text:\n" . $action['target'] . "\n.\n;\n"; + else + $script .= "\t".$action['type']." \"" . $this->_escape_string($action['target']) . "\";\n"; + break; + case 'keep': + case 'discard': + case 'stop': + $script .= "\t" . $action['type'] .";\n"; + break; + case 'vacation': + $extension = 'vacation'; + $script .= "\tvacation"; + if ($action['days']) + $script .= " :days " . $action['days']; + if ($action['addresses']) + $script .= " :addresses " . $this->_print_list($action['addresses']); + if ($action['subject']) + $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\""; + if ($action['handle']) + $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\""; + if ($action['from']) + $script .= " :from \"" . $this->_escape_string($action['from']) . "\""; + if ($action['mime']) + $script .= " :mime"; + if (strpos($action['reason'], "\n")!==false) + $script .= " text:\n" . $action['reason'] . "\n.\n;\n"; + else + $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n"; + break; + } + + $script .= "}\n"; + + if ($extension && !isset($exts[$extension])) + $exts[$extension] = $extension; + } + + // requires + if (sizeof($exts)) + $script = 'require ["' . implode('","', $exts) . "\"];\n" . $script; + + return $script; + } + + /** + * Returns script object + * + */ + public function as_array() + { + return $this->content; + } + + /** + * Returns array of supported extensions + * + */ + public function get_extensions() + { + return array_values($this->supported); + } + + /** + * Converts text script to rules array + * + * @param string Text script + */ + private function _parse_text($script) + { + $i = 0; + $content = array(); + + // remove C comments + $script = preg_replace('|/\*.*?\*/|sm', '', $script); + + // tokenize rules + if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) + foreach($tokens as $token) + { + if (preg_match('/^# rule:\[(.*)\]/', $token, $matches)) + { + $content[$i]['name'] = $matches[1]; + } + elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1) + { + if ($rule = $this->_tokenize_rule($token)) + { + $content[$i] = array_merge($content[$i], $rule); + $i++; + } + else // unknown rule format + unset($content[$i]); + } + } + + return $content; + } + + /** + * Convert text script fragment to rule object + * + * @param string Text rule + */ + private function _tokenize_rule($content) + { + $result = NULL; + + if (preg_match('/^(if|elsif|else)\s+((allof|anyof|exists|header|not|size)\s+(.*))\s+\{(.*)\}$/sm', trim($content), $matches)) + { + list($tests, $join) = $this->_parse_tests(trim($matches[2])); + $actions = $this->_parse_actions(trim($matches[5])); + + if ($tests && $actions) + $result = array( + 'tests' => $tests, + 'actions' => $actions, + 'join' => $join, + ); + } + + return $result; + } + + /** + * Parse body of actions section + * + * @param string Text body + * @return array Array of parsed action type/target pairs + */ + private function _parse_actions($content) + { + $result = NULL; + + // supported actions + $patterns[] = '^\s*discard;'; + $patterns[] = '^\s*keep;'; + $patterns[] = '^\s*stop;'; + $patterns[] = '^\s*redirect\s+(.*?[^\\\]);'; + if (in_array('fileinto', $this->supported)) + $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);'; + if (in_array('reject', $this->supported)) { + $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;'; + $patterns[] = '^\s*reject\s+(.*?[^\\\]);'; + $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;'; + $patterns[] = '^\s*ereject\s+(.*?[^\\\]);'; + } + if (in_array('vacation', $this->supported)) + $patterns[] = '^\s*vacation\s+(.*?[^\\\]);'; + + $pattern = '/(' . implode('$)|(', $patterns) . '$)/ms'; + + // parse actions body + if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) + { + foreach ($mm as $m) + { + $content = trim($m[0]); + + if(preg_match('/^(discard|keep|stop)/', $content, $matches)) + { + $result[] = array('type' => $matches[1]); + } + elseif(preg_match('/^fileinto/', $content)) + { + $result[] = array('type' => 'fileinto', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif(preg_match('/^redirect/', $content)) + { + $result[] = array('type' => 'redirect', 'target' => $this->_parse_string($m[sizeof($m)-1])); + } + elseif(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) + { + $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2])); + } + elseif(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) + { + $vacation = array('type' => 'vacation'); + + if (preg_match('/:(days)\s+([0-9]+)/', $content, $vm)) { + $vacation['days'] = $vm[2]; + $content = preg_replace('/:(days)\s+([0-9]+)/', '', $content); + } + if (preg_match('/:(subject)\s+(".*?[^\\\]")/', $content, $vm)) { + $vacation['subject'] = $vm[2]; + $content = preg_replace('/:(subject)\s+(".*?[^\\\]")/', '', $content); + } + if (preg_match('/:(addresses)\s+\[(.*?[^\\\])\]/', $content, $vm)) { + $vacation['addresses'] = $this->_parse_list($vm[2]); + $content = preg_replace('/:(addresses)\s+\[(.*?[^\\\])\]/', '', $content); + } + if (preg_match('/:(handle)\s+(".*?[^\\\]")/', $content, $vm)) { + $vacation['handle'] = $vm[2]; + $content = preg_replace('/:(handle)\s+(".*?[^\\\]")/', '', $content); + } + if (preg_match('/:(from)\s+(".*?[^\\\]")/', $content, $vm)) { + $vacation['from'] = $vm[2]; + $content = preg_replace('/:(from)\s+(".*?[^\\\]")/', '', $content); + } + $content = preg_replace('/^vacation/', '', $content); + $content = preg_replace('/;$/', '', $content); + $content = trim($content); + if (preg_match('/^:(mime)/', $content, $vm)) { + $vacation['mime'] = true; + $content = preg_replace('/^:mime/', '', $content); + } + + $vacation['reason'] = $this->_parse_string($content); + + $result[] = $vacation; + } + } + } + + return $result; + } + + /** + * Parse test/conditions section + * + * @param string Text + */ + + private function _parse_tests($content) + { + $result = NULL; + + // lists + if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches)) + { + $content = $matches[2]; + $join = $matches[1]=='allof' ? true : false; + } + else + $join = false; + + // supported tests regular expressions + // TODO: comparators, envelope + $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]'; + $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(true)'; + $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})'; + $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]'; + $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")'; + $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]'; + + // join patterns... + $pattern = '/(' . implode(')|(', $patterns) . ')/'; + + // ...and parse tests list + if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) + { + foreach ($matches as $match) + { + $size = sizeof($match); + + if (preg_match('/^(not\s+)?size/', $match[0])) + { + $result[] = array( + 'test' => 'size', + 'not' => $match[$size-4] ? true : false, + 'type' => $match[$size-2], // under/over + 'arg' => $match[$size-1], // value + ); + } + elseif (preg_match('/^(not\s+)?header/', $match[0])) + { + $result[] = array( + 'test' => 'header', + 'not' => $match[$size-5] ? true : false, + 'type' => $match[$size-3], // is/contains/matches + 'arg1' => $this->_parse_list($match[$size-2]), // header(s) + 'arg2' => $this->_parse_list($match[$size-1]), // string(s) + ); + } + elseif (preg_match('/^(not\s+)?exists/', $match[0])) + { + $result[] = array( + 'test' => 'exists', + 'not' => $match[$size-3] ? true : false, + 'arg' => $this->_parse_list($match[$size-1]), // header(s) + ); + } + elseif (preg_match('/^(not\s+)?true/', $match[0])) + { + $result[] = array( + 'test' => 'true', + 'not' => $match[$size-2] ? true : false, + ); + } + } + } + + return array($result, $join); + } + + /** + * Parse string value + * + * @param string Text + */ + private function _parse_string($content) + { + $text = ''; + $content = trim($content); + + if (preg_match('/^text:(.*)\.$/sm', $content, $matches)) + $text = trim($matches[1]); + elseif (preg_match('/^"(.*)"$/', $content, $matches)) + $text = str_replace('\"', '"', $matches[1]); + + return $text; + } + + /** + * Escape special chars in string value + * + * @param string Text + */ + private function _escape_string($content) + { + $replace['/"/'] = '\\"'; + + if (is_array($content)) + { + for ($x=0, $y=sizeof($content); $x<$y; $x++) + $content[$x] = preg_replace(array_keys($replace), array_values($replace), $content[$x]); + + return $content; + } + else + return preg_replace(array_keys($replace), array_values($replace), $content); + } + + /** + * Parse string or list of strings to string or array of strings + * + * @param string Text + */ + private function _parse_list($content) + { + $result = array(); + + for ($x=0, $len=strlen($content); $x<$len; $x++) + { + switch ($content[$x]) + { + case '\\': + $str .= $content[++$x]; + break; + case '"': + if (isset($str)) + { + $result[] = $str; + unset($str); + } + else + $str = ''; + break; + default: + if(isset($str)) + $str .= $content[$x]; + break; + } + } + + if (sizeof($result)>1) + return $result; + elseif (sizeof($result) == 1) + return $result[0]; + else + return NULL; + } + + /** + * Convert array of elements to list of strings + * + * @param string Text + */ + private function _print_list($list) + { + $list = (array) $list; + foreach($list as $idx => $val) + $list[$idx] = $this->_escape_string($val); + + return '["' . implode('","', $list) . '"]'; + } +} + +?> diff --git a/plugins/managesieve/localization/bg_BG.inc b/plugins/managesieve/localization/bg_BG.inc new file mode 100644 index 0000000..96ce63b --- /dev/null +++ b/plugins/managesieve/localization/bg_BG.inc @@ -0,0 +1,50 @@ + diff --git a/plugins/managesieve/localization/de_CH.inc b/plugins/managesieve/localization/de_CH.inc new file mode 100644 index 0000000..c4d4d49 --- /dev/null +++ b/plugins/managesieve/localization/de_CH.inc @@ -0,0 +1,52 @@ + diff --git a/plugins/managesieve/localization/de_DE.inc b/plugins/managesieve/localization/de_DE.inc new file mode 100644 index 0000000..c3a2feb --- /dev/null +++ b/plugins/managesieve/localization/de_DE.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/en_US.inc b/plugins/managesieve/localization/en_US.inc new file mode 100644 index 0000000..c671b83 --- /dev/null +++ b/plugins/managesieve/localization/en_US.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/es_ES.inc b/plugins/managesieve/localization/es_ES.inc new file mode 100644 index 0000000..9e7e3f5 --- /dev/null +++ b/plugins/managesieve/localization/es_ES.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/et_EE.inc b/plugins/managesieve/localization/et_EE.inc new file mode 100644 index 0000000..2915de8 --- /dev/null +++ b/plugins/managesieve/localization/et_EE.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/fi_FI.inc b/plugins/managesieve/localization/fi_FI.inc new file mode 100644 index 0000000..f066ca6 --- /dev/null +++ b/plugins/managesieve/localization/fi_FI.inc @@ -0,0 +1,49 @@ + diff --git a/plugins/managesieve/localization/fr_FR.inc b/plugins/managesieve/localization/fr_FR.inc new file mode 100644 index 0000000..632db98 --- /dev/null +++ b/plugins/managesieve/localization/fr_FR.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/gb_GB.inc b/plugins/managesieve/localization/gb_GB.inc new file mode 100644 index 0000000..c671b83 --- /dev/null +++ b/plugins/managesieve/localization/gb_GB.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/hu_HU.inc b/plugins/managesieve/localization/hu_HU.inc new file mode 100644 index 0000000..1647fbe --- /dev/null +++ b/plugins/managesieve/localization/hu_HU.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/it_IT.inc b/plugins/managesieve/localization/it_IT.inc new file mode 100644 index 0000000..f7079b3 --- /dev/null +++ b/plugins/managesieve/localization/it_IT.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/nl_NL.inc b/plugins/managesieve/localization/nl_NL.inc new file mode 100644 index 0000000..83a4e69 --- /dev/null +++ b/plugins/managesieve/localization/nl_NL.inc @@ -0,0 +1,49 @@ + diff --git a/plugins/managesieve/localization/pl_PL.inc b/plugins/managesieve/localization/pl_PL.inc new file mode 100644 index 0000000..f309a43 --- /dev/null +++ b/plugins/managesieve/localization/pl_PL.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/pt_BR.inc b/plugins/managesieve/localization/pt_BR.inc new file mode 100644 index 0000000..4515deb --- /dev/null +++ b/plugins/managesieve/localization/pt_BR.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/ru_RU.inc b/plugins/managesieve/localization/ru_RU.inc new file mode 100644 index 0000000..ad459a0 --- /dev/null +++ b/plugins/managesieve/localization/ru_RU.inc @@ -0,0 +1,49 @@ + diff --git a/plugins/managesieve/localization/sl_SI.inc b/plugins/managesieve/localization/sl_SI.inc new file mode 100644 index 0000000..ee3c6fe --- /dev/null +++ b/plugins/managesieve/localization/sl_SI.inc @@ -0,0 +1,53 @@ + diff --git a/plugins/managesieve/localization/sv_SE.inc b/plugins/managesieve/localization/sv_SE.inc new file mode 100644 index 0000000..48d0158 --- /dev/null +++ b/plugins/managesieve/localization/sv_SE.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/uk_UA.inc b/plugins/managesieve/localization/uk_UA.inc new file mode 100644 index 0000000..2cc4436 --- /dev/null +++ b/plugins/managesieve/localization/uk_UA.inc @@ -0,0 +1,54 @@ + diff --git a/plugins/managesieve/localization/zh_CN.inc b/plugins/managesieve/localization/zh_CN.inc new file mode 100644 index 0000000..fe63c6d --- /dev/null +++ b/plugins/managesieve/localization/zh_CN.inc @@ -0,0 +1,49 @@ + diff --git a/plugins/managesieve/managesieve.js b/plugins/managesieve/managesieve.js new file mode 100644 index 0000000..7ff1acf --- /dev/null +++ b/plugins/managesieve/managesieve.js @@ -0,0 +1,381 @@ +/* Sieve Filters (tab) */ + +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + // + var tab = $('').attr('id', 'settingstabpluginmanagesieve').addClass('tablink'); + + var button = $('').attr('href', rcmail.env.comm_path+'&_action=plugin.managesieve') + .attr('title', rcmail.gettext('managesieve.managefilters')) + .html(rcmail.gettext('managesieve.filters')) + .bind('click', function(e){ return rcmail.command('plugin.managesieve', this) }) + .appendTo(tab); + + // add button and register commands + rcmail.add_element(tab, 'tabs'); + rcmail.register_command('plugin.managesieve', function() { rcmail.goto_url('plugin.managesieve') }, true); + rcmail.register_command('plugin.managesieve-save', function() { rcmail.managesieve_save() }, true); + rcmail.register_command('plugin.managesieve-add', function() { rcmail.managesieve_add() }, true); + rcmail.register_command('plugin.managesieve-del', function() { rcmail.managesieve_del() }, true); + rcmail.register_command('plugin.managesieve-up', function() { rcmail.managesieve_up() }, true); + rcmail.register_command('plugin.managesieve-down', function() { rcmail.managesieve_down() }, true); + + if (rcmail.env.action == 'plugin.managesieve') + { + if (rcmail.gui_objects.sieveform) + rcmail.enable_command('plugin.managesieve-save', true); + else { + rcmail.enable_command('plugin.managesieve-del', 'plugin.managesieve-up', 'plugin.managesieve-down', false); + rcmail.enable_command('plugin.managesieve-add', !rcmail.env.sieveconnerror); + } + + if (rcmail.gui_objects.filterslist) { + var p = rcmail; + rcmail.filters_list = new rcube_list_widget(rcmail.gui_objects.filterslist, {multiselect:false, draggable:false, keyboard:false}); + rcmail.filters_list.addEventListener('select', function(o){ p.managesieve_select(o); }); + rcmail.filters_list.init(); + rcmail.filters_list.focus(); + } + } + }); + + /*********************************************************/ + /********* Managesieve filters methods *********/ + /*********************************************************/ + + rcube_webmail.prototype.managesieve_add = function() + { + this.load_managesieveframe(); + this.filters_list.clear_selection(); + }; + + rcube_webmail.prototype.managesieve_del = function() + { + var id = this.filters_list.get_single_selection(); + + if (confirm(this.get_label('managesieve.filterconfirmdelete'))) + this.http_request('plugin.managesieve', + '_act=delete&_fid='+this.filters_list.rows[id].uid, true); + }; + + rcube_webmail.prototype.managesieve_up = function() + { + var id = this.filters_list.get_single_selection(); + this.http_request('plugin.managesieve', + '_act=up&_fid='+this.filters_list.rows[id].uid, true); + }; + + rcube_webmail.prototype.managesieve_down = function() + { + var id = this.filters_list.get_single_selection(); + this.http_request('plugin.managesieve', + '_act=down&_fid='+this.filters_list.rows[id].uid, true); + }; + + rcube_webmail.prototype.managesieve_rowid = function(id) + { + var rows = this.filters_list.rows; + + for (var i=0; i id) + rows[i].uid = rows[i].uid-1; + } + break; + + case 'down': + var rows = this.filters_list.rows; + var from; + + // we need only to replace filter names... + for (var i=0; i0; i--) + { + if (rows[i] == null) { // removed row + } else if (i == id) { + this.enable_command('plugin.managesieve-down', false); + break; + } else { + this.enable_command('plugin.managesieve-down', true); + break; + } + } + }; + + // operations on filters form + rcube_webmail.prototype.managesieve_ruleadd = function(id) + { + this.http_post('plugin.managesieve', '_act=ruleadd&_rid='+id); + }; + + rcube_webmail.prototype.managesieve_rulefill = function(content, id, after) + { + if (content != '') + { + // create new element + var div = document.getElementById('rules'); + var row = document.createElement('div'); + + this.managesieve_insertrow(div, row, after); + // fill row after inserting (for IE) + row.setAttribute('id', 'rulerow'+id); + row.className = 'rulerow'; + row.innerHTML = content; + + this.managesieve_formbuttons(div); + } + }; + + rcube_webmail.prototype.managesieve_ruledel = function(id) + { + if (confirm(this.get_label('managesieve.ruledeleteconfirm'))) + { + var row = document.getElementById('rulerow'+id); + row.parentNode.removeChild(row); + this.managesieve_formbuttons(document.getElementById('rules')); + } + }; + + rcube_webmail.prototype.managesieve_actionadd = function(id) + { + this.http_post('plugin.managesieve', '_act=actionadd&_aid='+id); + }; + + rcube_webmail.prototype.managesieve_actionfill = function(content, id, after) + { + if (content != '') + { + var div = document.getElementById('actions'); + var row = document.createElement('div'); + + this.managesieve_insertrow(div, row, after); + // fill row after inserting (for IE) + row.className = 'actionrow'; + row.setAttribute('id', 'actionrow'+id); + row.innerHTML = content; + + this.managesieve_formbuttons(div); + } + }; + + rcube_webmail.prototype.managesieve_actiondel = function(id) + { + if (confirm(this.get_label('managesieve.actiondeleteconfirm'))) + { + var row = document.getElementById('actionrow'+id); + row.parentNode.removeChild(row); + this.managesieve_formbuttons(document.getElementById('actions')); + } + }; + + // insert rule/action row in specified place on the list + rcube_webmail.prototype.managesieve_insertrow = function(div, row, after) + { + for (var i=0; i0 || buttons.length>1) + { + $(button).removeClass('disabled'); + button.removeAttribute('disabled'); + } + else + { + $(button).addClass('disabled'); + button.setAttribute('disabled', true); + } + } + } +} diff --git a/plugins/managesieve/managesieve.php b/plugins/managesieve/managesieve.php new file mode 100644 index 0000000..21d974d --- /dev/null +++ b/plugins/managesieve/managesieve.php @@ -0,0 +1,854 @@ + + * + * Configuration (see config.inc.php.dist): + */ + +class managesieve extends rcube_plugin +{ + public $task = 'settings'; + + private $rc; + private $sieve; + private $errors; + private $form; + private $script = array(); + private $exts = array(); + private $headers = array( + 'subject' => 'Subject', + 'sender' => 'From', + 'recipient' => 'To', + ); + + function init() + { + // add Tab label/title + $this->add_texts('localization/', array('filters','managefilters')); + + // register actions + $this->register_action('plugin.managesieve', array($this, 'managesieve_init')); + $this->register_action('plugin.managesieve-save', array($this, 'managesieve_save')); + + // include main js script + $this->include_script('managesieve.js'); + } + + function managesieve_start() + { + $rcmail = rcmail::get_instance(); + $this->rc = &$rcmail; + + $this->load_config(); + + // register UI objects + $this->rc->output->add_handlers(array( + 'filterslist' => array($this, 'filters_list'), + 'filterframe' => array($this, 'filter_frame'), + 'filterform' => array($this, 'filter_form'), + )); + + require_once($this->home . '/lib/Net/Sieve.php'); + require_once($this->home . '/lib/rcube_sieve.php'); + + // try to connect to managesieve server and to fetch the script + $this->sieve = new rcube_sieve($_SESSION['username'], + $this->rc->decrypt($_SESSION['password']), + $this->rc->config->get('managesieve_host', 'localhost'), + $this->rc->config->get('managesieve_port', 2000), + $this->rc->config->get('managesieve_usetls', false), + $this->rc->config->get('managesieve_disabled_extensions')); + + $error = $this->sieve->error(); + + if ($error == SIEVE_ERROR_NOT_EXISTS) + { + // if script not exists build default script contents + $script_file = $this->rc->config->get('managesieve_default'); + if ($script_file && is_readable($script_file)) + $this->sieve->script->add_text(file_get_contents($script_file)); + // that's not exactly an error + $error = false; + } + elseif ($error) + { + switch ($error) + { + case SIEVE_ERROR_CONNECTION: + case SIEVE_ERROR_LOGIN: + $this->rc->output->show_message('managesieve.filterconnerror', 'error'); + break; + default: + $this->rc->output->show_message('managesieve.filterunknownerror', 'error'); + break; + } + + // to disable 'Add filter' button set env variable + $this->rc->output->set_env('filterconnerror', true); + } + + // finally set script objects + if ($error) + { + $this->script = array(); + } + else + { + $this->script = $this->sieve->script->as_array(); + $this->exts = $this->sieve->get_extensions(); + } + + return $error; + } + + function managesieve_init() + { + // Init plugin and handle managesieve connection + $error = $this->managesieve_start(); + + // Handle user requests + if ($action = get_input_value('_act', RCUBE_INPUT_GPC)) + { + $fid = (int) get_input_value('_fid', RCUBE_INPUT_GET); + + if ($action=='up' && !$error) + { + if ($fid && isset($this->script[$fid]) && isset($this->script[$fid-1])) + { + if ($this->sieve->script->update_rule($fid, $this->script[$fid-1]) !== false + && $this->sieve->script->update_rule($fid-1, $this->script[$fid]) !== false) + $result = $this->sieve->save(); + + if ($result) { +// $this->rc->output->show_message('managesieve.filtersaved', 'confirmation'); + $this->rc->output->command('managesieve_updatelist', 'up', '', $fid); + } else + $this->rc->output->show_message('managesieve.filtersaveerror', 'error'); + } + } + elseif ($action=='down' && !$error) + { + if (isset($this->script[$fid]) && isset($this->script[$fid+1])) + { + if ($this->sieve->script->update_rule($fid, $this->script[$fid+1]) !== false + && $this->sieve->script->update_rule($fid+1, $this->script[$fid]) !== false) + $result = $this->sieve->save(); + + if ($result) { +// $this->rc->output->show_message('managesieve.filtersaved', 'confirmation'); + $this->rc->output->command('managesieve_updatelist', 'down', '', $fid); + } else + $this->rc->output->show_message('managesieve.filtersaveerror', 'error'); + } + } + elseif ($action=='delete' && !$error) + { + if (isset($this->script[$fid])) + { + if ($this->sieve->script->delete_rule($fid)) + $result = $this->sieve->save(); + + if (!$result) + $this->rc->output->show_message('managesieve.filterdeleteerror', 'error'); + else { + $this->rc->output->show_message('managesieve.filterdeleted', 'confirmation'); + $this->rc->output->command('managesieve_updatelist', 'delete', '', $fid); + } + } + } + elseif ($action=='ruleadd') + { + $rid = get_input_value('_rid', RCUBE_INPUT_GPC); + $id = $this->genid(); + $content = $this->rule_div($fid, $id, false); + + $this->rc->output->command('managesieve_rulefill', $content, $id, $rid); + } + elseif ($action=='actionadd') + { + $aid = get_input_value('_aid', RCUBE_INPUT_GPC); + $id = $this->genid(); + $content = $this->action_div($fid, $id, false); + + $this->rc->output->command('managesieve_actionfill', $content, $id, $aid); + } + + $this->rc->output->send(); + } + + $this->managesieve_send(); + } + + function managesieve_save() + { + // Init plugin and handle managesieve connection + $error = $this->managesieve_start(); + + // add/edit action + if (isset($_POST['_name'])) + { + $name = trim(get_input_value('_name', RCUBE_INPUT_POST)); + $fid = trim(get_input_value('_fid', RCUBE_INPUT_POST)); + $join = trim(get_input_value('_join', RCUBE_INPUT_POST)); + + // and arrays + $headers = $_POST['_header']; + $cust_headers = $_POST['_custom_header']; + $ops = $_POST['_rule_op']; + $sizeops = $_POST['_rule_size_op']; + $sizeitems = $_POST['_rule_size_item']; + $sizetargets = $_POST['_rule_size_target']; + $targets = $_POST['_rule_target']; + $act_types = $_POST['_action_type']; + $mailboxes = $_POST['_action_mailbox']; + $act_targets = $_POST['_action_target']; + $area_targets = $_POST['_action_target_area']; + $reasons = $_POST['_action_reason']; + $addresses = $_POST['_action_addresses']; + $days = $_POST['_action_days']; + + // we need a "hack" for radiobuttons + foreach ($sizeitems as $item) + $items[] = $item; + + $this->form['join'] = $join=='allof' ? true : false; + $this->form['name'] = $name; + $this->form['tests'] = array(); + $this->form['actions'] = array(); + + if ($name == '') + $this->errors['name'] = $this->gettext('cannotbeempty'); + else + foreach($this->script as $idx => $rule) + if($rule['name'] == $name && $idx != $fid) { + $this->errors['name'] = $this->gettext('ruleexist'); + break; + } + + $i = 0; + // rules + if ($join == 'any') + { + $this->form['tests'][0]['test'] = 'true'; + } + else foreach($headers as $idx => $header) + { + $header = $this->strip_value($header); + $target = $this->strip_value($targets[$idx]); + $op = $this->strip_value($ops[$idx]); + + // normal header + if (in_array($header, $this->headers)) + { + if(preg_match('/^not/', $op)) + $this->form['tests'][$i]['not'] = true; + $type = preg_replace('/^not/', '', $op); + + if ($type == 'exists') + { + $this->form['tests'][$i]['test'] = 'exists'; + $this->form['tests'][$i]['arg'] = $header; + } + else + { + $this->form['tests'][$i]['type'] = $type; + $this->form['tests'][$i]['test'] = 'header'; + $this->form['tests'][$i]['arg1'] = $header; + $this->form['tests'][$i]['arg2'] = $target; + + if ($target == '') + $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty'); + } + } + else + switch ($header) + { + case 'size': + $sizeop = $this->strip_value($sizeops[$idx]); + $sizeitem = $this->strip_value($items[$idx]); + $sizetarget = $this->strip_value($sizetargets[$idx]); + + $this->form['tests'][$i]['test'] = 'size'; + $this->form['tests'][$i]['type'] = $sizeop; + $this->form['tests'][$i]['arg'] = $sizetarget.$sizeitem; + + if (!preg_match('/^[0-9]+(K|M|G)*$/i', $sizetarget)) + $this->errors['tests'][$i]['sizetarget'] = $this->gettext('wrongformat'); + break; + case '...': + $cust_header = $headers = $this->strip_value($cust_headers[$idx]); + + if(preg_match('/^not/', $op)) + $this->form['tests'][$i]['not'] = true; + $type = preg_replace('/^not/', '', $op); + + if ($cust_header == '') + $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty'); + else { + $headers = preg_split('/[\s,]+/', $cust_header, -1, PREG_SPLIT_NO_EMPTY); + + if (!count($headers)) + $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty'); + else { + foreach ($headers as $hr) + if (!preg_match('/^[a-z0-9-]+$/i', $hr)) + $this->errors['tests'][$i]['header'] = $this->gettext('forbiddenchars'); + } + } + + if (empty($this->errors['tests'][$i]['header'])) + $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers; + + if ($type == 'exists') + { + $this->form['tests'][$i]['test'] = 'exists'; + $this->form['tests'][$i]['arg'] = $cust_header; + } + else + { + $this->form['tests'][$i]['test'] = 'header'; + $this->form['tests'][$i]['type'] = $type; + $this->form['tests'][$i]['arg1'] = $cust_header; + $this->form['tests'][$i]['arg2'] = $target; + + if ($target == '') + $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty'); + } + break; + } + $i++; + } + + $i = 0; + // actions + foreach($act_types as $idx => $type) + { + $type = $this->strip_value($type); + $target = $this->strip_value($act_targets[$idx]); + + $this->form['actions'][$i]['type'] = $type; + + switch ($type) + { + case 'fileinto': + $mailbox = $this->strip_value($mailboxes[$idx]); + $this->form['actions'][$i]['target'] = $mailbox; + break; + case 'reject': + case 'ereject': + $target = $this->strip_value($area_targets[$idx]); + $this->form['actions'][$i]['target'] = str_replace("\r\n", "\n", $target); + + // if ($target == '') +// $this->errors['actions'][$i]['targetarea'] = $this->gettext('cannotbeempty'); + break; + case 'redirect': + $this->form['actions'][$i]['target'] = $target; + + if ($this->form['actions'][$i]['target'] == '') + $this->errors['actions'][$i]['target'] = $this->gettext('cannotbeempty'); + else if (!$this->check_email($this->form['actions'][$i]['target'])) + $this->errors['actions'][$i]['target'] = $this->gettext('noemailwarning'); + break; + case 'vacation': + $reason = $this->strip_value($reasons[$idx]); + $this->form['actions'][$i]['reason'] = str_replace("\r\n", "\n", $reason); + $this->form['actions'][$i]['days'] = $days[$idx]; + $this->form['actions'][$i]['addresses'] = explode(',', $addresses[$idx]); +// @TODO: vacation :subject, :mime, :from, :handle + + if ($this->form['actions'][$i]['addresses']) { + foreach($this->form['actions'][$i]['addresses'] as $aidx => $address) { + $address = trim($address); + if (!$address) + unset($this->form['actions'][$i]['addresses'][$aidx]); + else if(!$this->check_email($address)) { + $this->errors['actions'][$i]['addresses'] = $this->gettext('noemailwarning'); + break; + } else + $this->form['actions'][$i]['addresses'][$aidx] = $address; + } + } + + if ($this->form['actions'][$i]['reason'] == '') + $this->errors['actions'][$i]['reason'] = $this->gettext('cannotbeempty'); + if ($this->form['actions'][$i]['days'] && !preg_match('/^[0-9]+$/', $this->form['actions'][$i]['days'])) + $this->errors['actions'][$i]['days'] = $this->gettext('forbiddenchars'); + break; + } + + $i++; + } + + if (!$this->errors) + { + // zapis skryptu + if (!isset($this->script[$fid])) { + $fid = $this->sieve->script->add_rule($this->form); + $new = true; + } else + $fid = $this->sieve->script->update_rule($fid, $this->form); + + if ($fid !== false) + $save = $this->sieve->save(); + + if ($save && $fid !== false) + { + $this->rc->output->show_message('managesieve.filtersaved', 'confirmation'); + $this->rc->output->add_script(sprintf("rcmail.managesieve_updatelist('%s', '%s', %d);", + isset($new) ? 'add' : 'update', $this->form['name'], $fid), 'foot'); +// $this->rc->output->command('managesieve_updatelist', isset($new) ? 'add' : 'update', $this->form['name'], $fid); +// $this->rc->output->send(); + } + else + { + $this->rc->output->show_message('managesieve.filtersaveerror', 'error'); +// $this->rc->output->send(); + } + } + } + + $this->managesieve_send(); + } + + private function managesieve_send() + { + // Handle form action + if (isset($_GET['_framed']) || isset($_POST['_framed'])) + $this->rc->output->send('managesieve.managesieveedit'); + else { + $this->rc->output->set_pagetitle($this->gettext('filters')); + $this->rc->output->send('managesieve.managesieve'); + } + } + + // return the filters list as HTML table + function filters_list($attrib) + { + // add id to message list table if not specified + if (!strlen($attrib['id'])) + $attrib['id'] = 'rcmfilterslist'; + + // define list of cols to be displayed + $a_show_cols = array('managesieve.filtername'); + + foreach($this->script as $idx => $filter) + $result[] = array('managesieve.filtername' => $filter['name'], 'id' => $idx); + + // create XHTML table + $out = rcube_table_output($attrib, $result, $a_show_cols, 'id'); + + // set client env + $this->rc->output->add_gui_object('filterslist', $attrib['id']); + $this->rc->output->include_script('list.js'); + + // add some labels to client + $this->rc->output->add_label('managesieve.filterconfirmdelete'); + + return $out; + } + + function filter_frame($attrib) + { + if (!$attrib['id']) + $attrib['id'] = 'rcmfilterframe'; + + $attrib['name'] = $attrib['id']; + + $this->rc->output->set_env('contentframe', $attrib['name']); + $this->rc->output->set_env('blankpage', $attrib['src'] ? + $this->rc->output->abs_url($attrib['src']) : 'program/blank.gif'); + + return html::tag('iframe', $attrib); + } + + + function filter_form($attrib) + { + if (!$attrib['id']) + $attrib['id'] = 'rcmfilterform'; + + $fid = get_input_value('_fid', RCUBE_INPUT_GPC); + $scr = isset($this->form) ? $this->form : $this->script[$fid]; + + $hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $this->rc->task)); + $hiddenfields->add(array('name' => '_action', 'value' => 'plugin.managesieve-save')); + $hiddenfields->add(array('name' => '_framed', 'value' => ($_POST['_framed'] || $_GET['_framed'] ? 1 : 0))); + $hiddenfields->add(array('name' => '_fid', 'value' => $fid)); + + $out = ''."\n"; + $out .= $hiddenfields->show(); + + // 'any' flag + if (sizeof($scr['tests']) == 1 && $scr['tests'][0]['test'] == 'true' && !$scr['tests'][0]['not']) + $any = true; + + // filter name input + $field_id = '_name'; + $input_name = new html_inputfield(array('name' => '_name', 'id' => $field_id, 'size' => 30, + 'class' => ($this->errors['name'] ? 'error' : ''))); + + if (isset($scr)) + $input_name = $input_name->show($scr['name']); + else + $input_name = $input_name->show(); + + $out .= sprintf("\n %s

\n", + $field_id, Q($this->gettext('filtername')), $input_name); + + $out .= '
' . Q($this->gettext('messagesrules')) . "\n"; + + // any, allof, anyof radio buttons + $field_id = '_allof'; + $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'allof', + 'onclick' => 'rule_join_radio(\'allof\')', 'class' => 'radio')); + + if (isset($scr) && !$any) + $input_join = $input_join->show($scr['join'] ? 'allof' : ''); + else + $input_join = $input_join->show(); + + $out .= sprintf("%s \n", + $input_join, $field_id, Q($this->gettext('filterallof'))); + + $field_id = '_anyof'; + $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'anyof', + 'onclick' => 'rule_join_radio(\'anyof\')', 'class' => 'radio')); + + if (isset($scr) && !$any) + $input_join = $input_join->show($scr['join'] ? '' : 'anyof'); + else + $input_join = $input_join->show('anyof'); // default + + $out .= sprintf("%s\n", + $input_join, $field_id, Q($this->gettext('filteranyof'))); + + $field_id = '_any'; + $input_join = new html_radiobutton(array('name' => '_join', 'id' => $field_id, 'value' => 'any', + 'onclick' => 'rule_join_radio(\'any\')', 'class' => 'radio')); + + $input_join = $input_join->show($any ? 'any' : ''); + + $out .= sprintf("%s\n", + $input_join, $field_id, Q($this->gettext('filterany'))); + + $rows_num = isset($scr) ? sizeof($scr['tests']) : 1; + + $out .= '\n"; + + $out .= "
\n"; + + // actions + $out .= '
' . Q($this->gettext('messagesactions')) . "\n"; + + $rows_num = isset($scr) ? sizeof($scr['actions']) : 1; + + $out .= '
'; + for ($x=0; $x<$rows_num; $x++) + $out .= $this->action_div($fid, $x); + $out .= "
\n"; + + $out .= "
\n"; + + $this->rc->output->add_label('managesieve.ruledeleteconfirm'); + $this->rc->output->add_label('managesieve.actiondeleteconfirm'); + $this->rc->output->add_gui_object('sieveform', 'filterform'); + + return $out; + } + + function rule_div($fid, $id, $div=true) + { + $rule = isset($this->form) ? $this->form['tests'][$id] : $this->script[$fid]['tests'][$id]; + $rows_num = isset($this->form) ? sizeof($this->form['tests']) : sizeof($this->script[$fid]['tests']); + + $out = $div ? '
'."\n" : ''; + + $out .= ''; + + // add/del buttons + $out .= '
'; + + // headers select + $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id, + 'onchange' => 'header_select(' .$id .')')); + foreach($this->headers as $name => $val) + $select_header->add(Q($this->gettext($name)), Q($val)); + $select_header->add(Q($this->gettext('size')), 'size'); + $select_header->add(Q($this->gettext('...')), '...'); + + // TODO: list arguments + + if ((isset($rule['test']) && $rule['test'] == 'header') + && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)) + $out .= $select_header->show($rule['arg1']); + elseif ((isset($rule['test']) && $rule['test'] == 'exists') + && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)) + $out .= $select_header->show($rule['arg']); + elseif (isset($rule['test']) && $rule['test'] == 'size') + $out .= $select_header->show('size'); + elseif (isset($rule['test']) && $rule['test'] != 'true') + $out .= $select_header->show('...'); + else + $out .= $select_header->show(); + + $out .= ''; + + if ((isset($rule['test']) && $rule['test'] == 'header') + && (is_array($rule['arg1']) || !in_array($rule['arg1'], $this->headers))) + $custom = is_array($rule['arg1']) ? implode(', ', $rule['arg1']) : $rule['arg1']; + elseif ((isset($rule['test']) && $rule['test'] == 'exists') + && (is_array($rule['arg']) || !in_array($rule['arg'], $this->headers))) + $custom = is_array($rule['arg']) ? implode(', ', $rule['arg']) : $rule['arg']; + + $out .= '
+ error_class($id, 'test', 'header') + .' value="' .Q($custom). '" size="20" /> 
' . "\n"; + + // matching type select (operator) + $select_op = new html_select(array('name' => "_rule_op[]", 'id' => 'rule_op'.$id, + 'style' => 'display:' .($rule['test']!='size' ? 'inline' : 'none'), 'onchange' => 'rule_op_select('.$id.')')); + $select_op->add(Q($this->gettext('filtercontains')), 'contains'); + $select_op->add(Q($this->gettext('filternotcontains')), 'notcontains'); + $select_op->add(Q($this->gettext('filteris')), 'is'); + $select_op->add(Q($this->gettext('filterisnot')), 'notis'); + $select_op->add(Q($this->gettext('filterexists')), 'exists'); + $select_op->add(Q($this->gettext('filternotexists')), 'notexists'); +// $select_op->add(Q($this->gettext('filtermatches')), 'matches'); +// $select_op->add(Q($this->gettext('filternotmatches')), 'notmatches'); + + // target input (TODO: lists) + + if ($rule['test'] == 'header') + { + $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['type']); + $target = $rule['arg2']; + } + elseif ($rule['test'] == 'size') + { + $out .= $select_op->show(); + if(preg_match('/^([0-9]+)(K|M|G)*$/', $rule['arg'], $matches)) + { + $sizetarget = $matches[1]; + $sizeitem = $matches[2]; + } + } + else + { + $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['test']); + $target = ''; + } + + $out .= 'error_class($id, 'test', 'target') + . ' style="display:' . ($rule['test']!='size' && $rule['test'] != 'exists' ? 'inline' : 'none') . '" />'."\n"; + + $select_size_op = new html_select(array('name' => "_rule_size_op[]", 'id' => 'rule_size_op'.$id)); + $select_size_op->add(Q($this->gettext('filterunder')), 'under'); + $select_size_op->add(Q($this->gettext('filterover')), 'over'); + + $out .= '
'; + $out .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : ''); + $out .= 'error_class($id, 'test', 'sizetarget') .' /> + B + kB + MB + GB'; + $out .= '
'; + $out .= '
'; + $out .= ' '; + $out .= ''; + $out .= '
'; + + $out .= $div ? "
\n" : ''; + + return $out; + } + + function action_div($fid, $id, $div=true) + { + $action = isset($this->form) ? $this->form['actions'][$id] : $this->script[$fid]['actions'][$id]; + $rows_num = isset($this->form) ? sizeof($this->form['actions']) : sizeof($this->script[$fid]['actions']); + + $out = $div ? '
'."\n" : ''; + + $out .= ''; + + // actions target inputs + $out .= ''; + + // add/del buttons + $out .= ''; + + $out .= '
'; + + // action select + $select_action = new html_select(array('name' => "_action_type[]", 'id' => 'action_type'.$id, + 'onchange' => 'action_type_select(' .$id .')')); + if (in_array('fileinto', $this->exts)) + $select_action->add(Q($this->gettext('messagemoveto')), 'fileinto'); + $select_action->add(Q($this->gettext('messageredirect')), 'redirect'); + if (in_array('reject', $this->exts)) + $select_action->add(Q($this->gettext('messagediscard')), 'reject'); + elseif (in_array('ereject', $this->exts)) + $select_action->add(Q($this->gettext('messagediscard')), 'ereject'); + if (in_array('vacation', $this->exts)) + $select_action->add(Q($this->gettext('messagereply')), 'vacation'); + $select_action->add(Q($this->gettext('messagedelete')), 'discard'); + $select_action->add(Q($this->gettext('rulestop')), 'stop'); + + $out .= $select_action->show($action['type']); + $out .= ''; + // shared targets + $out .= 'error_class($id, 'action', 'target') .' />'; + $out .= '\n"; + + // vacation + $out .= '
'; + $out .= ''. Q($this->gettext('vacationreason')) .'
' + .'\n"; + $out .= '
' .Q($this->gettext('vacationaddresses')) . '
' + .'error_class($id, 'action', 'addresses') .' />'; + $out .= '
' . Q($this->gettext('vacationdays')) . '
' + .'error_class($id, 'action', 'days') .' />'; + $out .= '
'; + + // mailbox select + $out .= ''; + $out .= '
'; + $out .= ' '; + $out .= ''; + $out .= '
'; + + $out .= $div ? "
\n" : ''; + + return $out; + } + + private function genid() + { + $result = intval(rcube_timer()); + return $result; + } + + private function strip_value($str) + { + return trim(strip_tags($str)); + } + + private function error_class($id, $type, $target, $name_only=false) + { + // TODO: tooltips + if ($type == 'test' && isset($this->errors['tests'][$id][$target])) + return ($name_only ? 'error' : ' class="error"'); + elseif ($type == 'action' && isset($this->errors['actions'][$id][$target])) + return ($name_only ? 'error' : ' class="error"'); + + return ''; + } + + private function check_email($email) + { + // Check for invalid characters + if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) + return false; + + // Check that there's one @ symbol, and that the lengths are right + if (!preg_match('/^[^@]{1,64}@[^@]{1,255}$/', $email)) + return false; + + // Split it into sections to make life easier + $email_array = explode('@', $email); + + // Check local part + $local_array = explode('.', $email_array[0]); + foreach ($local_array as $local_part) + if (!preg_match('/^(([A-Za-z0-9!#$%&\'*+\/=?^_`{|}~-]+)|("[^"]+"))$/', $local_part)) + return false; + + // Check domain part + if (preg_match('/^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}$/', $email_array[1]) + || preg_match('/^\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]$/', $email_array[1])) + return true; // If an IP address + else + { // If not an IP address + $domain_array = explode('.', $email_array[1]); + if (sizeof($domain_array) < 2) + return false; // Not enough parts to be a valid domain + + foreach ($domain_array as $domain_part) + if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $domain_part)) + return false; + + return true; + } + + return false; + } + +} + +?> diff --git a/plugins/managesieve/skins/default/filter_add_act.png b/plugins/managesieve/skins/default/filter_add_act.png new file mode 100644 index 0000000000000000000000000000000000000000..d8915dc891cf8fb95dddf772a5623058cc02bec8 GIT binary patch literal 1391 zcmV-#1(5oQP)SD?sBqh{oeF96xr973F|`DTtRlUc4hwesu1W;xmxGf-7M##$XcuGqXN7 znY3&O{8SON!lC_f6=t(J=lN2*b#oA8vYs|h=ER(3;h?7Xq&(rYrX-a+!+~7bO?cz$ zC_396sAeIIPmDv!D&MlwGJO|$TIqJf>+@~h0g+BU1G*Qc=mM5*IMBGi77w>QVnDzR z_Hk8*LRA$A|IB97792=xW_s%1KT&U_pJhS74Cbf75gZZzOJ*roS5Xc?&2T^{?*1}@ zR|N#-FviEnpr~shkR%4T%R>-&={c@vFH2XE88D*Ks3#zp7Z7+g2(pu_X6m1(prHTE zNC{WB!T}9zh7V5OqY=WiPD3)0T{SHu%ZvnCY4TiMMLD3RpB6PppRT^JRs}Uu7-IxM zQdtXusw7cSQGuFTudoWMRf64SF?1D|0fSjYL%j+Jg1t>%v=IcdoTQqU$q^+h(i997 zv6vfjzWEFYgZ`IS!MGah(sbr zM@&V_^3L37bQL`xzmAcS@9^Bw7x3E2r#1PN@gH&Gl_RLX`(C`o-n5WTx#&))syq|5 zXfD}DzL3Y`33Q!#7Go1vaORzJD0jFqGjm;Af9OyO zQq$dwry2|AnloZd-$^?81}}ex@yVZX@X$d-R6mA>zd`3qUD`b~+)uJ49B6J5&OTPx zhU4UfsUQb5qbVr3?t$S<>T4vjihsn;iBd!uQ0O59fiFW^E3qc_!B!+ zRr@Z+iZTzJq}<&I?0%G?beB=0VlHH2P3*DSq)`4za_dgWbt5blvYv^|sRN6}3NP-* zf_EAv1VhChVD@`M$L@CEuEWsQBznQVF=YDz)Y1w*4Mhe@Y&PV*hh!!mj~fs8p2Wb- zODNmBfU+8Wt??Os!=A@!&whPx;J1>+i3|Uor)lVx4p4-wqoc!GU0bJNP`#^aLzF6A zZqX<(f3Xu+XV1cMz>l3RbUN1R>-L?*d_p|Tdf^C(APYi;I~1`#R*BX*eIe30KRiwzhn=6f2`uu+BGNdg`wyrlzJw$X?{O z5Gaab+EMCwKC~PV?USW4!}D4-GfFmTvzR59f6Q4kH_SbWM50u1#cH*N%gf9CRaKa6 zZf>4!ZEcON$#{MS1O(mP-4+_06rDwqB0d_Lcj$Kwh1_Vy-uotG002ovPDHLkV1i}Nl92!a literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_add_pas.png b/plugins/managesieve/skins/default/filter_add_pas.png new file mode 100644 index 0000000000000000000000000000000000000000..b5be806181c66b713a2370cf5bea80fbe884a271 GIT binary patch literal 1409 zcmV-{1%CR8P)?*Pj|Q4)7hvjY z87_?bAx27&N~geVDBoZpXg`HWBjGUg4-D)d08gi#0UL!-{s*!vemF9gfTxmA*C}9i z^)YHwL8%EGpC}6Z0N_|AcIrPm`*1z|q=5obSH43I*9gZiiBhwzjsO6yX@DD^{7!*a zIR&B&snk4}bUOu-L?9gMMGE@yJ!WUG!B&SEs7GZdc{Mf)iG0vSwQy0ie#?yaw_6`mAhKL0pny9x14p z+bN)C1$ugVAfD*wUcv2_z~^z*ZFQD`y0TD)b`>B6j~wfVBvN1)6|8v!ftU@ER%yV9 z`M5sM_MQRmoPR%gdBifPY8vRe0nIj9O#`e!3r>0LDFaS{Q*H#udV^y^TC>%>64?>6 zdgOFZ8L)+8=sKunwJIcHIcJRQDB6YFzdVq>^>Z{^*a&a{eUX?l{=~^Ov{l-(1{6i9 zcf@M6q-D)5t6Rc1*DjCzy}TTG{6rESJC?NOmsT=?udjR=z4O=71|d@d+o2qU-8vzq zb}A{gL0RMK$v>zG^3~N(A6;I~L{6VN3x|h~LLpbOPCh<;>P3<-tVf5Qfs@$O#I^xY zr+cw#W3fQ68VG(?aOJy^z8>8u{M$V=JOnD0Vc}K^#*@$C`7Dm-L1hYd#stq`d@u?AF_2<@BS^VwJ&6w!HU5{CY)WS_j&86+V0lu4>{}twOPG=gp zx`3ZYa9V7$72CDU#mGT(Af$2S!Hc}PDQcwTbp;vZ@>zKMwRh_67%%U>`964kKKTCn zli-V>ErWF9J-$P!v{wbSblW1d<03Uds+uZDK}rOXfh_CW8jjNvC@~<2&Cw|GtqW@} z15R&Y4GqS2>@*vKt(Ow7%Wu$bU8DxU8y}nlc@^NZ55KU$UYI%uy$^XIa0tLe0Gq0$ z1L&mvPG||wrl3}WH4QP4ToU!`aoOl8cwJcezFwr_cPCQ-p`!qa6S%Lujdl>k$~Dg# z0x;WN4Y?Vw#{(_FCE(qYQi|4KTea8g|CICF^47M^} zHGO3>!+YC30q_VXCMLvKV$k9scBt>JEJZ?LQzU|_&dICAt{Vx@Kw0!-CPH!hz6%#g z5hYh%^_V}sCoJMru@wMm70&eZw2;r|yGye2!oa}bu6sJeF!1@DKbu|e7NYrRU_|ly z2MtSTTT5|D7^`LP^8P}V;KE>}el1c(v}DTUF|(ao{3v2ACMPEayt;XGk|bfrs{p^Y z9*y(GVv%Gr8CNcsb8DJrWx(xro47&ID|lbune&wb39V zQW2SZ_u!A6P$=Z=l_Y4IhVJg}w&`hTfTuw6P%sPF-43EJ5ykMxNHqms^a&K`&1`{& zEXhYV0IrZTFZLeTV>d#K&Pa4zk>zSxhogX130=HZ!X&3aE)fM8F#8|{vdk&yLM#?@ z4oOXNvo!+#7@***P!PlL@Cf9(NU+KT^C@js>l!0aIpw6mnNz)DEY~SWGOM9OuHonJ zP+)r=xUMe#=ui+1|Xap1mYCzeSbvOk44mlL)M8QBZh7rnw za@mGmE~Cus%3jA&=3LAtf*m0XxN`n^>*YQn$h0h!t#S>jtY+vMfum%Bd&qWODVr87 z@>s}~8lmMCWLC}nQDUG>N-e{z2o<`SRL5Q$chbv9{dxo0+@_f8J>HL3PD~0}yhj0-@uN2O8`Uw?Z6nREuHosdA85;xGmrOWf!+}y;QZ@i7ppoXo@tXO{fiBTjT8^kwPFCqR6Ze(v`oIhY|GqwQ$ zKK}ee-&MA3++4Yit^7KMhK69UU97AwV{+nkaq+RibR?5W&gpZ<$1p=;l=ci@UM%ys z?bEyuL9tlG?etA39fSniLu%y~eop;Tk-^8Qm7ib|@_di1;$52VYA*|HHQPErg#yr$+N=X^k3sqEZ^ZohtWv+HSxFeNZ3KvhFg>rbclKrU zi~@1G_?kkkS>ZQznG7f%suHTc)?BI?L?RJ9u04yj!XM}u(1EBk|Ixy}%o;z^k^-v#^2rxYyhC89l!lUv46CV>6-Ek$LT5T1?!SLu9oqQ4 z&}W*a#$!cQRh?*B?;q@6qi%U+e0+RoX=$nXUo%d@?Ch*cw+IPR1A&0>lHcoj@6B&) zYy?+VS7W(cuE#J8%?VOfrxra&RdSuGVRn9g-ipWLY-?-lzw_txF98MsiDD?~HB8%1 P00000NkvXXu0mjfgmq^V literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_del_act.png b/plugins/managesieve/skins/default/filter_del_act.png new file mode 100644 index 0000000000000000000000000000000000000000..1974417c89501fbd05f222aeecf512ad9528d6f9 GIT binary patch literal 1337 zcmV-91;+Y`P)@0gP3oXmS zZpB(!NbN%vFMR;5jT+wTiyD*if~mZS7aAI4NLwE?-UzAM_+VqyzCdD3OdH!s4aAUC z8j2QQ3<_GiM2<6!-EV{{vVL z!0^ZjbvT@>W|v{Hh`CHAjpvRWDgHDEP^^7=_H+7Q9iM!5magYVEEP~RwgN&31dgBB zw7wvy*w zDWInMsG?D!C^9%cn@!jP0K+oTQvc!eyUp|y3kAefzCjMx2*)ql#6nvY0f3U%05{zG zZ33@w3T!Kwp1uxQSxbQ^64ci0U=)Pdb5zTo#a4wGFr!kX$0=Cg6oga?tjSgK^-pyw z*f%j#LY1v(KqZ^tqZgZ1K?p?_B!T%=j4|e!umQC+X`!u(04T=Oyaw6l)sYpo3L3-} zTxS&IK*)k=N~k8bimEh z?PHY^xHg4)*22lOrlZbIT>R$ibA$1CSJ?m*Jxq;%gL@^{!)KG4%zLf`|3-G3kygF>oLZ*WjtLSD{=U5-NwP$S)@Xt zqcjrXk92JafJI2UySuA`jXTvKgY|(8w^Tk>0XKiSiZF8?ZwTT?rxS_C4{G4X$8dP} zE_nW!MNKM&9Zu(gk{K8n7_dd7(fjA;=WvpVtK|QXy)AG}PJ*~xh$NG^7O017XOns_ z<()$6&pCv>UQEQ}H)fN`!(~w*%d+6}xQ{O1U9wYs;U|2LRk&2=$76?{^jsPqemyGM z=Y{t6bJ^&kHlLN|&*vjuHOW+XvAz9;M0ItnaV(9c+f#iw_MA<>)Gh_x#W4zXA*Z^B5Zs9+p;O00000NkvXXu0mjfohEz~ literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_del_pas.png b/plugins/managesieve/skins/default/filter_del_pas.png new file mode 100644 index 0000000000000000000000000000000000000000..9f17e38ec0c59fde416319b63076f4bee76a85f9 GIT binary patch literal 1339 zcmV-B1;qM^P)t@yzU-_kO?Mdpkyi5ID_)gHt|E-2wi2 z13!$7B|IK)btgK4#RC{s)dIYI?b^+{9Qb!zh7S|(zmzCPzrI;N0@fk#W&t4t9E%>g z%r?gvXfXaT3tA^&oCJf*<*IsKY!Hq|K?QYdak?C9BL@%Inp5_K&@sKFzZ?xz)6T(X z_ww-ZTa6Gx8A$z+0^O0mKGA2+qMo({gAnQHIJpB1P7?vs3oic;Y;QI~Pdp44E?%-J zAh!AlHmSf^2j-v4#ht(b%Q7;d|KgpqHvGsz0kV~Ek;62?{O4VKwXM1w09?@kGd%R8 z0v|C7TvO!OSK>O{bf%8(wTV2e=@F(`0?i!1q0%2d#A zWJ5ymNi<-Pjo{07V}>BO0}2vH*Tq&JT^Z31BQ;62t-2h**3&Ep>F3k2tdRxnd=_RX z1tony1z0aZAP|7|aD=T2g1|$A$8FoH4*^?QREB02Pzuhy7J-YD0!=H?oY$x$x+Bsu z4K!lbw)@HP5nzk6&yB%u!(c&Gp;*+Q+9s)JfH<^ZrN^-luo4_IBeY#~ER0YsTGekN zGh$*#j*o?aDIBd>ghIYh77{WRCj=dsxK{MUNp^3~dqoXl2JA3{Egam|89S&F&V5h-yyL_=TpULn~C&uG# zkxnrb2}=)?-^Q$Y4{m)EYn3D^dNwA$arNpz0zf!A1y=Pkj4>gSef+cE3-|8EBhjc> znivN+?TcQ%EImpl4GPY5bV|bHB%o9ZUcYiBn3LrJI&k;Mm5?|DWxWR~{FiU4R#NnQ zUR2t{5M*=b|AL<09?2M6SO8Bb41_u$udTz%+}w5@6qvcp1Y~lL%M_Hk$L7`d-r(Z$ z#%5aT?goKw;k>jM`BzuLzp@I=RKSKK@i`vwOW z=QcLdd{@-i5BO4s`{qUHqFDsF5* zNy&oX^8r^V99jpsLo1;XJ+{@4Nu|VEee9aKeLL3I5tW+e=Yh;T0Yss9y9H><<)CYG z6M7MnK5@G*)crPOetSJ0sM&{91^+mijPs#pVD!6-8rGuU3R6TExyg zOM@>G=_c#z?2)NM9bm;Gkw~~g;Z6h9P@8nXQ5pogQ4JIdsMYIjc_9MB!^4~`%l@^s75M!6^%wsSQPo*tdOF^%s-n!hU|IB~ zJ^c-fhWZ~phF5cn)J#$QTUC|yY&cD;&|C8+ZYx@wdeOI#whlYkYdKWZJ<9MEjQmF*& xUVA*R-#K1(R;Tu~k&zL+Z@tt0d;Y%w0|135!lIJ!jY0qb002ovPDHLkV1k?Ca-RSI literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_del_sel.png b/plugins/managesieve/skins/default/filter_del_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..483ffc08e3d4a924fe3035790efeddb44eed0518 GIT binary patch literal 1230 zcmV;<1Tp)GP))YFIT?~u4wkJfQcj3tIOfJ*h`1o1P{<-7`4k-EoSfv4;6r3viA|ib z6xr;?9=6$fsmkk>nPz|)CKAYA?VSMPn_d#_$IAp}mcNjTwl;tKc=niw4&6@I^5 z{*YB6u>zx-rebn(lGUWZZZ?N^-+B9gvGV6%eytq=r{(fR({ht5CYdlWiMjl7W59)N zxNzs37L@(Rqo5Ra$=!~XvA|0_iE#^kp?f%xQJ(^ituyx&yni-;2rFQ5X$hs_8w>`m zwTz*$u@UijynZKiHee14>Na+_0%+@sqCb@?r@-+(69u{iH_?!oRKEhSgA9( z5MuNWqt|_sR4%JF3PhREnY%^2XHXy&iGoE4zMld~GAKBOSS)6}#14tgqY)_e0dhW< zf*AVyQ)L;9dzH4S@XD$U0rxE{3@*RbYL2A}2}u$jIwTK2$Dx2b7TBiF{9#cL^$`V& zi{KR~5S%02(12((W}@Nu`5^m!d$MXnpwu9Xf-F(ck&GclQK0J_T-Tus?yj{A6;>A| zB19cgU~F#ut_-%Bf>cd|uIV0Bg=c6hfqJ6Aeu>+r=&A;dEH>q`gV1skvMA@i9v{$Y zQ%zA_q2}I#Rpd-~)Zku!I*t!0#bRlOu>mL-sP69K(d;ZVRl%#Dezs>cG4R{9YmgNM zA2QbVBe3qJLpsU_jGNi{-XGy~x|E$A{QJ`_B$7!aIy-Uyn`syEyI)Tsnn)nocN(o1 zF3f)bv{mnfWjtDrxQ|NupPu~z9o^l~Zq1noU)RNp`19LobGoae1N`=FYkcWauae8n z)3C4VOekDM=$vh zftiibF)XMkQJ0sY<+2Eb!|)M3l(%ay%ehlym{9L|ex>&C`|og`q9C&H5V(I2jH+Nc zK<7q2kC#?faE6o`^7(EaD-+5kr8%omp8qvHje$rLPR;)gX=@7#jnhC;Msao_*RRrz`8 sysd1J literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_down_act.png b/plugins/managesieve/skins/default/filter_down_act.png new file mode 100644 index 0000000000000000000000000000000000000000..7a24a4f3c503fa464cadbba51f2987f79326ad05 GIT binary patch literal 1327 zcmV+~1RCwCNS4nJJMHv3(ZFZc+Zkp9` z3n&FuNhG+yiGq+2M-G$-RTY~G5wr*h5*H9i;Rcl;5TObrN$ph1#4m3$@A==p@B3#$7-Klfq2P$ekq4mt--GK53oI6k zH!hMpu($`J^?D7@o;^!i3SeWifY+v8xhq*-`ReP|E8vepu>wK}6cImB-MOO3Y6$$O zNZMhLKa4_E)rRkPb{OqJ5LDfF&fSSQiQqu}cf3d#f9BZE-QhqZ>P2j|Sb;DrNj(9Pj*nGXMPDuUkC7lW)vR(K`6BAJ7OS}S(0@aGc;1Bbq zRhX6)+G@2EvH%Je>;nP*q)`MymYbY}8T3Gf1xty`zr}icosha(76cL+mRgSPJWW*< zr7oMdVQJf-Hh*6?QLJxCZU|fLsldbBFFli9T_u>jr?i%06lcd40~8Ldz#jFu|ZKQfsUcXY6H_%>WDmax8_ z$J+R>c>UEErSrjC52L?-0Lf$zwkze6v$L}+!O>;E-H=O1>F!LtRJy&TGf((LoUs~P zb+9hxwprJ9s+HdA_o?(1T)oKeU)IvqRokp>RZ6VNxui}`PLlcgdA4r_1O(I5(>lNP zSeB)0nkF?a?#-ilJ(tTF*=#mlEEfBc$>hfP`1tDJ;GoHU)VaO#+}xbqTmjPVzvs;d ljL3*Zq*gy@v-?kg0RUErwTU1ap-WV~);3zK)M|>T50P0>J z{II+{tLxFmO>_W@yD(~5Re1BCLx@6W#XNV5F;n+u&I;9t3d2?!y;B73BG zYm6ltfc)UE1U4}zc@Vo%qX0hIU7*G z0`)QKQ9&sI5uc*qLj>SkMjrH^zx`wYKMGNR0_FSU2#tvNRYh&I)lmRI>lzS-=Ps4t z13`ge!rIy`AheYNRYfq+e~eR*;ooB(dLdgKA`oOHr6(x3FDS@J3c}#hdi}E+75qO6 zAYt?{8jxfo_~M-tQV@8ThJ?9a>hE#Sh;PW!WR12u3V{0G7B$EpA1#}*3Pw~DZgC1+ z(nCXPf3GS%L3bWpfM>{*8mG?!7q=l5bz6}3L|`Nha5L0Q_Ge? zzOt=px++Dd@KQ8Q3jL|14c(L>tqI4mZKzhNH6bDCoUzuiNU4kR=8hHv4FSUt;XR;K zuCQ@B?$CW2L0b)!N~NG9*0M#r%v?e#v;~?5?CuDUhSbYxE`A3aKz{?U<-LmF_jYR99Scj@pW@5j)PDs64 z7Ge}NENeMDd73~au8S;EjZnow+&eIZK$TR)WJJPNcU2(a51?n#t5*rOZKI1fzdFrA z0ZFt}-jumRtO{W|?AsfQ&)+6D;g!!$l*L_FKfQBN#M{0PFrR{;!Fc?8Ys<3nOM`E$ zuWns>?$q?-jWa|H2Yxb}nVE)IEDF5|4ZdH^My`Hx=W^{NPTC&JX|0!1Nri`ms0hEs zMpTvRx=v5wGk@K_an}pbiUPKya=mTrzytmi*UIX3;Ld*$S`D4#+D}7~d{ox~nsvt) zx*lPgp0Oek71Vn;yYa`h$&=ILfUV#tKioGco$LRkv;1v=Kg;3lcMU)YZgzH7Nsk_v z9Hbu`-uG9)@UbClXmAkv@AhZ^T>E+Q8}udlDS zoLEtFmztlS$GKcCX_%$A#>S2x=wQj44JsCkdLf^WTTk4deD&oD$G3he{(gmCezEv#?6;wzyb+6P zWs?zyYYCk{e;zF@EwOzO5C-Sw<}iQN9LK?`s`57Lf_QDdn)f$1H}S^CMvq|_(PT1N zotT&?rPFDyXGXX^XK`_nwj|K)&+}$PLYD%;k0#P?*!?fS0EsiVof;z>$^ZZW07*qo IM6N<$g2=geg#Z8m literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_down_sel.png b/plugins/managesieve/skins/default/filter_down_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a9e670caaa99869ce544c38cee4327018d3606 GIT binary patch literal 1231 zcmV;=1Tg!FP)i@ysB(q0gKA&mQ@t7%Rk63U=vl!jwL7&5=eBz0*QyzMWqU&NF}hT zkZr7}#I+rJJg<8>_s%0TW7m#tq)c+!We^#+$6Z*?ZN|4o_Aq$bCW5G z8b3+SVQ~PXj$^~_?k*WCfYVMF-g|fLf64NvpMM!2fne(zrpR1yMI-?Okx0lNxpuxY9?83j?;rO+LTu|OkABGf~lxd{$bm@a@I*S+5@`0$k!WJwPy)ha~GSJO1_ zUP54QZVvMK{PZFW9FPD7y9KAs6fBfg_h9(gPnpwJ79qU&x~gxu&Fd) znP#-Z_yF`9bkR|>*8q~SfVc`US%X&h#Dkwr(FsHJgpgRAaS|zgMC!O*19|=h3?PM< z=AhoHVzto8G{9og_Jr7HCvwb%+3qzVPtzbX2W({b6py)X*rc71g@NFEo{{)iMB;&P ztr%7pY}Nx8dxXO@JnjsrnDc6YvQXWKy$FOv(`L!rDOipHEOp9L*I)5liL2=vcuQ z%*>1ig+c*}#Vc?t_pu0qrg||G^O?MGuK)8AKYWa~Sm znG9UQAtYV913y0eM$o}g2%O(8-hrd`U$DBmT0cBIG)IbXdwW}MG#b}WPX2}O@83Vy zV9Awk7zSk9*D%c|@c!B@;XGTt27k1UAe~Ob`uchv1L!2{XP^kNrlmLEc>Oa3Lq*<+ zj|@w6%J4kjH%(Ki)oKN(vJyJ~JX?M7Ua3^96^q3aoEJJ+HFCLJCj0yQF*_vLCOHLL zTU#prWNq74IrqHe_;3FwSNwdvUQZny9F&^P=De=!Pghn}YKx1D7Wy#JUVCR}#|dB9 t!PGtJ=Xr9#h<*%3O3fVguK7=Z0RYP6TD~*PP>28k002ovPDHLkV1if`J?a1e literal 0 HcmV?d00001 diff --git a/plugins/managesieve/skins/default/filter_up_act.png b/plugins/managesieve/skins/default/filter_up_act.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9689bb51ff27106f6fb6a681f6db56c3ce2607 GIT binary patch literal 1321 zcmV+^1=jkBP)~3~vX6GEwIdk7!cT>p8PB?RB=KsI*ecylP3}K96FPnlrZhHnZ;TF-9YowfQNt+XxuGFa6M$Q*8N4?3>K&Qo$De#!I|E+Jhu*w zvLW=NV$wE){9_cVsz#n~Zi>|>fzP^^oTn3g64HTs_qa$Hzh+nLJL7?<>$1OG2j(A6+w943?(L{nyu)Y$eA$ z_9)6_GvWcco`jC=0eBKnhe}GFK(g;19w6$D8-|IOzdDB3PrZoIbI0U9V7rxYQvlRk zy_mbOND#~iQGIUknS06qURlIbBM;-`7e^%{6!l0zT_3xYdvztd!Kpk9PWR}!C;jBo zTx&@Q;+gjcxsNLK0B~wq7|5ZT`e5Wiox>=X@yhvVa^K-Dh@aW#;xt}+75H>JrRVsY z_+@DF3ORM*X_;KKgB{L3Kme&E@_sAOxTE_BiV(Hds1j=*@yA*@s*jG8Eaj>>NnNY z)RdOVWcu|&@z0)~-W_i)OQjNS(#v@8{sCl)I#|rWKYy&CUwsHoO-;z>GJVt2(~JJr z#m&2+1h#D}$>zjMdiHh$a~FOh_E^!S+8-BvUDq26TKHORI7ADNO8_nUEsK_826rM= z4CD>n%<9=o$z(FeyRjW#MC>Hg zwTs${6tybPL5`9Jo_P$BKnR|Yg+P%AKLACc4@gJ}C=!VBP|-es6e3kfJRu>%MLIM$ zhyC&8(o#Mgjsyo$8y1@=s_PoOFfnniBLG{461<*&?U6+J@h9gxXTZHm+Z8|vfyARn zvfY;?!3Nil@SrgT@u37{Sq?lO>kV}$L7jEiIdjMR$fpBw=Qv0x`wv_A(d0l-b_w48 zss?X97lkxZVP$m%u&;dMvA8{pA?;74AbaG<{sC}0Z3b)xQPClK5F(!d&TCC z^S(DuyJvtq=id)sIUx*cx(of6 z4I9LD`eTmYb|!%nrk`q3Pd59b8#=o9`v_Rm&lE8@6t#l!jJ1wHm%ZD5;8p@5sQAOJ}64=l8hR5Apbb;m&@gzf^vT? znT*MmT2%ljmP%MQW9x}TLSwmvCMPG+;^HE0#Q>R}o>rM*%`{C(Q53K&Yu5`0zx?B> zs><8(V$FcGv_mTllmR7eizw=2_hPY_!R(oh&YJ>oBI8DQVB|h+C(U=I8YD= zsDnf(v16|t@2`2x%Tn4vfb6gYlnM#Ppja%1i^vhR*`I-A4)ElQB`Cto z%xtQIu`K9Fima`{7>HMeWl+9+R?cachD0$V2hHen5($`!APRNur%-}|#wDm!fOU}o z8H15CXP{6hN;Y&&14Gw3wgzJ$8Bi!elS^>!d=X}O6$AkT76cF=i=Pbx#qhv{2^^vd zgmdwI@5-b!g6H~>L?qgD^5_dd=_XF{ zV4WYR4(QCqb)U=h*@Nrw`rNB<>)~|?re6;929VS-ImI+6HTM$EpEauR(xqwm^v926 z(&9-ZP<~^AW3PwHN<=OqR?ekiHzz@S{PhR2WCzay7S%E;?K^7A1H;4fT)4HFb{-p# zK>ULDWxpD%52&=lkPeYIaUBq~*Yn}?%^T9W7|-X!eSiSM%yw)`fZ(J3>&9-5pPgfO zLhRbk!yCf+7u%nJMl`s0|3Q4~>fDuR-$dZa)*s0vY+rb1viju9-BJ?75q%)qV{n?2 z8!=TvqCdQKz6|yHzRUsf`)8MLK9O==HGbAWr#f6Ny$z2ZKZdQ%hwt)bl}HNb*qzWw zY|Tk(V#35k3C73s^3p-81;6c#aKsz{3IIBo`Up%-O~Ic}pNdDeBgaQez#}B{^Yf;n zybw9)H*C8BqtC{`ASM{p;zaPxi8UqxT2>Ag7Z-O|R#v>eNw~bctW~Sk>Ak&w;j7!X zkG;7RO~*Eyu=jTj>g^^cfd8(x;F~+&f?*h-Y1+9+0_>ZFTu=JS%NO70WEebwNW=_g zI%Rxa%&%JQBP$<-R(rV>$nXIp`V?PFhrKP3cx#53! zREDnWGUIs!UH)=)t_uK7*KT{vF`b~z-&Zc!ZQHH+wLAHIexKWGWSw^ffMLzJqMVQX zzd#PX5sm3<6I;!LhH1BKV1^5ggPOZ>cSF$b^|JGy00RKH^<5qgC2QIM0000 + + +<roundcube:object name="pagetitle" /> + + + + + + + + + + +
+ + + + +
+ +
+ +
+ +
+ +
+ + + diff --git a/plugins/managesieve/skins/default/templates/managesieveedit.html b/plugins/managesieve/skins/default/templates/managesieveedit.html new file mode 100644 index 0000000..2302073 --- /dev/null +++ b/plugins/managesieve/skins/default/templates/managesieveedit.html @@ -0,0 +1,109 @@ + + + +<roundcube:object name="pagetitle" /> + + + + + + + +
+ + +

+ +

+ + +
+ + + + diff --git a/plugins/markasjunk/junk_act.png b/plugins/markasjunk/junk_act.png new file mode 100644 index 0000000000000000000000000000000000000000..b5a84f604f025c212a3880994c84ee6f06170809 GIT binary patch literal 1995 zcmV;+2Q>JJP)1eN?50htG$d^ykW>Ps76=Lw1W|A=MoO@$zudS+)Q2(&IqkDGmxijC)d^2-a@H`J6 zw8{namicE|MqkXO4A zjm7ZzW8YipRydI4wI3cm`2DlXH@)!UOWzt98GfWvt@QS6?!ABCSH9f;p#WTa?S1k* zY&nbkTyk$+_c|1(i`~}^z%fdw&s8yBoP${} zd}xxjw4~rTj{JwCf86$gDcG_9rolwCc}p}L!56nZigLM(pYKmY5FC zpUY;_7@rtJ&%L+d?CGI9cYS-~!+nP@KC=XXUbBC=?ZHhL{Z|3AGgVxSTtqSvhi+IP z^FgjH*pABqaBjFR+FDb%lFu`eAf8Adkw{{yFoE$)mtdO)lF1}esT4*pj3Fgi`1tk> z7(D*oAyW6ul16mj{!PZthkL>uU0Kv>wf{-iOA@%^)HF^${kDGkPnTj#BGA2Nt#a;I zFVUcE&A7XB z51PZR7@2pmQ(TK8N^Hn>Ns~v4!^uml~N(po)eEOSdN*%TV+#OR{iJW=#u3 zN#dbq09rbl(RO1Bp6GDHmSy7>P)XY|TBGfV$#HDy*a<^WFjzefPbQ-BXjn+JHzC#4 z1lwZX+q`v`h85=QSVJQcJaJ5^YOuHm{}q-7mw7-XXB!SAwF$SUcA>Xz2TqOtbpij~ z?Yprx^*I=p%hSS=DEnCiCy^`gt68%li{if<0Em(>q*gSZU&pby2Q~@gBO?>@T-PjY zO*{ZekVr|GN>s(z^acNULBweBT_Tyyy=CP}N;x+KW;r0US+|(2GYu&qlDwr>b)T^w z3qL(5`rNlI3#Ms9Q&hZNISHjzL9V$Q0~2o`9%)8Nokk{+MO{_#skQrXYVH^aE_0iJ zI|NjRsau>MO2zO(N$2c}2a<;lZdbqX*r#LhRE)<)AQBAs=W)a4G}g;^V0-5C2ul(4 z4Ie}Q`>(>0O(GZ>Aj7+F-iO_r_HsSHAAAyLiYIVoXdbzqW?rI%)iUCtW*pdd2ou#YyfN|@eD%(Q`1)OsFX*Xi^Ef*66rUjMEXhDHFctxj zL~(r8^8k_d;@ni34i-gB*rtQq30u;r@-YYFcpUpdu&wz1oFacp@ed&44o#KO{E74u}2lrKYjwgdF@9C zg=pr>J{Oqc-<|v^m7KMp3MgGB&{14FB(fN=gptr#%LV^)Gp z2{X>RU??WwpX0CKZ|7gaZ~LC$1B=ZDR;Y(hJb_mSUZB08eIi0}>ZDXbqUayvc>AU_ zSVT-G;tZ|$zkn1nFtPi-cI)MvKHHUUN=MjDlqVHL(_!*}O38HjA%v9_&o2YuftMm# zApq3u z&bCg{mc~h&I88562q_4Hno97H7J`BVK}b;qs0yGy^`(ymAtWdlB_QAdloVAYL@iAq ztwRH$DhQ{cxhb*TT8`uO+IyYdo%=t-`TtoPJ8Kh~m%gy}zcV{KbIy0p`ObI#m3-gF zhxwBJ4`3w#M~=R9V5TrD&OP6Q$9yo4(UIQijk$~&hUr?C<$4}-fAXUr9={U+>PlZk zqES5i?QbskmE2mP`3uh+{8sVSn~tA&W&P;r^$j(xmLA%)`PIE&_hnUsd%2JsH%d#Y%enD40OX- zPp0k+fD}$6o=PI+Fxd6bviJ1&_e*;|_f$BW%_5h}Ar_0_r-u$>Xyb#J&gXG+dS=y1 z_T}3ai6ZrU&I3An%k#x`U6jf-%+8fDb#rN;YuBtrVYU#zV*t!5qfydODU@J0@*kLF zU0o>`5XLV~qDDpvg#wzoMb0I$xFd>Y5eYr0icgN}cH8R;?Yl z%o6|vH#`qL-6`Cdoa7`yEFMQZp1@3g3KQ?W2iLZcNFo;_-CRXY3B*1IU6 z1PU{=V2&SaE5Vw9)z0N(g>XJ&inIkiNdJmkLSSi-9RB}kv>vs=$tDhEm0;? zPS+R887#^WNhlnHArcGOZ2)9dvU*mb2#P-FyYC+Am3CUM`EoB-f)*M8uf^p}jf zq739xl!Pq;!05Y)^F`zy=ng)M<)fL`VK!{N9e^wu@t$aM?WQaNP{^GGnJ`o;b*$dh zC)_iN$mfxe0_Gz3V{88|Bs;n>T6q`4lSg5}6jAc~81bb(YoTwjOX!40Lgo^vg5|?8 z-DZ0m%6?r@rM}ES7i2{dspbH>`jY6mI|W~6!m;DHSch%INEO|YUPRRxw)8y?OKRf0 zb`riyL>1X^5by0oYE>s(hez)U=`LSy@?f<#A(DJqYib4@5y5{JFN4P;pq6tj21W10 zL#dtE+_MAc#{Rf~e{1g(cr^7HSdJ&k!i%T|RfHgsCyAq7cZu+FwGDu*NF}{yi25?- zhzML-oR6GLJaW@;@M!!=D3U@(I;cf;jL%*To|j~d6)qCVToEmAS28NNA#u+wGTRM@ z`zp4TfJjo%H8WtW&(lv2$^rLX$AN9TFq%64QF{Z;?j~}{H5i#Xi&!{`vObGUJd1{| z1C zqFtvzT-!eL;;6UmFp?G7X_OkGofxy}AxrSUsFMU6o^Z}zA6>f(j&H$m8fe-TG%O95 zZ;UM1d8l^>Y~2$X5cv)_n+G0WxbL-h zUV`t)#C#Jz4XTgb^CaH9bP}(>`};t$VFxLvQuS0D? z)G4+3nQ25#LUu2;E(xfnR}l*(v47ihn9{~^cJx(z@sWf0@?*~~*wKs%j$ZhIm>_&F z$w6>14gpYPxxV6lfJj>{%~WY)QNYyXP0SUGupJu?)$i1Wm&HAQy?h#<9@>pH>2=6N z?!um-Z%{`p-6_3!$vCmK6#)>)YB66%wOoZslgy>o1Etufj(BV0 zG=BE_cMuBE%vS?0aKpcNPRxAPG%ht6WK@=e zZJdyADuYABWMNiZ762LK*)Tuxr443%PLFh^!~91B*$+}F)14|AmqH=xj$nu2AyBu! zO#A`S9+d!)`cX9`6~Z|OB+^fItSWVu!_47B?_1I#Nv3Pwtr;NMLT9~b{_Fie&un3? z-_hEKw93;O#%z8t2Q?EL69iGVG(AY2_})Xo59SV~-ZrbwMec)^F7Pk@Wz@=m(ps$w zE;%k \ No newline at end of file diff --git a/plugins/markasjunk/localization/et_EE.inc b/plugins/markasjunk/localization/et_EE.inc new file mode 100644 index 0000000..daf1405 --- /dev/null +++ b/plugins/markasjunk/localization/et_EE.inc @@ -0,0 +1,7 @@ + diff --git a/plugins/markasjunk/localization/pl_PL.inc b/plugins/markasjunk/localization/pl_PL.inc new file mode 100644 index 0000000..a98f0aa --- /dev/null +++ b/plugins/markasjunk/localization/pl_PL.inc @@ -0,0 +1,7 @@ + diff --git a/plugins/markasjunk/localization/sv_SE.inc b/plugins/markasjunk/localization/sv_SE.inc new file mode 100644 index 0000000..f4c5959 --- /dev/null +++ b/plugins/markasjunk/localization/sv_SE.inc @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/markasjunk/markasjunk.js b/plugins/markasjunk/markasjunk.js new file mode 100644 index 0000000..8b02d74 --- /dev/null +++ b/plugins/markasjunk/markasjunk.js @@ -0,0 +1,28 @@ +/* Mark-as-Junk plugin script */ + +function rcmail_markasjunk(prop) +{ + if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length)) + return; + + var uids = rcmail.env.uid ? rcmail.env.uid : rcmail.message_list.get_selection().join(','); + + rcmail.set_busy(true, 'loading'); + rcmail.http_post('plugin.markasjunk', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), true); +} + +// callback for app-onload event +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + + // register command (directly enable in message view mode) + rcmail.register_command('plugin.markasjunk', rcmail_markasjunk, rcmail.env.uid); + + // add event-listener to message list + if (rcmail.message_list) + rcmail.message_list.addEventListener('select', function(list){ + rcmail.enable_command('plugin.markasjunk', list.get_selection().length > 0); + }); + }) +} + diff --git a/plugins/markasjunk/markasjunk.php b/plugins/markasjunk/markasjunk.php new file mode 100644 index 0000000..959111d --- /dev/null +++ b/plugins/markasjunk/markasjunk.php @@ -0,0 +1,47 @@ +register_action('plugin.markasjunk', array($this, 'request_action')); + $GLOBALS['IMAP_FLAGS']['JUNK'] = 'Junk'; + + $rcmail = rcmail::get_instance(); + if ($rcmail->action == '' || $rcmail->action == 'show') { + $this->include_script('markasjunk.js'); + $this->add_texts('localization', true); + $this->add_button(array('command' => 'plugin.markasjunk', 'imagepas' => 'junk_pas.png', 'imageact' => 'junk_act.png'), 'toolbar'); + } + } + + function request_action() + { + $this->add_texts('localization'); + + $uids = get_input_value('_uid', RCUBE_INPUT_POST); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); + + $rcmail = rcmail::get_instance(); + $rcmail->imap->set_flag($uids, 'JUNK'); + + if (($junk_mbox = $rcmail->config->get('junk_mbox')) && $mbox != $junk_mbox) { + $rcmail->output->command('move_messages', $junk_mbox); + } + + $rcmail->output->command('display_message', $this->gettext('reportedasjunk'), 'confirmation'); + $rcmail->output->send(); + } + +} \ No newline at end of file diff --git a/plugins/new_user_dialog/localization/de_CH.inc b/plugins/new_user_dialog/localization/de_CH.inc new file mode 100644 index 0000000..d2a1310 --- /dev/null +++ b/plugins/new_user_dialog/localization/de_CH.inc @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/new_user_dialog/localization/de_DE.inc b/plugins/new_user_dialog/localization/de_DE.inc new file mode 100644 index 0000000..d2a1310 --- /dev/null +++ b/plugins/new_user_dialog/localization/de_DE.inc @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/new_user_dialog/localization/en_US.inc b/plugins/new_user_dialog/localization/en_US.inc new file mode 100644 index 0000000..d9f531b --- /dev/null +++ b/plugins/new_user_dialog/localization/en_US.inc @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/new_user_dialog/localization/et_EE.inc b/plugins/new_user_dialog/localization/et_EE.inc new file mode 100644 index 0000000..7c6b3f2 --- /dev/null +++ b/plugins/new_user_dialog/localization/et_EE.inc @@ -0,0 +1,7 @@ + diff --git a/plugins/new_user_dialog/localization/pl_PL.inc b/plugins/new_user_dialog/localization/pl_PL.inc new file mode 100644 index 0000000..a385836 --- /dev/null +++ b/plugins/new_user_dialog/localization/pl_PL.inc @@ -0,0 +1,7 @@ + diff --git a/plugins/new_user_dialog/localization/sv_SE.inc b/plugins/new_user_dialog/localization/sv_SE.inc new file mode 100644 index 0000000..b3e665e --- /dev/null +++ b/plugins/new_user_dialog/localization/sv_SE.inc @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/plugins/new_user_dialog/new_user_dialog.php b/plugins/new_user_dialog/new_user_dialog.php new file mode 100644 index 0000000..4f6250f --- /dev/null +++ b/plugins/new_user_dialog/new_user_dialog.php @@ -0,0 +1,109 @@ +add_hook('create_identity', array($this, 'create_identity')); + + // register additional hooks if session flag is set + if ($_SESSION['plugin.newuserdialog']) { + $this->add_hook('render_page', array($this, 'render_page')); + $this->register_action('plugin.newusersave', array($this, 'save_data')); + } + } + + /** + * Check newly created identity at first login + */ + function create_identity($p) + { + // set session flag when a new user was created and the default identity seems to be incomplete + if ($p['login'] && !$p['complete']) + $_SESSION['plugin.newuserdialog'] = true; + } + + /** + * Callback function when HTML page is rendered + * We'll add an overlay box here. + */ + function render_page($p) + { + if ($_SESSION['plugin.newuserdialog']) { + $this->add_texts('localization'); + + $rcmail = rcmail::get_instance(); + $identity = $rcmail->user->get_identity(); + $identities_level = intval($rcmail->config->get('identities_level', 0)); + + // compose user-identity dialog + $table = new html_table(array('cols' => 2)); + + $table->add('title', $this->gettext('name')); + $table->add(null, html::tag('input', array('type' => "text", 'name' => "_name", 'value' => $identity['name']))); + + $table->add('title', $this->gettext('email')); + $table->add(null, html::tag('input', array('type' => "text", 'name' => "_email", 'value' => $identity['email'], 'disabled' => ($identities_level == 1 || $identities_level == 3)))); + + // add overlay input box to html page + $rcmail->output->add_footer(html::div(array('id' => "newuseroverlay"), + html::tag('form', array( + 'action' => $rcmail->url('plugin.newusersave'), + 'method' => "post"), + html::tag('h3', null, Q($this->gettext('identitydialogtitle'))) . + html::p('hint', Q($this->gettext('identitydialoghint'))) . + $table->show() . + html::p(array('class' => "formbuttons"), + html::tag('input', array('type' => "submit", 'class' => "button mainaction", 'value' => $this->gettext('save')))) + ) + )); + + $this->include_stylesheet('newuserdialog.css'); + } + } + + /** + * Handler for submitted form + * + * Check fields and save to default identity if valid. + * Afterwards the session flag is removed and we're done. + */ + function save_data() + { + $rcmail = rcmail::get_instance(); + $identity = $rcmail->user->get_identity(); + $identities_level = intval($rcmail->config->get('identities_level', 0)); + + $save_data = array( + 'name' => get_input_value('_name', RCUBE_INPUT_POST), + 'email' => get_input_value('_email', RCUBE_INPUT_POST), + ); + + // don't let the user alter the e-mail address if disabled by config + if ($identities_level == 1 || $identities_level == 3) + $save_data['email'] = $identity['email']; + + // save data if not empty + if (!empty($save_data['name']) && !empty($save_data['name'])) { + $rcmail->user->update_identity($identity['identity_id'], $save_data); + rcube_sess_unset('plugin.newuserdialog'); + } + + $rcmail->output->redirect(''); + } + +} + +?> \ No newline at end of file diff --git a/plugins/new_user_dialog/newuserdialog.css b/plugins/new_user_dialog/newuserdialog.css new file mode 100644 index 0000000..c03e6fd --- /dev/null +++ b/plugins/new_user_dialog/newuserdialog.css @@ -0,0 +1,59 @@ +/** Styles for the new-user-dialog overlay box */ + +#newuseroverlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10000; + background: rgba(0,0,0,0.5) !important; + background: #333; + + /** IE hacks */ + filter: alpha(opacity=90); + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; + width: expression(document.documentElement.clientWidth+'px'); + height: expression(document.documentElement.clientHeight+'px'); +} + +#newuseroverlay h3 { + color: #333; + font-size: normal; + margin-top: 0.5em; + margin-bottom: 0; +} + +#newuseroverlay p.hint { + margin-top: 0.5em; + font-style: italic; +} + +#newuseroverlay form { + width: 32em; + margin: 8em auto; + padding: 1em 2em; + background: #F6F6F6; + border: 2px solid #555; + border-radius: 6px; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; +} + +#newuseroverlay table td.title +{ + color: #666; + text-align: right; + padding-right: 1em; + white-space: nowrap; +} + +#newuseroverlay table td input +{ + width: 20em; +} + +#newuseroverlay .formbuttons { + margin-top: 1.5em; + text-align: center; +} \ No newline at end of file diff --git a/plugins/new_user_identity/new_user_identity.php b/plugins/new_user_identity/new_user_identity.php new file mode 100644 index 0000000..7559569 --- /dev/null +++ b/plugins/new_user_identity/new_user_identity.php @@ -0,0 +1,49 @@ +add_hook('create_user', array($this, 'lookup_user_name')); + } + + function lookup_user_name($args) + { + $rcmail = rcmail::get_instance(); + if ($addressbook = $rcmail->config->get('new_user_identity_addressbook')) { + $match = $rcmail->config->get('new_user_identity_match'); + $ldap = $rcmail->get_address_book($addressbook); + $ldap->prop['search_fields'] = array($match); + $results = $ldap->search($match, $args['user'], TRUE); + if (count($results->records) == 1) { + $args['user_name'] = $results->records[0][$rcmail->config->get('new_user_identity_field')]; + } + } + return $args; + } +} +?> diff --git a/plugins/password/README b/plugins/password/README new file mode 100644 index 0000000..033af5f --- /dev/null +++ b/plugins/password/README @@ -0,0 +1,184 @@ + ----------------------------------------------------------------------- + Password Plugin for Roundcube + ----------------------------------------------------------------------- + + Plugin that adds a possibility to change user password using many + methods (drivers) via Settings/Password tab. + + ----------------------------------------------------------------------- + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + @version 1.2 + @author Aleksander 'A.L.E.C' Machniak + @author + ----------------------------------------------------------------------- + + 1. Configuration + 2. Drivers + 2.1. Database (sql) + 2.2. Cyrus/SASL (sasl) + 2.3. Poppassd/Courierpassd (poppassd) + 2.4. LDAP (ldap) + 2.5. DirectAdmin Control Panel + 3. Driver API + + + 1. Configuration + ---------------- + + Copy config.inc.php.dist to config.inc.php and set the options as described + within the file. + + + 2. Drivers + ---------- + + Password plugin supports many password change mechanisms which are + handled by included drivers. Just pass driver name in 'password_driver' option. + + + 2.1. Database (sql) + ------------------- + + You can specify which database to connect by 'password_db_dsn' option and + what SQL query to execute by 'password_query'. See main.inc.php file for + more info. + + Example implementations of an update_passwd function: + + - This is for use with LMS (http://lms.org.pl) database and postgres: + + CREATE OR REPLACE FUNCTION update_passwd(hash text, account text) RETURNS integer AS $$ + DECLARE + res integer; + BEGIN + UPDATE passwd SET password = hash + WHERE login = split_part(account, '@', 1) + AND domainid = (SELECT id FROM domains WHERE name = split_part(account, '@', 2)) + RETURNING id INTO res; + RETURN res; + END; + $$ LANGUAGE plpgsql SECURITY DEFINER; + + - This is for use with a SELECT update_passwd(%o,%c,%u) query + Updates the password only when the old password matches the MD5 password + in the database + + CREATE FUNCTION update_password (oldpass text, cryptpass text, user text) RETURNS text + MODIFIES SQL DATA + BEGIN + DECLARE currentsalt varchar(20); + DECLARE error text; + SET error = 'incorrect current password'; + SELECT substring_index(substr(user.password,4),_latin1'$',1) INTO currentsalt FROM users WHERE username=user; + SELECT '' INTO error FROM users WHERE username=user AND password=ENCRYPT(oldpass,currentsalt); + UPDATE users SET password=cryptpass WHERE username=user AND password=ENCRYPT(oldpass,currentsalt); + RETURN error; + END + + Example SQL UPDATEs: + + - Plain text passwords: + UPDATE users SET password=%p WHERE username=%u AND password=%o AND domain=%h LIMIT 1 + + - Crypt text passwords: + UPDATE users SET password=%c WHERE username=%u LIMIT 1 + + - Use a MYSQL crypt function (*nix only) with random 8 character salt + UPDATE users SET password=ENCRYPT(%p,concat(_utf8'$1$',right(md5(rand()),8),_utf8'$')) WHERE username=%u LIMIT 1 + + - MD5 stored passwords: + UPDATE users SET password=MD5(%p) WHERE username=%u AND password=MD5(%o) LIMIT 1 + + + 2.2. Cyrus/SASL (sasl) + ---------------------- + + Cyrus SASL database authentication allows your Cyrus+RoundCube + installation to host mail users without requiring a Unix Shell account! + + This driver only covers the "sasldb" case when using Cyrus SASL. Kerberos + and PAM authentication mechanisms will require other techniques to enable + user password manipulations. + + Cyrus SASL includes a shell utility called "saslpasswd" for manipulating + user passwords in the "sasldb" database. This plugin attempts to use + this utility to perform password manipulations required by your webmail + users without any administrative interaction. Unfortunately, this + scheme requires that the "saslpasswd" utility be run as the "cyrus" + user - kind of a security problem since we have chosen to SUID a small + script which will allow this to happen. + + This driver is based on the Squirrelmail Change SASL Password Plugin. + See http://www.squirrelmail.org/plugin_view.php?id=107 for details. + + Installation: + + Change into the drivers directory. Edit the chgsaslpasswd.c file as is + documented within it. + + Compile the wrapper program: + gcc -o chgsaslpasswd chgsaslpasswd.c + + Chown the compiled chgsaslpasswd binary to the cyrus user and group + that your browser runs as, then chmod them to 4550. + + For example, if your cyrus user is 'cyrus' and the apache server group is + 'nobody' (I've been told Redhat runs Apache as user 'apache'): + + chown cyrus:nobody chgsaslpasswd + chmod 4550 chgsaslpasswd + + Stephen Carr has suggested users should try to run the scripts on a test + account as the cyrus user eg; + + su cyrus -c "./chgsaslpasswd -p test_account" + + This will allow you to make sure that the script will work for your setup. + Should the script not work, make sure that: + 1) the user the script runs as has access to the saslpasswd|saslpasswd2 + file and proper permissions + 2) make sure the user in the chgsaslpasswd.c file is set correctly. + This could save you some headaches if you are the paranoid type. + + + 2.3. Poppassd/Courierpassd (poppassd) + ------------------------------------- + + You can specify which host to connect to via `password_pop_host` and + what port via `password_pop_port`. See config.inc.php file for more info. + + + 2.4. LDAP (ldap) + ---------------- + + See config.inc.php file. Requires PEAR::Net_LDAP2 package. + + + 2.5. DirectAdmin Control Panel + ------------------------------------- + + You can specify which host to connect to via `password_directadmin_host` + and what port via `password_direactadmin_port`. See config.inc.php file + for more info. + + + 3. Driver API + ------------- + + Driver file (.php) must define 'password_save' function with + two arguments. First - current password, second - new password. Function + may return PASSWORD_SUCCESS on success or any of PASSWORD_CONNECT_ERROR, + PASSWORD_CRYPT_ERROR, PASSWORD_ERROR when driver was unable to change password. + See existing drivers in drivers/ directory for examples. diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist new file mode 100644 index 0000000..076cfd6 --- /dev/null +++ b/plugins/password/config.inc.php.dist @@ -0,0 +1,142 @@ + diff --git a/plugins/password/drivers/chgsaslpasswd.c b/plugins/password/drivers/chgsaslpasswd.c new file mode 100644 index 0000000..bcdcb2e --- /dev/null +++ b/plugins/password/drivers/chgsaslpasswd.c @@ -0,0 +1,29 @@ +#include +#include + +// set the UID this script will run as (cyrus user) +#define UID 96 +// set the path to saslpasswd or saslpasswd2 +#define CMD "/usr/sbin/saslpasswd2" + +/* INSTALLING: + gcc -o chgsaslpasswd chgsaslpasswd.c + chown cyrus.apache chgsaslpasswd + strip chgsaslpasswd + chmod 4550 chgsaslpasswd +*/ + +main(int argc, char *argv[]) +{ + int rc,cc; + + cc = setuid(UID); + rc = execvp(CMD, argv); + if ((rc != 0) || (cc != 0)) + { + fprintf(stderr, "__ %s: failed %d %d\n", argv[0], rc, cc); + return 1; + } + + return 0; +} diff --git a/plugins/password/drivers/directadmin.php b/plugins/password/drivers/directadmin.php new file mode 100644 index 0000000..d11aae7 --- /dev/null +++ b/plugins/password/drivers/directadmin.php @@ -0,0 +1,483 @@ + + * + */ + + +function password_save($curpass, $passwd){ + + $rcmail = rcmail::get_instance(); + $Socket = new HTTPSocket; + + $da_user = $_SESSION['username']; + $da_curpass = $curpass; + $da_newpass = $passwd; + $da_host = $rcmail->config->get('password_directadmin_host'); + $da_port = $rcmail->config->get('password_directadmin_port'); + + $Socket->connect($da_host,$da_port); + $Socket->set_method('POST'); + $Socket->query('/CMD_CHANGE_EMAIL_PASSWORD', + array( + 'email' => $da_user, + 'oldpassword' => $da_curpass, + 'password1' => $da_newpass, + 'password2' => $da_newpass, + 'api' => '1' + )); + $response = $Socket->fetch_parsed_body(); + + //console("DA error response: $response[text] [$da_user]"); + + if($Socket->result_status_code <> 200) + return PASSWORD_CONNECT_ERROR; + elseif($response['error'] == 1){ //Error description: $response[text] + return PASSWORD_ERROR; + }else + return PASSWORD_SUCCESS; + +} + + +/** + * Socket communication class. + * + * Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need. + * + * Very, very basic usage: + * $Socket = new HTTPSocket; + * echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that'); + * + * @author Phi1 'l0rdphi1' Stier + * @package HTTPSocket + * @version 2.6 + */ +class HTTPSocket { + + var $version = '2.6'; + + /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */ + + var $method = 'GET'; + + var $remote_host; + var $remote_port; + var $remote_uname; + var $remote_passwd; + + var $result; + var $result_header; + var $result_body; + var $result_status_code; + + var $lastTransferSpeed; + + var $bind_host; + + var $error = array(); + var $warn = array(); + var $query_cache = array(); + + var $doFollowLocationHeader = TRUE; + var $redirectURL; + + var $extra_headers = array(); + + /** + * Create server "connection". + * + */ + function connect($host, $port = '' ) + { + if (!is_numeric($port)) + { + $port = 80; + } + + $this->remote_host = $host; + $this->remote_port = $port; + } + + function bind( $ip = '' ) + { + if ( $ip == '' ) + { + $ip = $_SERVER['SERVER_ADDR']; + } + + $this->bind_host = $ip; + } + + /** + * Change the method being used to communicate. + * + * @param string|null request method. supports GET, POST, and HEAD. default is GET + */ + function set_method( $method = 'GET' ) + { + $this->method = strtoupper($method); + } + + /** + * Specify a username and password. + * + * @param string|null username. defualt is null + * @param string|null password. defualt is null + */ + function set_login( $uname = '', $passwd = '' ) + { + if ( strlen($uname) > 0 ) + { + $this->remote_uname = $uname; + } + + if ( strlen($passwd) > 0 ) + { + $this->remote_passwd = $passwd; + } + + } + + /** + * Query the server + * + * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too. + * @param string|array query to pass to url + * @param int if connection KB/s drops below value here, will drop connection + */ + function query( $request, $content = '', $doSpeedCheck = 0 ) + { + $this->error = $this->warn = array(); + $this->result_status_code = NULL; + + // is our request a http:// ... ? + if (preg_match('!^http://!i',$request)) + { + $location = parse_url($request); + $this->connect($location['host'],$location['port']); + $this->set_login($location['user'],$location['pass']); + + $request = $location['path']; + $content = $location['query']; + + if ( strlen($request) < 1 ) + { + $request = '/'; + } + + } + + $array_headers = array( + 'User-Agent' => "HTTPSocket/$this->version", + 'Host' => ( $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port" ), + 'Accept' => '*/*', + 'Connection' => 'Close' ); + + foreach ( $this->extra_headers as $key => $value ) + { + $array_headers[$key] = $value; + } + + $this->result = $this->result_header = $this->result_body = ''; + + // was content sent as an array? if so, turn it into a string + if (is_array($content)) + { + $pairs = array(); + + foreach ( $content as $key => $value ) + { + $pairs[] = "$key=".urlencode($value); + } + + $content = join('&',$pairs); + unset($pairs); + } + + $OK = TRUE; + + // instance connection + if ($this->bind_host) + { + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_bind($socket,$this->bind_host); + + if (!@socket_connect($socket,$this->remote_host,$this->remote_port)) + { + $OK = FALSE; + } + + } + else + { + $socket = @fsockopen( $this->remote_host, $this->remote_port, $sock_errno, $sock_errstr, 10 ); + } + + if ( !$socket || !$OK ) + { + $this->error[] = "Can't create socket connection to $this->remote_host:$this->remote_port."; + return 0; + } + + // if we have a username and password, add the header + if ( isset($this->remote_uname) && isset($this->remote_passwd) ) + { + $array_headers['Authorization'] = 'Basic '.base64_encode("$this->remote_uname:$this->remote_passwd"); + } + + // for DA skins: if $this->remote_passwd is NULL, try to use the login key system + if ( isset($this->remote_uname) && $this->remote_passwd == NULL ) + { + $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}"; + } + + // if method is POST, add content length & type headers + if ( $this->method == 'POST' ) + { + $array_headers['Content-type'] = 'application/x-www-form-urlencoded'; + $array_headers['Content-length'] = strlen($content); + } + // else method is GET or HEAD. we don't support anything else right now. + else + { + if ($content) + { + $request .= "?$content"; + } + } + + // prepare query + $query = "$this->method $request HTTP/1.0\r\n"; + foreach ( $array_headers as $key => $value ) + { + $query .= "$key: $value\r\n"; + } + $query .= "\r\n"; + + // if POST we need to append our content + if ( $this->method == 'POST' && $content ) + { + $query .= "$content\r\n\r\n"; + } + + // query connection + if ($this->bind_host) + { + socket_write($socket,$query); + + // now load results + while ( $out = socket_read($socket,2048) ) + { + $this->result .= $out; + } + } + else + { + fwrite( $socket, $query, strlen($query) ); + + // now load results + $this->lastTransferSpeed = 0; + $status = socket_get_status($socket); + $startTime = time(); + $length = 0; + $prevSecond = 0; + while ( !feof($socket) && !$status['timed_out'] ) + { + $chunk = fgets($socket,1024); + $length += strlen($chunk); + $this->result .= $chunk; + + $elapsedTime = time() - $startTime; + + if ( $elapsedTime > 0 ) + { + $this->lastTransferSpeed = ($length/1024)/$elapsedTime; + } + + if ( $doSpeedCheck > 0 && $elapsedTime > 5 && $this->lastTransferSpeed < $doSpeedCheck ) + { + $this->warn[] = "kB/s for last 5 seconds is below 50 kB/s (~".( ($length/1024)/$elapsedTime )."), dropping connection..."; + $this->result_status_code = 503; + break; + } + + } + + if ( $this->lastTransferSpeed == 0 ) + { + $this->lastTransferSpeed = $length/1024; + } + + } + + list($this->result_header,$this->result_body) = split("\r\n\r\n",$this->result,2); + + if ($this->bind_host) + { + socket_close($socket); + } + else + { + fclose($socket); + } + + $this->query_cache[] = $query; + + + $headers = $this->fetch_header(); + + // what return status did we get? + if (!$this->result_status_code) + { + preg_match("#HTTP/1\.. (\d+)#",$headers[0],$matches); + $this->result_status_code = $matches[1]; + } + + // did we get the full file? + if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) ) + { + $this->result_status_code = 206; + } + + // now, if we're being passed a location header, should we follow it? + if ($this->doFollowLocationHeader) + { + if ($headers['location']) + { + $this->redirectURL = $headers['location']; + $this->query($headers['location']); + } + } + + } + + function getTransferSpeed() + { + return $this->lastTransferSpeed; + } + + /** + * The quick way to get a URL's content :) + * + * @param string URL + * @param boolean return as array? (like PHP's file() command) + * @return string result body + */ + function get($location, $asArray = FALSE ) + { + $this->query($location); + + if ( $this->get_status_code() == 200 ) + { + if ($asArray) + { + return split("\n",$this->fetch_body()); + } + + return $this->fetch_body(); + } + + return FALSE; + } + + /** + * Returns the last status code. + * 200 = OK; + * 403 = FORBIDDEN; + * etc. + * + * @return int status code + */ + function get_status_code() + { + return $this->result_status_code; + } + + /** + * Adds a header, sent with the next query. + * + * @param string header name + * @param string header value + */ + function add_header($key,$value) + { + $this->extra_headers[$key] = $value; + } + + /** + * Clears any extra headers. + * + */ + function clear_headers() + { + $this->extra_headers = array(); + } + + /** + * Return the result of a query. + * + * @return string result + */ + function fetch_result() + { + return $this->result; + } + + /** + * Return the header of result (stuff before body). + * + * @param string (optional) header to return + * @return array result header + */ + function fetch_header( $header = '' ) + { + $array_headers = split("\r\n",$this->result_header); + + $array_return = array( 0 => $array_headers[0] ); + unset($array_headers[0]); + + foreach ( $array_headers as $pair ) + { + list($key,$value) = split(": ",$pair,2); + $array_return[strtolower($key)] = $value; + } + + if ( $header != '' ) + { + return $array_return[strtolower($header)]; + } + + return $array_return; + } + + /** + * Return the body of result (stuff after header). + * + * @return string result body + */ + function fetch_body() + { + return $this->result_body; + } + + /** + * Return parsed body in array format. + * + * @return array result parsed + */ + function fetch_parsed_body() + { + parse_str($this->result_body,$x); + return $x; + } + +} + +?> diff --git a/plugins/password/drivers/ldap.php b/plugins/password/drivers/ldap.php new file mode 100644 index 0000000..e38f13f --- /dev/null +++ b/plugins/password/drivers/ldap.php @@ -0,0 +1,186 @@ + + * + * function hashPassword based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/). + * function randomSalt based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/). + * + */ + +function password_save($curpass, $passwd) +{ + $rcmail = rcmail::get_instance(); + require_once ('Net/LDAP2.php'); + + // Building user DN + $userDN = str_replace('%login', $_SESSION['username'], $rcmail->config->get('password_ldap_userDN_mask')); + + $parts = explode('@', $_SESSION['username']); + if (count($parts) == 2) + { + $userDN = str_replace('%name', $parts[0], $userDN); + $userDN = str_replace('%domain', $parts[1], $userDN); + } + + if (empty($userDN)) {return PASSWORD_CONNECT_ERROR;} + + // Connection Method + switch($rcmail->config->get('password_ldap_method')) { + case 'user': $binddn = $userDN; $bindpw = $curpass; break; + case 'admin': $binddn = $rcmail->config->get('password_ldap_adminDN'); $bindpw = $rcmail->config->get('password_ldap_adminPW'); break; + default: $binddn = $userDN; $bindpw = $curpass; break; // default is user mode + } + + // Configuration array + $ldapConfig = array ( + 'binddn' => $binddn, + 'bindpw' => $bindpw, + 'basedn' => $rcmail->config->get('password_ldap_basedn'), + 'host' => $rcmail->config->get('password_ldap_host'), + 'port' => $rcmail->config->get('password_ldap_port'), + 'starttls' => $rcmail->config->get('password_ldap_starttls'), + 'version' => $rcmail->config->get('password_ldap_version'), + ); + + // Connecting using the configuration array + $ldap = Net_LDAP2::connect($ldapConfig); + + // Checking for connection error + if (PEAR::isError($ldap)) {return PASSWORD_CONNECT_ERROR;} + + // Crypting new password + $newCryptedPassword = hashPassword($passwd, $rcmail->config->get('password_ldap_encodage')); + if (!$newCryptedPassword) {return PASSWORD_CRYPT_ERROR;} + + // Writing new crypted password to LDAP + $userEntry = $ldap->getEntry($userDN); + if (Net_LDAP2::isError($userEntry)) {return PASSWORD_CONNECT_ERROR;} + if (!$userEntry->replace(array($rcmail->config->get('password_ldap_pwattr') => $newCryptedPassword),$rcmail->config->get('password_ldap_force_replace'))) {return PASSWORD_CONNECT_ERROR;} + if (Net_LDAP2::isError($userEntry->update())) {return PASSWORD_CONNECT_ERROR;} + + // All done, no error + return PASSWORD_SUCCESS; +} + + +/** + * Code originaly from the phpLDAPadmin development team + * http://phpldapadmin.sourceforge.net/ + * + * Hashes a password and returns the hash based on the specified enc_type. + * + * @param string $passwordClear The password to hash in clear text. + * @param string $encodageType Standard LDAP encryption type which must be one of + * crypt, ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear. + * @return string The hashed password. + * + */ + +function hashPassword( $passwordClear, $encodageType ) +{ + $encodageType = strtolower( $encodageType ); + switch( $encodageType ) { + case 'crypt': + $cryptedPassword = '{CRYPT}' . crypt($passwordClear,randomSalt(2)); + break; + + case 'ext_des': + // extended des crypt. see OpenBSD crypt man page. + if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {return FALSE;} //Your system crypt library does not support extended DES encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear, '_' . randomSalt(8) ); + break; + + case 'md5crypt': + if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {return FALSE;} //Your system crypt library does not support md5crypt encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$1$' . randomSalt(9) ); + break; + + case 'blowfish': + if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {return FALSE;} //Your system crypt library does not support blowfish encryption. + $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$2a$12$' . randomSalt(13) ); // hardcoded to second blowfish version and set number of rounds + break; + + case 'md5': + $cryptedPassword = '{MD5}' . base64_encode( pack( 'H*' , md5( $passwordClear) ) ); + break; + + case 'sha': + if( function_exists('sha1') ) { + // use php 4.3.0+ sha1 function, if it is available. + $cryptedPassword = '{SHA}' . base64_encode( pack( 'H*' , sha1( $passwordClear) ) ); + } elseif( function_exists( 'mhash' ) ) { + $cryptedPassword = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $passwordClear) ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'ssha': + if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) { + mt_srand( (double) microtime() * 1000000 ); + $salt = mhash_keygen_s2k( MHASH_SHA1, $passwordClear, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 ); + $cryptedPassword = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $passwordClear.$salt ).$salt ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'smd5': + if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) { + mt_srand( (double) microtime() * 1000000 ); + $salt = mhash_keygen_s2k( MHASH_MD5, $passwordClear, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 ); + $cryptedPassword = "{SMD5}".base64_encode( mhash( MHASH_MD5, $passwordClear.$salt ).$salt ); + } else { + return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes. + } + break; + + case 'clear': + default: + $cryptedPassword = $passwordClear; + } + + return $cryptedPassword; +} + + + +/** + * Code originaly from the phpLDAPadmin development team + * http://phpldapadmin.sourceforge.net/ + * + * Used to generate a random salt for crypt-style passwords. Salt strings are used + * to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses + * not only the user's password but also a randomly generated string. The string is + * stored as the first N characters of the hash for reference of hashing algorithms later. + * + * --- added 20021125 by bayu irawan --- + * --- ammended 20030625 by S C Rigler --- + * + * @param int $length The length of the salt string to generate. + * @return string The generated salt string. + */ + +function randomSalt( $length ) +{ + $possible = '0123456789'. + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + './'; + $str = ""; + mt_srand((double)microtime() * 1000000); + + while( strlen( $str ) < $length ) + $str .= substr( $possible, ( rand() % strlen( $possible ) ), 1 ); + + return $str; +} + +?> diff --git a/plugins/password/drivers/poppassd.php b/plugins/password/drivers/poppassd.php new file mode 100644 index 0000000..8a54fb7 --- /dev/null +++ b/plugins/password/drivers/poppassd.php @@ -0,0 +1,56 @@ +connect($rcmail->config->get('password_pop_host'), $rcmail->config->get('password_pop_port'), null))) { + return PASSWORD_CONNECT_ERROR; + } + else { + $result = $poppassd->readLine(); + if(!preg_match('/^2\d\d/', $result)) { + $poppassd->disconnect(); + return PASSWORD_ERROR; + } + else { + $poppassd->writeLine("user ". $_SESSION['username']); + $result = $poppassd->readLine(); + if(!preg_match('/^[23]\d\d/', $result) ) { + $poppassd->disconnect(); + return PASSWORD_CONNECT_ERROR; + } + else { + $poppassd->writeLine("pass ". $curpass); + $result = $poppassd->readLine(); + if(!preg_match('/^[23]\d\d/', $result) ) { + $poppassd->disconnect(); + return PASSWORD_ERROR; + } + else { + $poppassd->writeLine("newpass ". $passwd); + $result = $poppassd->readLine(); + $poppassd->disconnect(); + if (!preg_match('/^2\d\d/', $result)) + return PASSWORD_ERROR; + else + return PASSWORD_SUCCESS; + } + } + } + } +} + +?> diff --git a/plugins/password/drivers/sasl.php b/plugins/password/drivers/sasl.php new file mode 100644 index 0000000..b1e9ba4 --- /dev/null +++ b/plugins/password/drivers/sasl.php @@ -0,0 +1,44 @@ +config->get('password_saslpasswd_args', ''); + + if ($fh = popen("$curdir/chgsaslpasswd -p $args $username", 'w')) { + fwrite($fh, $newpass."\n"); + $code = pclose($fh); + + if ($code == 0) + return PASSWORD_SUCCESS; + } + else { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Password plugin: Unable to execute $curdir/chgsaslpasswd" + ), true, false); + } + + return PASSWORD_ERROR; +} + +?> diff --git a/plugins/password/drivers/sql.php b/plugins/password/drivers/sql.php new file mode 100644 index 0000000..9afaa65 --- /dev/null +++ b/plugins/password/drivers/sql.php @@ -0,0 +1,107 @@ + + * + */ + +function password_save($curpass, $passwd) +{ + $rcmail = rcmail::get_instance(); + + if (!($sql = $rcmail->config->get('password_query'))) + $sql = 'SELECT update_passwd(%c, %u)'; + + if ($dsn = $rcmail->config->get('password_db_dsn')) { + // #1486067: enable new_link option + if (is_array($dsn) && empty($dsn['new_link'])) + $dsn['new_link'] = true; + else if (!is_array($dsn) && !preg_match('/\?new_link=true/', $dsn)) + $dsn .= '?new_link=true'; + + $db = new rcube_mdb2($dsn, '', FALSE); + $db->set_debug((bool)$rcmail->config->get('sql_debug')); + $db->db_connect('w'); + } else { + $db = $rcmail->get_dbh(); + } + + if ($err = $db->is_error()) + return PASSWORD_ERROR; + + // crypted password + if (strpos($sql, '%c') !== FALSE) { + $salt = ''; + if (CRYPT_MD5) { + $len = rand(3, CRYPT_SALT_LENGTH); + } else if (CRYPT_STD_DES) { + $len = 2; + } else { + return PASSWORD_CRYPT_ERROR; + } + for ($i = 0; $i < $len ; $i++) { + $salt .= chr(rand(ord('.'), ord('z'))); + } + $sql = str_replace('%c', $db->quote(crypt($passwd, CRYPT_MD5 ? '$1$'.$salt.'$' : $salt)), $sql); + } + + // hashed passwords + if (preg_match('/%[n|q]/', $sql)) { + + if (!extension_loaded('hash')) { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Password plugin: 'hash' extension not loaded!" + ), true, false); + return PASSWORD_ERROR; + } + + if (!($hash_algo = strtolower($rcmail->config->get('password_hash_algorithm')))) + $hash_algo = 'sha1'; + + $hash_passwd = hash($hash_algo, $passwd); + $hash_curpass = hash($hash_algo, $curpass); + + if ($rcmail->config->get('password_hash_base64')) { + $hash_passwd = base64_encode(pack('H*', $hash_passwd)); + $hash_curpass = base64_encode(pack('H*', $hash_curpass)); + } + + $sql = str_replace('%n', $db->quote($hash_passwd, 'text'), $sql); + $sql = str_replace('%q', $db->quote($hash_curpass, 'text'), $sql); + } + + $user_info = explode('@', $_SESSION['username']); + if (count($user_info) >= 2) { + $sql = str_replace('%l', $db->quote($user_info[0], 'text'), $sql); + $sql = str_replace('%d', $db->quote($user_info[0], 'text'), $sql); + } + + $sql = str_replace('%u', $db->quote($_SESSION['username'],'text'), $sql); + $sql = str_replace('%h', $db->quote($_SESSION['imap_host'],'text'), $sql); + $sql = str_replace('%p', $db->quote($passwd,'text'), $sql); + $sql = str_replace('%o', $db->quote($curpass,'text'), $sql); + + $res = $db->query($sql); + + if (!$db->is_error()) { + if (strtolower(substr(trim($query),0,6))=='select') { + if ($result = $db->fetch_array($res)) + return PASSWORD_SUCCESS; + } else { + if ($db->affected_rows($res) == 1) + return PASSWORD_SUCCESS; // This is the good case: 1 row updated + } + } + + return PASSWORD_ERROR; +} + +?> diff --git a/plugins/password/localization/de_CH.inc b/plugins/password/localization/de_CH.inc new file mode 100644 index 0000000..a28990d --- /dev/null +++ b/plugins/password/localization/de_CH.inc @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/plugins/password/localization/de_DE.inc b/plugins/password/localization/de_DE.inc new file mode 100644 index 0000000..a28990d --- /dev/null +++ b/plugins/password/localization/de_DE.inc @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/plugins/password/localization/en_US.inc b/plugins/password/localization/en_US.inc new file mode 100644 index 0000000..6c64cc0 --- /dev/null +++ b/plugins/password/localization/en_US.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/et_EE.inc b/plugins/password/localization/et_EE.inc new file mode 100644 index 0000000..0f351d7 --- /dev/null +++ b/plugins/password/localization/et_EE.inc @@ -0,0 +1,17 @@ + diff --git a/plugins/password/localization/fr_FR.inc b/plugins/password/localization/fr_FR.inc new file mode 100644 index 0000000..8ba37b1 --- /dev/null +++ b/plugins/password/localization/fr_FR.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/hu_HU.inc b/plugins/password/localization/hu_HU.inc new file mode 100644 index 0000000..c8c3015 --- /dev/null +++ b/plugins/password/localization/hu_HU.inc @@ -0,0 +1,17 @@ + diff --git a/plugins/password/localization/it_IT.inc b/plugins/password/localization/it_IT.inc new file mode 100644 index 0000000..b33d8d5 --- /dev/null +++ b/plugins/password/localization/it_IT.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/nl_NL.inc b/plugins/password/localization/nl_NL.inc new file mode 100644 index 0000000..6d7c401 --- /dev/null +++ b/plugins/password/localization/nl_NL.inc @@ -0,0 +1,17 @@ + diff --git a/plugins/password/localization/pl_PL.inc b/plugins/password/localization/pl_PL.inc new file mode 100644 index 0000000..4520d79 --- /dev/null +++ b/plugins/password/localization/pl_PL.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/pt_BR.inc b/plugins/password/localization/pt_BR.inc new file mode 100644 index 0000000..c196d75 --- /dev/null +++ b/plugins/password/localization/pt_BR.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/pt_PT.inc b/plugins/password/localization/pt_PT.inc new file mode 100644 index 0000000..80ddf58 --- /dev/null +++ b/plugins/password/localization/pt_PT.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/sl_SI.inc b/plugins/password/localization/sl_SI.inc new file mode 100644 index 0000000..df17583 --- /dev/null +++ b/plugins/password/localization/sl_SI.inc @@ -0,0 +1,18 @@ + diff --git a/plugins/password/localization/sv_SE.inc b/plugins/password/localization/sv_SE.inc new file mode 100644 index 0000000..5d9398e --- /dev/null +++ b/plugins/password/localization/sv_SE.inc @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/plugins/password/password.js b/plugins/password/password.js new file mode 100644 index 0000000..c3252f0 --- /dev/null +++ b/plugins/password/password.js @@ -0,0 +1,36 @@ +/* Password change interface (tab) */ + +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + // + var tab = $('').attr('id', 'settingstabpluginpassword').addClass('tablink'); + + var button = $('
').attr('href', rcmail.env.comm_path+'&_action=plugin.password').html(rcmail.gettext('password')).appendTo(tab); + button.bind('click', function(e){ return rcmail.command('plugin.password', this) }); + + // add button and register commands + rcmail.add_element(tab, 'tabs'); + rcmail.register_command('plugin.password', function() { rcmail.goto_url('plugin.password') }, true); + rcmail.register_command('plugin.password-save', function() { + var input_curpasswd = rcube_find_object('_curpasswd'); + var input_newpasswd = rcube_find_object('_newpasswd'); + var input_confpasswd = rcube_find_object('_confpasswd'); + + if (input_curpasswd && input_curpasswd.value=='') { + alert(rcmail.gettext('nocurpassword', 'password')); + input_curpasswd.focus(); + } else if (input_newpasswd && input_newpasswd.value=='') { + alert(rcmail.gettext('nopassword', 'password')); + input_newpasswd.focus(); + } else if (input_confpasswd && input_confpasswd.value=='') { + alert(rcmail.gettext('nopassword', 'password')); + input_confpasswd.focus(); + } else if ((input_newpasswd && input_confpasswd) && (input_newpasswd.value != input_confpasswd.value)) { + alert(rcmail.gettext('passwordinconsistency', 'password')); + input_newpasswd.focus(); + } else { + rcmail.gui_objects.passform.submit(); + } + }, true); + }) +} diff --git a/plugins/password/password.php b/plugins/password/password.php new file mode 100644 index 0000000..0e5e1ef --- /dev/null +++ b/plugins/password/password.php @@ -0,0 +1,212 @@ + | + +-------------------------------------------------------------------------+ + + $Id: index.php 2645 2009-06-15 07:01:36Z alec $ + +*/ + +define('PASSWORD_CRYPT_ERROR', 1); +define('PASSWORD_ERROR', 2); +define('PASSWORD_CONNECT_ERROR', 3); +define('PASSWORD_SUCCESS', 0); + +/** + * Change password plugin + * + * Plugin that adds functionality to change a users password. + * It provides common functionality and user interface and supports + * several backends to finally update the password. + * + * For installation and configuration instructions please read the README file. + * + * @version 1.3.1 + * @author Aleksander Machniak + */ +class password extends rcube_plugin +{ + public $task = 'settings'; + + function init() + { + $rcmail = rcmail::get_instance(); + // add Tab label + $rcmail->output->add_label('password'); + $this->register_action('plugin.password', array($this, 'password_init')); + $this->register_action('plugin.password-save', array($this, 'password_save')); + $this->include_script('password.js'); + } + + function password_init() + { + $this->add_texts('localization/'); + $this->register_handler('plugin.body', array($this, 'password_form')); + + $rcmail = rcmail::get_instance(); + $rcmail->output->set_pagetitle($this->gettext('changepasswd')); + $rcmail->output->send('plugin'); + } + + function password_save() + { + $rcmail = rcmail::get_instance(); + $this->load_config(); + + $this->add_texts('localization/'); + $this->register_handler('plugin.body', array($this, 'password_form')); + $rcmail->output->set_pagetitle($this->gettext('changepasswd')); + + $confirm = $rcmail->config->get('password_confirm_current'); + + if (($confirm && !isset($_POST['_curpasswd'])) || !isset($_POST['_newpasswd'])) { + $rcmail->output->command('display_message', $this->gettext('nopassword'), 'error'); + } + else { + $curpwd = get_input_value('_curpasswd', RCUBE_INPUT_POST); + $newpwd = get_input_value('_newpasswd', RCUBE_INPUT_POST); + + if ($confirm && $rcmail->decrypt($_SESSION['password']) != $curpwd) + $rcmail->output->command('display_message', $this->gettext('passwordincorrect'), 'error'); + else if (!($res = $this->_save($curpwd,$newpwd))) { + $rcmail->output->command('display_message', $this->gettext('successfullysaved'), 'confirmation'); + $_SESSION['password'] = $rcmail->encrypt($newpwd); + } else + $rcmail->output->command('display_message', $res, 'error'); + } + + rcmail_overwrite_action('plugin.password'); + $rcmail->output->send('plugin'); + } + + function password_form() + { + $rcmail = rcmail::get_instance(); + $this->load_config(); + + // add some labels to client + $rcmail->output->add_label( + 'password.nopassword', + 'password.nocurpassword', + 'password.passwordinconsistency' + ); + + $rcmail->output->set_env('product_name', $rcmail->config->get('product_name')); + + $table = new html_table(array('cols' => 2)); + + if ($rcmail->config->get('password_confirm_current')) { + // show current password selection + $field_id = 'curpasswd'; + $input_curpasswd = new html_passwordfield(array('name' => '_curpasswd', 'id' => $field_id, + 'size' => 20, 'autocomplete' => 'off')); + + $table->add('title', html::label($field_id, Q($this->gettext('curpasswd')))); + $table->add(null, $input_curpasswd->show()); + } + + // show new password selection + $field_id = 'newpasswd'; + $input_newpasswd = new html_passwordfield(array('name' => '_newpasswd', 'id' => $field_id, + 'size' => 20, 'autocomplete' => 'off')); + + $table->add('title', html::label($field_id, Q($this->gettext('newpasswd')))); + $table->add(null, $input_newpasswd->show()); + + // show confirm password selection + $field_id = 'confpasswd'; + $input_confpasswd = new html_passwordfield(array('name' => '_confpasswd', 'id' => $field_id, + 'size' => 20, 'autocomplete' => 'off')); + + $table->add('title', html::label($field_id, Q($this->gettext('confpasswd')))); + $table->add(null, $input_confpasswd->show()); + + $out = html::div(array('class' => "settingsbox", 'style' => "margin:0"), + html::div(array('id' => "prefs-title"), $this->gettext('changepasswd')) . + html::div(array('style' => "padding:15px"), $table->show() . + html::p(null, + $rcmail->output->button(array( + 'command' => 'plugin.password-save', + 'type' => 'input', + 'class' => 'button mainaction', + 'label' => 'save' + ))) + ) + ); + + $rcmail->output->add_gui_object('passform', 'password-form'); + + return $rcmail->output->form_tag(array( + 'id' => 'password-form', + 'name' => 'password-form', + 'method' => 'post', + 'action' => './?_task=settings&_action=plugin.password-save', + ), $out); + } + + private function _save($curpass, $passwd) + { + $config = rcmail::get_instance()->config; + $driver = $this->home.'/drivers/'.$config->get('password_driver', 'sql').'.php'; + + if (!is_readable($driver)) { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Password plugin: Unable to open driver file $driver" + ), true, false); + return $this->gettext('internalerror'); + } + + include($driver); + + if (!function_exists('password_save')) { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Password plugin: Broken driver: $driver" + ), true, false); + return $this->gettext('internalerror'); + } + + $result = password_save($curpass, $passwd); + + switch ($result) { + case PASSWORD_SUCCESS: + return; + case PASSWORD_CRYPT_ERROR; + return $this->gettext('crypterror'); + case PASSWORD_CONNECT_ERROR; + return $this->gettext('connecterror'); + case PASSWORD_ERROR: + default: + return $this->gettext('internalerror'); + } + } + +} + +?> diff --git a/plugins/show_additional_headers/show_additional_headers.php b/plugins/show_additional_headers/show_additional_headers.php new file mode 100644 index 0000000..7e7c503 --- /dev/null +++ b/plugins/show_additional_headers/show_additional_headers.php @@ -0,0 +1,52 @@ +action == 'show' || $rcmail->action == 'preview') { + $this->add_hook('imap_init', array($this, 'imap_init')); + $this->add_hook('message_headers_output', array($this, 'message_headers')); + } else if ($rcmail->action == '') { + // with enabled_caching we're fetching additional headers before show/preview + $this->add_hook('imap_init', array($this, 'imap_init')); + } + } + + function imap_init($p) + { + $rcmail = rcmail::get_instance(); + if ($add_headers = $rcmail->config->get('show_additional_headers', array())) + $p['fetch_headers'] = trim($p['fetch_headers'].' ' . strtoupper(join(' ', $add_headers))); + + return $p; + } + + function message_headers($p) + { + $rcmail = rcmail::get_instance(); + foreach ($rcmail->config->get('show_additional_headers', array()) as $header) { + $key = strtolower($header); + if ($value = $p['headers']->others[$key]) + $p['output'][$key] = array('title' => $header, 'value' => $value); + } + + return $p; + } +} diff --git a/plugins/squirrelmail_usercopy/config.inc.php.dist b/plugins/squirrelmail_usercopy/config.inc.php.dist new file mode 100644 index 0000000..5c2560f --- /dev/null +++ b/plugins/squirrelmail_usercopy/config.inc.php.dist @@ -0,0 +1,5 @@ +add_hook('create_user', array($this, 'create_user')); + $this->add_hook('create_identity', array($this, 'create_identity')); + } + + public function create_user($p) + { + // read prefs and add email address + $this->read_squirrel_prefs($p['user']); + if ($this->prefs['email_address']) + $p['user_email'] = $this->prefs['email_address']; + + return $p; + } + + public function create_identity($p) + { + // only execute on login + if ($p['login'] && $this->prefs) { + if ($this->prefs['full_name']) + $p['record']['name'] = $this->prefs['full_name']; + if ($this->prefs['email_address']) + $p['record']['email'] = $this->prefs['email_address']; + if ($this->prefs['signature']) + $p['record']['signature'] = $this->prefs['signature']; + + // copy address book + $rcmail = rcmail::get_instance(); + $contacts = $rcmail->get_address_book(null, true); + if ($contacts && count($this->abook)) { + foreach ($this->abook as $rec) + $contacts->insert($rec, true); + } + + // mark identity as complete for following hooks + $p['complete'] = true; + } + + return $p; + } + + private function read_squirrel_prefs($uname) + { + $this->load_config(); + $rcmail = rcmail::get_instance(); + + if ($srcdir = $rcmail->config->get('squirrelmail_data_dir')) { + $prefsfile = slashify($srcdir) . $uname . '.pref'; + $abookfile = slashify($srcdir) . $uname . '.abook'; + $sigfile = slashify($srcdir) . $uname . '.sig'; + + if (is_readable($prefsfile)) { + $this->prefs = array(); + foreach (file($prefsfile) as $line) { + list($key, $value) = explode('=', $line); + $this->prefs[$key] = utf8_encode(rtrim($value)); + } + + // also read signature file if exists + if (is_readable($sigfile)) { + $this->prefs['signature'] = utf8_encode(file_get_contents($sigfile)); + } + + // parse addres book file + if (filesize($abookfile)) { + foreach(file($abookfile) as $line) { + list($rec['name'], $rec['firstname'], $rec['surname'], $rec['email']) = explode('|', utf8_encode(rtrim($line))); + if ($rec['name'] && $rec['email']) + $this->abook[] = $rec; + } + } + } + } + } + +} + +?> \ No newline at end of file diff --git a/plugins/subscriptions_option/localization/en_US.inc b/plugins/subscriptions_option/localization/en_US.inc new file mode 100644 index 0000000..5a348e0 --- /dev/null +++ b/plugins/subscriptions_option/localization/en_US.inc @@ -0,0 +1,6 @@ + diff --git a/plugins/subscriptions_option/localization/et_EE.inc b/plugins/subscriptions_option/localization/et_EE.inc new file mode 100644 index 0000000..6c5f6f4 --- /dev/null +++ b/plugins/subscriptions_option/localization/et_EE.inc @@ -0,0 +1,6 @@ + diff --git a/plugins/subscriptions_option/localization/pl_PL.inc b/plugins/subscriptions_option/localization/pl_PL.inc new file mode 100644 index 0000000..8544c7d --- /dev/null +++ b/plugins/subscriptions_option/localization/pl_PL.inc @@ -0,0 +1,6 @@ + diff --git a/plugins/subscriptions_option/localization/sv_SE.inc b/plugins/subscriptions_option/localization/sv_SE.inc new file mode 100644 index 0000000..05b7006 --- /dev/null +++ b/plugins/subscriptions_option/localization/sv_SE.inc @@ -0,0 +1,6 @@ + diff --git a/plugins/subscriptions_option/subscriptions_option.php b/plugins/subscriptions_option/subscriptions_option.php new file mode 100644 index 0000000..09ee6e3 --- /dev/null +++ b/plugins/subscriptions_option/subscriptions_option.php @@ -0,0 +1,92 @@ +add_texts('localization/', false); + $dont_override = rcmail::get_instance()->config->get('dont_override', array()); + if (!in_array('use_subscriptions', $dont_override)) { + $this->add_hook('user_preferences', array($this, 'settings_blocks')); + $this->add_hook('save_preferences', array($this, 'save_prefs')); + } + $this->add_hook('list_mailboxes', array($this, 'list_mailboxes')); + $this->add_hook('manage_folders', array($this, 'manage_folders')); + } + + function settings_blocks($args) + { + if ($args['section'] == 'server') { + $use_subscriptions = rcmail::get_instance()->config->get('use_subscriptions'); + $field_id = 'rcmfd_use_subscriptions'; + $checkbox = new html_checkbox(array('name' => '_use_subscriptions', 'id' => $field_id, 'value' => 1)); + + $args['blocks']['main']['options']['use_subscriptions'] = array( + 'title' => html::label($field_id, Q($this->gettext('useimapsubscriptions'))), + 'content' => $checkbox->show($use_subscriptions?1:0), + ); + } + + return $args; + } + + function save_prefs($args) + { + if ($args['section'] == 'server') { + $rcmail = rcmail::get_instance(); + $use_subscriptions = $rcmail->config->get('use_subscriptions'); + + $args['prefs']['use_subscriptions'] = isset($_POST['_use_subscriptions']) ? true : false; + + // if the use_subscriptions preference changes, flush the folder cache + if (($use_subscriptions && !isset($_POST['_use_subscriptions'])) || + (!$use_subscriptions && isset($_POST['_use_subscriptions']))) { + $rcmail->imap_init(true); + $rcmail->imap->clear_cache('mailboxes'); + } + } + return $args; + } + + function list_mailboxes($args) + { + $rcmail = rcmail::get_instance(); + if (!$rcmail->config->get('use_subscriptions', true)) { + $args['folders'] = iil_C_ListMailboxes($rcmail->imap->conn, $rcmail->imap->mod_mailbox($args['root']), $args['filter']); + } + return $args; + } + + function manage_folders($args) + { + $rcmail = rcmail::get_instance(); + if (!$rcmail->config->get('use_subscriptions', true)) { + $args['table']->remove_column('subscribed'); + } + return $args; + } +} diff --git a/plugins/userinfo/localization/de_CH.inc b/plugins/userinfo/localization/de_CH.inc new file mode 100644 index 0000000..5f236b6 --- /dev/null +++ b/plugins/userinfo/localization/de_CH.inc @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/plugins/userinfo/localization/en_US.inc b/plugins/userinfo/localization/en_US.inc new file mode 100644 index 0000000..1a2fd90 --- /dev/null +++ b/plugins/userinfo/localization/en_US.inc @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/plugins/userinfo/localization/et_EE.inc b/plugins/userinfo/localization/et_EE.inc new file mode 100644 index 0000000..97830b4 --- /dev/null +++ b/plugins/userinfo/localization/et_EE.inc @@ -0,0 +1,9 @@ + diff --git a/plugins/userinfo/localization/pl_PL.inc b/plugins/userinfo/localization/pl_PL.inc new file mode 100644 index 0000000..6b03c32 --- /dev/null +++ b/plugins/userinfo/localization/pl_PL.inc @@ -0,0 +1,9 @@ + diff --git a/plugins/userinfo/localization/pt_PT.inc b/plugins/userinfo/localization/pt_PT.inc new file mode 100644 index 0000000..e36d678 --- /dev/null +++ b/plugins/userinfo/localization/pt_PT.inc @@ -0,0 +1,9 @@ + diff --git a/plugins/userinfo/localization/sv_SE.inc b/plugins/userinfo/localization/sv_SE.inc new file mode 100644 index 0000000..a34923a --- /dev/null +++ b/plugins/userinfo/localization/sv_SE.inc @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/plugins/userinfo/userinfo.js b/plugins/userinfo/userinfo.js new file mode 100644 index 0000000..70a5085 --- /dev/null +++ b/plugins/userinfo/userinfo.js @@ -0,0 +1,16 @@ +/* Show user-info plugin script */ + +if (window.rcmail) { + rcmail.addEventListener('init', function(evt) { + // + var tab = $('').attr('id', 'settingstabpluginuserinfo').addClass('tablink'); + + var button = $('').attr('href', rcmail.env.comm_path+'&_action=plugin.userinfo').html(rcmail.gettext('userinfo', 'userinfo')).appendTo(tab); + button.bind('click', function(e){ return rcmail.command('plugin.userinfo', this) }); + + // add button and register command + rcmail.add_element(tab, 'tabs'); + rcmail.register_command('plugin.userinfo', function(){ rcmail.goto_url('plugin.userinfo') }, true); + }) +} + diff --git a/plugins/userinfo/userinfo.php b/plugins/userinfo/userinfo.php new file mode 100644 index 0000000..0f1b18c --- /dev/null +++ b/plugins/userinfo/userinfo.php @@ -0,0 +1,53 @@ +add_texts('localization/', array('userinfo')); + $this->register_action('plugin.userinfo', array($this, 'infostep')); + $this->include_script('userinfo.js'); + } + + function infostep() + { + $this->register_handler('plugin.body', array($this, 'infohtml')); + rcmail::get_instance()->output->send('plugin'); + } + + function infohtml() + { + $rcmail = rcmail::get_instance(); + $user = $rcmail->user; + + $table = new html_table(array('cols' => 2, 'cellpadding' => 3)); + + $table->add('title', 'ID'); + $table->add('', Q($user->ID)); + + $table->add('title', Q($this->gettext('username'))); + $table->add('', Q($user->data['username'])); + + $table->add('title', Q($this->gettext('server'))); + $table->add('', Q($user->data['mail_host'])); + + $table->add('title', Q($this->gettext('created'))); + $table->add('', Q($user->data['created'])); + + $table->add('title', Q($this->gettext('lastlogin'))); + $table->add('', Q($user->data['last_login'])); + + $identity = $user->get_identity(); + $table->add('title', Q($this->gettext('defaultidentity'))); + $table->add('', Q($identity['name'] . ' <' . $identity['email'] . '>')); + + return html::tag('h4', null, Q('Infos for ' . $user->get_username())) . $table->show(); + } + +} \ No newline at end of file diff --git a/plugins/vcard_attachments/vcard_add_contact.png b/plugins/vcard_attachments/vcard_add_contact.png new file mode 100644 index 0000000000000000000000000000000000000000..478c1f3f21f36fa4ff521e3ab3f57dfbfc1cfa41 GIT binary patch literal 1361 zcmV-X1+MyuP)~a9t%7ckb>`j z*ars=);v1$6o`(CNlOOGo`UL#rEOhYprovv3rxsdxq1y^W8=ZscP?YW zf*??}tL1s^T}LT4K9``eS?pn6pmL@4R9s?Npa7pQ z4J}@v*Ao*kG&}-}f`jGeet!PY-q|f~Gu9Dpst&k-yKop+d$?BQ0`3I8cG}uH*uAs} zjY6@9d4W89!LS|B;RTEdM$MrkvOq<}PL>-P8G~E5M&ahrF!T-l4ZZzC(9=5z8l+p( z59+R7=tR_AJuD_@)`F<176Ja^`NBWvEKQp5SCW|)^e1C$8@ct+V<{5JHARIr-Y;;l3&@Vtq@ zXBlf{fmY;(8O-L}I^k5qX;_@&4D&Y$;2tel6ree(@lUhvM}b#N1ky4xANqT~rKJT{ zlzBkdP5}b4@fj}?VCu655sE2Cb5s+UW!H)VZYBb$>7T>H@_s@RytT#$me&dpT`e~8 zTxAfTm~u2nHOi${?wkNb*e-k=OGPM_^6z|YKY+Cni1TC$ z*diWJxlAdD`p)il!es&Co5a-;6jLs;N-;cbreXuux+6A|`l}enRK(xL)@tqCSf}5y z*~kZ%tp6b*_b7%bhx6%(FVz}dCs@&k0)0dxc8EPPh2PKtapX90rg5B&kj`f!e#mNM zBT~*;kGzK{d94lgl?3QS!JE;ud@VS z0(jliypKYD1bp>2dnbBi+N1-`Ae^G3>zq{;%1;yTrSe TAem#400000NkvXXu0mjfu;qI* literal 0 HcmV?d00001 diff --git a/plugins/vcard_attachments/vcard_attachments.php b/plugins/vcard_attachments/vcard_attachments.php new file mode 100644 index 0000000..532311e --- /dev/null +++ b/plugins/vcard_attachments/vcard_attachments.php @@ -0,0 +1,115 @@ +action == 'show' || $rcmail->action == 'preview') { + $this->add_hook('message_load', array($this, 'message_load')); + $this->add_hook('template_object_messagebody', array($this, 'html_output')); + } + + $this->register_action('plugin.savevcard', array($this, 'save_vcard')); + } + + /** + * Check message attachments for vcards + */ + function message_load($p) + { + $this->message = $p['object']; + + foreach ((array)$this->message->attachments as $attachment) { + if (in_array($attachment->mimetype, array('text/vcard', 'text/x-vcard'))) + $this->vcard_part = $attachment->mime_id; + } + } + + /** + * This callback function adds a box below the message content + * if there is a vcard attachment available + */ + function html_output($p) + { + if ($this->vcard_part) { + $vcard = new rcube_vcard($this->message->get_part_content($this->vcard_part)); + + // successfully parsed vcard + if ($vcard->displayname) { + $display = $vcard->displayname; + if ($vcard->email[0]) + $display .= ' <'.$vcard->email[0].'>'; + + // add box below messsage body + $p['content'] .= html::p(array('style' => "margin:1em; padding:0.5em; border:1px solid #999; border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px; width: auto;"), + html::a(array( + 'href' => "#", + 'onclick' => "return plugin_vcard_save_contact('".JQ($this->vcard_part)."')", + 'title' => "Save contact in local address book"), // TODO: localize this title + html::img(array('src' => $this->url('vcard_add_contact.png'), 'align' => "middle"))) + . ' ' . html::span(null, Q($display))); + + $this->include_script('vcardattach.js'); + } + } + + return $p; + } + + /** + * Handler for request action + */ + function save_vcard() + { + $uid = get_input_value('_uid', RCUBE_INPUT_POST); + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST); + $mime_id = get_input_value('_part', RCUBE_INPUT_POST); + + $rcmail = rcmail::get_instance(); + $part = $uid && $mime_id ? $rcmail->imap->get_message_part($uid, $mime_id) : null; + + $error_msg = 'Failed to saved vcard'; // TODO: localize this text + + if ($part && ($vcard = new rcube_vcard($part)) && $vcard->displayname && $vcard->email) { + $contacts = $rcmail->get_address_book(null, true); + + // check for existing contacts + $existing = $contacts->search('email', $vcard->email[0], true, false); + if ($done = $existing->count) { + $rcmail->output->command('display_message', $this->gettext('contactexists'), 'warning'); + } + else { + // add contact + $success = $contacts->insert(array( + 'name' => $vcard->displayname, + 'firstname' => $vcard->firstname, + 'surname' => $vcard->surname, + 'email' => $vcard->email[0], + 'vcard' => $vcard->export(), + )); + + if ($success) + $rcmail->output->command('display_message', $this->gettext('addedsuccessfully'), 'confirmation'); + else + $rcmail->output->command('display_message', $error_msg, 'error'); + } + } + else + $rcmail->output->command('display_message', $error_msg, 'error'); + + $rcmail->output->send(); + } + +} \ No newline at end of file diff --git a/plugins/vcard_attachments/vcardattach.js b/plugins/vcard_attachments/vcardattach.js new file mode 100644 index 0000000..e03e508 --- /dev/null +++ b/plugins/vcard_attachments/vcardattach.js @@ -0,0 +1,10 @@ + +function plugin_vcard_save_contact(mime_id) +{ + rcmail.set_busy(true, 'loading'); + rcmail.http_post('plugin.savevcard', '_uid='+rcmail.env.uid+'&_mbox='+urlencode(rcmail.env.mailbox)+'&_part='+urlencode(mime_id), true); + + return false; +} + + diff --git a/program/include/bugs.inc b/program/include/bugs.inc index e5dcf42..4ce4998 100644 --- a/program/include/bugs.inc +++ b/program/include/bugs.inc @@ -5,7 +5,7 @@ | program/include/bugs.inc | | | | This file is part of the RoudCube Webmail client | - | Copyright (C) 2005-2007, RoudCube Dev - Switzerland | + | Copyright (C) 2005-2009, RoudCube Dev - Switzerland | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -15,7 +15,7 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: bugs.inc 2025 2008-10-30 09:15:01Z alec $ + $Id: bugs.inc 2862 2009-08-17 09:54:53Z thomasb $ */ @@ -73,31 +73,16 @@ function log_bug($arg_arr) if ($CONFIG['debug_level'] & 1) { $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : ''); - $log_entry = sprintf("[%s] %s Error: %s%s (%s %s)\n", - date("d-M-Y H:i:s O", mktime()), + $log_entry = sprintf("%s Error: %s%s (%s %s)", $program, $arg_arr['message'], $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '', $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'] . $post_query); - - if (empty($CONFIG['log_dir'])) - $CONFIG['log_dir'] = INSTALL_PATH.'logs'; - - // try to open specific log file for writing - if ($CONFIG['log_driver'] == 'syslog') + + if (!write_log('errors', $log_entry)) { - syslog(LOG_ERR, $log_entry); - } - else if ($fp = @fopen($CONFIG['log_dir'].'/errors', 'a')) - { - // log_driver == 'file' is the default, assumed here. - fwrite($fp, $log_entry); - fclose($fp); - } - else - { - // send error to PHPs error handler + // send error to PHPs error handler if write_log didn't succeed trigger_error($arg_arr['message']); } } diff --git a/program/include/html.php b/program/include/html.php index 01ad415..350b894 100644 --- a/program/include/html.php +++ b/program/include/html.php @@ -33,7 +33,7 @@ class html protected $content; public static $common_attrib = array('id','class','style','title','align'); - public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style'); + public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style','script'); public static $lc_tags = true; /** @@ -457,7 +457,7 @@ class html_textarea extends html unset($this->attrib['value']); } - if (!empty($value) && !ereg('mce_editor', $this->attrib['class'])) { + if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) { $value = Q($value, 'strict', false); } @@ -599,6 +599,34 @@ class html_table extends html $this->header[] = $cell; } + /** + * Remove a column from a table + * Useful for plugins making alterations + * + * @param string $class + */ + public function remove_column($class) + { + // Remove the header + foreach($this->header as $index=>$header){ + if($header->attrib['class'] == $class){ + unset($this->header[$index]); + break; + } + } + + // Remove cells from rows + foreach($this->rows as $i=>$row){ + foreach($row->cells as $j=>$cell){ + if($cell->attrib['class'] == $class){ + unset($this->rows[$i]->cells[$j]); + break; + } + } + } + } + + /** * Jump to next row * diff --git a/program/include/iniset.php b/program/include/iniset.php index 5ca17cc..5d83686 100755 --- a/program/include/iniset.php +++ b/program/include/iniset.php @@ -16,13 +16,13 @@ | Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: cache.inc 88 2005-12-03 16:54:12Z roundcube $ + $Id: iniset.php 2916 2009-09-04 10:58:29Z thomasb $ */ // application constants -define('RCMAIL_VERSION', '0.2.2'); +define('RCMAIL_VERSION', '0.3-stable'); define('RCMAIL_CHARSET', 'UTF-8'); define('JS_OBJECT_NAME', 'rcmail'); @@ -34,7 +34,7 @@ define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config'); // make sure path_separator is defined if (!defined('PATH_SEPARATOR')) { - define('PATH_SEPARATOR', (eregi('win', PHP_OS) ? ';' : ':')); + define('PATH_SEPARATOR', (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') ? ';' : ':'); } // RC include folders MUST be included FIRST to avoid other @@ -58,8 +58,10 @@ if (isset($_SERVER['HTTPS'])) { } ini_set('session.name', 'roundcube_sessid'); ini_set('session.use_cookies', 1); -ini_set('session.only_use_cookies', 1); -set_magic_quotes_runtime(0); +ini_set('session.use_only_cookies', 1); +if (function_exists('set_magic_quotes_runtime')) { + set_magic_quotes_runtime(0); +} // increase maximum execution time for php scripts // (does not work in safe mode) @@ -78,26 +80,32 @@ if(extension_loaded('mbstring')) * @todo Make Zend, PEAR etc play with this * @todo Make our classes conform to a more straight forward CS. */ -function __autoload($classname) +function rcube_autoload($classname) { $filename = preg_replace( - array('/MDB2_(.+)/', - '/Mail_(.+)/', - '/^html_.+/', - '/^utf8$/', - '/html2text/' - ), - array('MDB2/\\1', - 'Mail/\\1', - 'html', - 'utf8.class', - 'lib/html2text' // see #1485505 - ), + array( + '/MDB2_(.+)/', + '/Mail_(.+)/', + '/Net_(.+)/', + '/^html_.+/', + '/^utf8$/', + '/html2text/' + ), + array( + 'MDB2/\\1', + 'Mail/\\1', + 'Net/\\1', + 'html', + 'utf8.class', + 'lib/html2text' // see #1485505 + ), $classname ); include $filename. '.php'; } +spl_autoload_register('rcube_autoload'); + /** * Local callback function for PEAR errors */ diff --git a/program/include/main.inc b/program/include/main.inc index 4c6ddff..f25f4cb 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -15,7 +15,7 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: main.inc 2483 2009-05-15 10:22:29Z thomasb $ + $Id: main.inc 2852 2009-08-10 21:32:44Z thomasb $ */ @@ -84,9 +84,9 @@ function get_sequence_name($sequence) * @return string Localized text * @see rcmail::gettext() */ -function rcube_label($p) +function rcube_label($p, $domain=null) { - return rcmail::get_instance()->gettext($p); + return rcmail::get_instance()->gettext($p, $domain); } @@ -124,7 +124,9 @@ function rcmail_url($action, $p=array(), $task=null) */ function rcmail_temp_gc() { - $tmp = unslashify($CONFIG['temp_dir']); + $rcmail = rcmail::get_instance(); + + $tmp = unslashify($rcmail->config->get('temp_dir')); $expire = mktime() - 172800; // expire in 48 hours if ($dir = opendir($tmp)) @@ -177,35 +179,19 @@ function rcube_charset_convert($str, $from, $to=NULL) static $mbstring_loaded = null; static $mbstring_list = null; static $convert_warning = false; + static $conv = null; + + $error = false; - $from = strtoupper($from); - $to = $to==NULL ? strtoupper(RCMAIL_CHARSET) : strtoupper($to); - $error = false; $conv = null; - - # RFC1642 - if ($from == 'UNICODE-1-1-UTF-7') - $from = 'UTF-7'; - if ($to == 'UNICODE-1-1-UTF-7') - $to = 'UTF-7'; + $to = empty($to) ? $to = strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to); + $from = rcube_parse_charset($from); if ($from == $to || empty($str) || empty($from)) return $str; - - $aliases = array( - 'US-ASCII' => 'ISO-8859-1', - 'ANSI_X3.110-1983' => 'ISO-8859-1', - 'ANSI_X3.4-1968' => 'ISO-8859-1', - 'UNKNOWN-8BIT' => 'ISO-8859-15', - 'X-UNKNOWN' => 'ISO-8859-15', - 'X-USER-DEFINED' => 'ISO-8859-15', - 'ISO-8859-8-I' => 'ISO-8859-8', - 'KS_C_5601-1987' => 'EUC-KR', - ); // convert charset using iconv module if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') { - $aliases['GB2312'] = 'GB18030'; - $_iconv = iconv(($aliases[$from] ? $aliases[$from] : $from), ($aliases[$to] ? $aliases[$to] : $to) . "//IGNORE", $str); + $_iconv = iconv($from, $to . '//IGNORE', $str); if ($_iconv !== false) { return $_iconv; } @@ -216,14 +202,13 @@ function rcube_charset_convert($str, $from, $to=NULL) // convert charset using mbstring module if ($mbstring_loaded) { - $aliases['UTF-7'] = 'UTF7-IMAP'; $aliases['WINDOWS-1257'] = 'ISO-8859-13'; if (is_null($mbstring_list)) { $mbstring_list = mb_list_encodings(); $mbstring_list = array_map('strtoupper', $mbstring_list); } - + $mb_from = $aliases[$from] ? $aliases[$from] : $from; $mb_to = $aliases[$to] ? $aliases[$to] : $to; @@ -234,59 +219,211 @@ function rcube_charset_convert($str, $from, $to=NULL) } } - # try to convert with custom classes - if (class_exists('utf8')) - $conv = new utf8(); - - // convert string to UTF-8 - if ($from == 'UTF-7') { - if ($_str = utf7_to_utf8($str)) - $str = $_str; - else - $error = true; - } - else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) { - $str = utf8_encode($str); - } - else if ($from != 'UTF-8' && $conv) { - $conv->loadCharset($from); - $str = $conv->strToUtf8($str); - } - else if ($from != 'UTF-8') + // convert charset using bundled classes/functions + if ($to == 'UTF-8') { + if ($from == 'UTF7-IMAP') { + if ($_str = utf7_to_utf8($str)) + return $_str; + } + else if ($from == 'UTF-7') { + if ($_str = rcube_utf7_to_utf8($str)) + return $_str; + } + else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) { + return utf8_encode($str); + } + else if (class_exists('utf8')) { + if (!$conv) + $conv = new utf8($from); + else + $conv->loadCharset($from); + + if($_str = $conv->strToUtf8($str)) + return $_str; + } $error = true; - - // encode string for output - if ($to == 'UTF-7') { - return utf8_to_utf7($str); - } - else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) { - return utf8_decode($str); } - else if ($to != 'UTF-8' && $conv) { - $conv->loadCharset($to); - return $conv->utf8ToStr($str); - } - else if ($to != 'UTF-8') { + + // encode string for output + if ($from == 'UTF-8') { + // @TODO: we need a function for UTF-7 (RFC2152) conversion + if ($to == 'UTF7-IMAP' || $to == 'UTF-7') { + if ($_str = utf8_to_utf7($str)) + return $_str; + } + else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) { + return utf8_decode($str); + } + else if (class_exists('utf8')) { + if (!$conv) + $conv = new utf8($to); + else + $conv->loadCharset($from); + + if ($_str = $conv->strToUtf8($str)) + return $_str; + } $error = true; } // report error - if ($error && !$convert_warning){ + if ($error && !$convert_warning) { raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, - 'message' => "Could not convert string from $from to $to. Make sure iconv is installed or lib/utf8.class is available" + 'line' => __LINE__, + 'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available." ), true, false); $convert_warning = true; } - // return UTF-8 string + // return UTF-8 or original string return $str; } +/** + * Parse and validate charset name string (see #1485758). + * Sometimes charset string is malformed, there are also charset aliases + * but we need strict names for charset conversion (specially utf8 class) + * + * @param string Input charset name + * @return The validated charset name + */ +function rcube_parse_charset($charset) + { + $charset = strtoupper($charset); + + # RFC1642 + $charset = str_replace('UNICODE-1-1-', '', $charset); + + # Aliases: some of them from HTML5 spec. + $aliases = array( + 'USASCII' => 'WINDOWS-1252', + 'ANSIX31101983' => 'WINDOWS-1252', + 'ANSIX341968' => 'WINDOWS-1252', + 'UNKNOWN8BIT' => 'ISO-8859-15', + 'UNKNOWN' => 'ISO-8859-15', + 'USERDEFINED' => 'ISO-8859-15', + 'KSC56011987' => 'EUC-KR', + 'GB2312' => 'GBK', + 'GB231280' => 'GBK', + 'UNICODE' => 'UTF-8', + 'UTF7IMAP' => 'UTF7-IMAP', + 'TIS620' => 'WINDOWS-874', + 'ISO88599' => 'WINDOWS-1254', + 'ISO885911' => 'WINDOWS-874', + ); + + // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8) + $str = preg_replace(array('/[^a-z0-9]/i', '/^x+/i'), '', $charset); + + if (isset($aliases[$str])) + return $aliases[$str]; + + if (preg_match('/UTF(7|8|16|32)(BE|LE)*/', $str, $m)) + return 'UTF-' . $m[1] . $m[2]; + + if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) { + $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1); + # some clients sends windows-1252 text as latin1, + # it is safe to use windows-1252 for all latin1 + return $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso; + } + + return $charset; + } + + +/** + * Converts string from standard UTF-7 (RFC 2152) to UTF-8. + * + * @param string Input string + * @return The converted string + */ +function rcube_utf7_to_utf8($str) +{ + $Index_64 = array( + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, + 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0, + 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0, + 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, + 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0, + ); + + $u7len = strlen($str); + $str = strval($str); + $res = ''; + + for ($i=0; $u7len > 0; $i++, $u7len--) + { + $u7 = $str[$i]; + if ($u7 == '+') + { + $i++; + $u7len--; + $ch = ''; + + for (; $u7len > 0; $i++, $u7len--) + { + $u7 = $str[$i]; + + if (!$Index_64[ord($u7)]) + break; + + $ch .= $u7; + } + + if ($ch == '') { + if ($u7 == '-') + $res .= '+'; + continue; + } + + $res .= rcube_utf16_to_utf8(base64_decode($ch)); + } + else + { + $res .= $u7; + } + } + + return $res; +} + +/** + * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion) + * + * @param string Input string + * @return The converted string + */ +function rcube_utf16_to_utf8($str) +{ + $len = strlen($str); + $dec = ''; + + for ($i = 0; $i < $len; $i += 2) { + $c = ord($str[$i]) << 8 | ord($str[$i + 1]); + if ($c >= 0x0001 && $c <= 0x007F) { + $dec .= chr($c); + } else if ($c > 0x07FF) { + $dec .= chr(0xE0 | (($c >> 12) & 0x0F)); + $dec .= chr(0x80 | (($c >> 6) & 0x3F)); + $dec .= chr(0x80 | (($c >> 0) & 0x3F)); + } else { + $dec .= chr(0xC0 | (($c >> 6) & 0x1F)); + $dec .= chr(0x80 | (($c >> 0) & 0x3F)); + } + } + return $dec; +} + + /** * Replacing specials characters to a specific encoding type * @@ -298,23 +435,13 @@ function rcube_charset_convert($str, $from, $to=NULL) */ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) { - global $OUTPUT; static $html_encode_arr = false; static $js_rep_table = false; static $xml_rep_table = false; - $charset = $OUTPUT->get_charset(); - $is_iso_8859_1 = false; - if ($charset == 'ISO-8859-1') { - $is_iso_8859_1 = true; - } if (!$enctype) $enctype = $OUTPUT->type; - // encode for plaintext - if ($enctype=='text') - return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str); - // encode for HTML output if ($enctype=='html') { @@ -344,9 +471,6 @@ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) return $newlines ? nl2br($out) : $out; } - if ($enctype=='url') - return rawurlencode($str); - // if the replace tables for XML and JS are not yet defined if ($js_rep_table===false) { @@ -354,12 +478,7 @@ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) $xml_rep_table['&'] = '&'; for ($c=160; $c<256; $c++) // can be increased to support more charsets - { $xml_rep_table[Chr($c)] = "&#$c;"; - - if ($is_iso_8859_1) - $js_rep_table[Chr($c)] = sprintf("\\u%04x", $c); - } $xml_rep_table['"'] = '"'; $js_rep_table['"'] = '\\"'; @@ -367,18 +486,20 @@ function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE) $js_rep_table["\\"] = "\\\\"; } - // encode for XML - if ($enctype=='xml') - return strtr($str, $xml_rep_table); - // encode for javascript use if ($enctype=='js') - { - if ($charset!='UTF-8') - $str = rcube_charset_convert($str, RCMAIL_CHARSET,$charset); - return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table)); - } + + // encode for plaintext + if ($enctype=='text') + return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str); + + if ($enctype=='url') + return rawurlencode($str); + + // encode for XML + if ($enctype=='xml') + return strtr($str, $xml_rep_table); // no encoding given -> return original string return $str; @@ -420,7 +541,7 @@ function JQ($str) * @return string Field value or NULL if not available */ function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) - { +{ global $OUTPUT; $value = NULL; @@ -437,7 +558,10 @@ function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) else if (isset($_COOKIE[$fname])) $value = $_COOKIE[$fname]; } - + + if (empty($value)) + return $value; + // strip single quotes if magic_quotes_sybase is enabled if (ini_get('magic_quotes_sybase')) $value = str_replace("''", "'", $value); @@ -454,16 +578,35 @@ function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset); else return $value; +} + +/** + * Convert array of request parameters (prefixed with _) + * to a regular array with non-prefixed keys. + * + * @param int Source to get value from (GPC) + * @return array Hash array with all request parameters + */ +function request2param($mode = RCUBE_INPUT_GPC) +{ + $out = array(); + $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST); + foreach ($src as $key => $value) { + $fname = $key[0] == '_' ? substr($key, 1) : $key; + $out[$fname] = get_input_value($key, $mode); } + + return $out; +} /** * Remove all non-ascii and non-word chars - * except . and - + * except ., -, _ */ -function asciiwords($str, $css_id = false) +function asciiwords($str, $css_id = false, $replace_with = '') { $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : ''); - return preg_replace("/[^$allowed]/i", '', $str); + return preg_replace("/[^$allowed]/i", $replace_with, $str); } /** @@ -516,7 +659,7 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col) while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) { $zebra_class = $c % 2 ? 'even' : 'odd'; - $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => "contact $zebra_class")); + $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class)); // format each col foreach ($a_show_cols as $col) @@ -530,7 +673,7 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col) foreach ($table_data as $row_data) { $zebra_class = $c % 2 ? 'even' : 'odd'; - $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => "contact $zebra_class")); + $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class)); // format each col foreach ($a_show_cols as $col) @@ -596,7 +739,7 @@ function rcmail_mod_css_styles($source, $container_id) $replacements = new rcube_string_replacer; // ignore the whole block if evil styles are detected - $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entitiy_decode($source)); + $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source)); if (preg_match('/expression|behavior|url\(|import/', $stripped)) return '/* evil! */'; @@ -637,22 +780,22 @@ function rcmail_mod_css_styles($source, $container_id) * @param string CSS content to decode * @return string Decoded string */ -function rcmail_xss_entitiy_decode($content) +function rcmail_xss_entity_decode($content) { $out = html_entity_decode(html_entity_decode($content)); - $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entitiy_decode_callback', $out); + $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out); $out = preg_replace('#/\*.*\*/#Um', '', $out); return $out; } /** - * preg_replace_callback callback for rcmail_xss_entitiy_decode_callback + * preg_replace_callback callback for rcmail_xss_entity_decode_callback * * @param array matches result from preg_replace_callback * @return string decoded entity */ -function rcmail_xss_entitiy_decode_callback($matches) +function rcmail_xss_entity_decode_callback($matches) { return chr(hexdec($matches[1])); } @@ -688,11 +831,11 @@ function parse_attrib_string($str) preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER); // convert attributes to an associative array (name => value) - if ($regs) - foreach ($regs as $attr) - { - $attrib[strtolower($attr[1])] = $attr[3] . $attr[4]; - } + if ($regs) { + foreach ($regs as $attr) { + $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]); + } + } return $attrib; } @@ -761,6 +904,9 @@ function format_date($date, $format=NULL) else if (!$format) $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i'; + // strftime() format + if (preg_match('/%[a-z]+/i', $format)) + return strftime($format, $timestamp); // parse format string manually in order to provide localized weekday and month names // an alternative would be to convert the date() format string to fit with strftime() @@ -825,8 +971,16 @@ function format_email_recipient($email, $name='') */ function console() { + $args = func_get_args(); + + if (class_exists('rcmail', false)) { + $rcmail = rcmail::get_instance(); + if (is_object($rcmail->plugins)) + $rcmail->plugins->exec_hook('console', $args); + } + $msg = array(); - foreach (func_get_args() as $arg) + foreach ($args as $arg) $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; if (!($GLOBALS['CONFIG']['debug_level'] & 4)) @@ -851,22 +1005,34 @@ function console() */ function write_log($name, $line) { - global $CONFIG; + global $CONFIG, $RCMAIL; if (!is_string($line)) $line = var_export($line, true); + + if (empty($CONFIG['log_date_format'])) + $CONFIG['log_date_format'] = 'd-M-Y H:i:s O'; + + $date = date($CONFIG['log_date_format']); - $log_entry = sprintf("[%s]: %s\n", - date("d-M-Y H:i:s O", mktime()), - $line); + // trigger logging hook + if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) { + $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line)); + $name = $log['name']; + $line = $log['line']; + $date = $log['date']; + if ($log['abort']) + return; + } + + $log_entry = sprintf("[%s]: %s\n", $date, $line); if ($CONFIG['log_driver'] == 'syslog') { - if ($name == 'errors') - $prio = LOG_ERR; - else - $prio = LOG_INFO; + $prio = $name == 'errors' ? LOG_ERR : LOG_INFO; syslog($prio, $log_entry); - } else { + return true; + } + else { // log_driver == 'file' is assumed here if (empty($CONFIG['log_dir'])) $CONFIG['log_dir'] = INSTALL_PATH.'logs'; @@ -876,8 +1042,10 @@ function write_log($name, $line) fwrite($fp, $log_entry); fflush($fp); fclose($fp); + return true; } } + return false; } @@ -885,17 +1053,16 @@ function write_log($name, $line) * @access private */ function rcube_timer() - { - list($usec, $sec) = explode(" ", microtime()); - return ((float)$usec + (float)$sec); - } +{ + return microtime(true); +} /** * @access private */ -function rcube_print_time($timer, $label='Timer') - { +function rcube_print_time($timer, $label='Timer', $dest='console') +{ static $print_count = 0; $print_count++; @@ -905,8 +1072,8 @@ function rcube_print_time($timer, $label='Timer') if (empty($label)) $label = 'Timer '.$print_count; - console(sprintf("%s: %0.4f sec", $label, $diff)); - } + write_log($dest, sprintf("%s: %0.4f sec", $label, $diff)); +} /** @@ -944,6 +1111,9 @@ function rcmail_mailbox_list($attrib) foreach ($a_folders as $folder) rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter); } + + // allow plugins to alter the folder tree or to localize folder names + $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter)); if ($type=='select') { $select = new html_select($attrib); @@ -952,12 +1122,12 @@ function rcmail_mailbox_list($attrib) if ($attrib['noselection']) $select->add(rcube_label($attrib['noselection']), '0'); - rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']); + rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']); $out = $select->show(); } else { $js_mailboxlist = array(); - $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); + $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']); $RCMAIL->output->set_env('mailboxes', $js_mailboxlist); @@ -1018,7 +1188,7 @@ function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='') if (!isset($arrFolders[$currentFolder])) { $arrFolders[$currentFolder] = array( 'id' => $path, - 'name' => rcube_charset_convert($currentFolder, 'UTF-7'), + 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'), 'virtual' => $virtual, 'folders' => array()); } @@ -1064,7 +1234,7 @@ function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $at } // make folder name safe for ids and class names - $folder_id = asciiwords($folder['id'], true); + $folder_id = asciiwords($folder['id'], true, '_'); $classes = array('mailbox'); // set special class for Sent, Drafts, Trash and Junk @@ -1193,7 +1363,7 @@ function rcmail_localize_foldername($name) if ($folder_class = rcmail_folder_classname($name)) return rcube_label($folder_class); else - return rcube_charset_convert($name, 'UTF-7'); + return rcube_charset_convert($name, 'UTF7-IMAP'); } diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 9aad25b..4624ee1 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -28,15 +28,17 @@ */ class rcmail { - static public $main_tasks = array('mail','settings','addressbook','login','logout'); + static public $main_tasks = array('mail','settings','addressbook','login','logout','dummy'); static private $instance; public $config; public $user; public $db; + public $smtp; public $imap; public $output; + public $plugins; public $task = 'mail'; public $action = ''; public $comm_path = './'; @@ -88,9 +90,9 @@ class rcmail $syslog_facility = $this->config->get('syslog_facility', LOG_USER); openlog($syslog_id, LOG_ODELAY, $syslog_facility); } - + // set task and action properties - $this->set_task(strip_quotes(get_input_value('_task', RCUBE_INPUT_GPC))); + $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC)); $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC)); // connect to database @@ -123,7 +125,7 @@ class rcmail // reset some session parameters when changing task if ($_SESSION['task'] != $this->task) - unset($_SESSION['page']); + rcube_sess_unset('page'); // set current task to session $_SESSION['task'] = $this->task; @@ -131,6 +133,9 @@ class rcmail // create IMAP object if ($this->task == 'mail') $this->imap_init(); + + // create plugin API and load plugins + $this->plugins = rcube_plugin_api::get_instance(); } @@ -141,14 +146,12 @@ class rcmail */ public function set_task($task) { - if (!in_array($task, self::$main_tasks)) - $task = 'mail'; - - $this->task = $task; - $this->comm_path = $this->url(array('task' => $task)); + $task = asciiwords($task); + $this->task = $task ? $task : 'mail'; + $this->comm_path = $this->url(array('task' => $this->task)); if ($this->output) - $this->output->set_env('task', $task); + $this->output->set_env('task', $this->task); } @@ -255,10 +258,19 @@ class rcmail $contacts = null; $ldap_config = (array)$this->config->get('ldap_public'); $abook_type = strtolower($this->config->get('address_book_type')); + + $plugin = $this->plugins->exec_hook('get_address_book', array('id' => $id, 'writeable' => $writeable)); - if ($id && $ldap_config[$id]) { + // plugin returned instance of a rcube_addressbook + if ($plugin['instance'] instanceof rcube_addressbook) { + $contacts = $plugin['instance']; + } + else if ($id && $ldap_config[$id]) { $contacts = new rcube_ldap($ldap_config[$id]); } + else if ($id === '0') { + $contacts = new rcube_contacts($this->db, $this->user->ID); + } else if ($abook_type == 'ldap') { // Use the first writable LDAP address book. foreach ($ldap_config as $id => $prop) { @@ -290,10 +302,6 @@ class rcmail if (!($this->output instanceof rcube_template)) $this->output = new rcube_template($this->task, $framed); - foreach (array('flag_for_deletion','read_when_deleted') as $js_config_var) { - $this->output->set_env($js_config_var, $this->config->get($js_config_var)); - } - // set keep-alive/check-recent interval if ($keep_alive = $this->config->get('keep_alive')) { // be sure that it's less than session lifetime @@ -310,10 +318,10 @@ class rcmail $this->output->set_env('task', $this->task); $this->output->set_env('action', $this->action); $this->output->set_env('comm_path', $this->comm_path); - $this->output->set_charset($this->config->get('charset', RCMAIL_CHARSET)); + $this->output->set_charset(RCMAIL_CHARSET); // add some basic label to client - $this->output->add_label('loading'); + $this->output->add_label('loading', 'servererror'); return $this->output; } @@ -331,6 +339,20 @@ class rcmail return $this->output; } + + + /** + * Create SMTP object and connect to server + * + * @param boolean True if connection should be established + */ + public function smtp_init($connect = false) + { + $this->smtp = new rcube_smtp(); + + if ($connect) + $this->smtp->connect(); + } /** @@ -358,13 +380,10 @@ class rcmail $options = array( 'imap' => $this->config->get('imap_auth_type', 'check'), 'delimiter' => isset($_SESSION['imap_delimiter']) ? $_SESSION['imap_delimiter'] : $this->config->get('imap_delimiter'), + 'rootdir' => isset($_SESSION['imap_root']) ? $_SESSION['imap_root'] : $this->config->get('imap_root'), + 'debug_mode' => (bool) $this->config->get('imap_debug', 0), ); - - if (isset($_SESSION['imap_root'])) - $options['rootdir'] = $_SESSION['imap_root']; - else if ($imap_root = $this->config->get('imap_root')) - $options['rootdir'] = $imap_root; - + $this->imap->set_options($options); // set global object for backward compatibility @@ -385,7 +404,7 @@ class rcmail $conn = false; if ($_SESSION['imap_host'] && !$this->imap->conn) { - if (!($conn = $this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']))) { + if (!($conn = $this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']))) { if ($this->output) $this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error'); } @@ -462,7 +481,7 @@ class rcmail // lowercase username if it's an e-mail address (#1484473) if (strpos($username, '@')) - $username = rc_strtolower($username); + $username = mb_strtolower($username); // user already registered -> overwrite username if ($user = rcube_user::query($username, $host)) @@ -484,6 +503,13 @@ class rcmail // get existing mailboxes (but why?) // $a_mailboxes = $this->imap->list_mailboxes(); } + else { + raise_error(array( + 'code' => 600, + 'type' => 'php', + 'message' => "Failed to create a user record. Maybe aborted by a plugin?" + ), true, false); + } } else { raise_error(array( @@ -504,7 +530,7 @@ class rcmail $_SESSION['imap_host'] = $host; $_SESSION['imap_port'] = $imap_port; $_SESSION['imap_ssl'] = $imap_ssl; - $_SESSION['password'] = $this->encrypt_passwd($pass); + $_SESSION['password'] = $this->encrypt($pass); $_SESSION['login_time'] = mktime(); if ($_REQUEST['_timezone'] != '_default_') @@ -598,7 +624,7 @@ class rcmail * @param mixed Named parameters array or label name * @return string Localized text */ - public function gettext($attrib) + public function gettext($attrib, $domain=null) { // load localization files if not done yet if (empty($this->texts)) @@ -613,9 +639,12 @@ class rcmail $command_name = !empty($attrib['command']) ? $attrib['command'] : NULL; $alias = $attrib['name'] ? $attrib['name'] : ($command_name && $command_label_map[$command_name] ? $command_label_map[$command_name] : ''); - + + // check for text with domain + if ($domain && ($text_item = $this->texts[$domain.'.'.$alias])) + ; // text does not exist - if (!($text_item = $this->texts[$alias])) { + else if (!($text_item = $this->texts[$alias])) { /* raise_error(array( 'code' => 500, @@ -677,7 +706,7 @@ class rcmail * * @param string Language ID */ - public function load_language($lang = null) + public function load_language($lang = null, $add = array()) { $lang = $this->language_prop(($lang ? $lang : $_SESSION['language'])); @@ -707,6 +736,10 @@ class rcmail $_SESSION['language'] = $lang; } + + // append additional texts (from plugin) + if (is_array($add) && !empty($add)) + $this->texts += $add; } @@ -779,6 +812,9 @@ class rcmail */ public function kill_session() { + $this->plugins->exec_hook('kill_session'); + + rcube_sess_unset(); $_SESSION = array('language' => $this->user->language, 'auth_time' => time(), 'temp' => true); rcmail::setcookie('sessauth', '-del-', time() - 60); $this->user->reset(); @@ -821,6 +857,9 @@ class rcmail $this->imap->write_cache(); } + if (is_object($this->smtp)) + $this->smtp->disconnect(); + if (is_object($this->contacts)) $this->contacts->close(); @@ -830,6 +869,35 @@ class rcmail } + /** + * Generate a unique token to be used in a form request + * + * @return string The request token + */ + public function get_request_token() + { + $key = $this->task; + + if (!$_SESSION['request_tokens'][$key]) + $_SESSION['request_tokens'][$key] = md5(uniqid($key . rand(), true)); + + return $_SESSION['request_tokens'][$key]; + } + + + /** + * Check if the current request contains a valid token + * + * @param int Request method + * @return boolean True if request token is valid false if not + */ + public function check_request($mode = RCUBE_INPUT_POST) + { + $token = get_input_value('_token', $mode); + return !empty($token) && $_SESSION['request_tokens'][$this->task] == $token; + } + + /** * Create unique authorization hash * @@ -851,65 +919,109 @@ class rcmail return md5($auth_string); } + /** - * Encrypt IMAP password using DES encryption + * Encrypt using 3DES + * + * @param string $clear clear text input + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not to base64_encode() the result before returning * - * @param string Password to encrypt - * @return string Encryprted string + * @return string encrypted text */ - public function encrypt_passwd($pass) + public function encrypt($clear, $key = 'des_key', $base64 = true) { - if (function_exists('mcrypt_module_open') && ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_ECB, ""))) { + if (!$clear) + return ''; + /*- + * Add a single canary byte to the end of the clear text, which + * will help find out how much of padding will need to be removed + * upon decryption; see http://php.net/mcrypt_generic#68082 + */ + $clear = pack("a*H2", $clear, "80"); + + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) + { $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND); - mcrypt_generic_init($td, $this->config->get_des_key(), $iv); - $cypher = mcrypt_generic($td, $pass); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $cipher = $iv . mcrypt_generic($td, $clear); mcrypt_generic_deinit($td); mcrypt_module_close($td); } - else if (function_exists('des')) { - $cypher = des($this->config->get_des_key(), $pass, 1, 0, NULL); + else if (function_exists('des')) + { + define('DES_IV_SIZE', 8); + $iv = ''; + for ($i = 0; $i < constant('DES_IV_SIZE'); $i++) + $iv .= sprintf("%c", mt_rand(0, 255)); + $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); } - else { - $cypher = $pass; - + else + { raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, - 'message' => "Could not convert encrypt password. Make sure Mcrypt is installed or lib/des.inc is available" - ), true, false); + 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); } - - return base64_encode($cypher); + + return $base64 ? base64_encode($cipher) : $cipher; } - /** - * Decrypt IMAP password using DES encryption + * Decrypt 3DES-encrypted string + * + * @param string $cipher encrypted text + * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key' + * @param boolean $base64 whether or not input is base64-encoded * - * @param string Encrypted password - * @return string Plain password + * @return string decrypted text */ - public function decrypt_passwd($cypher) + public function decrypt($cipher, $key = 'des_key', $base64 = true) { - if (function_exists('mcrypt_module_open') && ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_ECB, ""))) { - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND); - mcrypt_generic_init($td, $this->config->get_des_key(), $iv); - $pass = mdecrypt_generic($td, base64_decode($cypher)); + if (!$cipher) + return ''; + + $cipher = $base64 ? base64_decode($cipher) : $cipher; + + if (function_exists('mcrypt_module_open') && + ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) + { + $iv = substr($cipher, 0, mcrypt_enc_get_iv_size($td)); + $cipher = substr($cipher, mcrypt_enc_get_iv_size($td)); + mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + $clear = mdecrypt_generic($td, $cipher); mcrypt_generic_deinit($td); mcrypt_module_close($td); } - else if (function_exists('des')) { - $pass = des($this->config->get_des_key(), base64_decode($cypher), 0, 0, NULL); + else if (function_exists('des')) + { + define('DES_IV_SIZE', 8); + $iv = substr($cipher, 0, constant('DES_IV_SIZE')); + $cipher = substr($cipher, constant('DES_IV_SIZE')); + $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); } - else { - $pass = base64_decode($cypher); + else + { + raise_error(array( + 'code' => 500, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" + ), true, true); } - - return preg_replace('/\x00/', '', $pass); + + /*- + * Trim PHP's padding and the canary byte; see note in + * rcmail::encrypt() and http://php.net/mcrypt_generic#68082 + */ + $clear = substr(rtrim($clear, "\0"), 0, -1); + + return $clear; } - /** * Build a valid URL to this instance of RoundCube * @@ -920,18 +1032,17 @@ class rcmail { if (!is_array($p)) $p = array('_action' => @func_get_arg(0)); - - if (!$p['task'] || !in_array($p['task'], rcmail::$main_tasks)) - $p['task'] = $this->task; - - $p['_task'] = $p['task']; + + $task = $p['_task'] ? $p['_task'] : ($p['task'] ? $p['task'] : $this->task); + $p['_task'] = $task; unset($p['task']); $url = './'; $delm = '?'; - foreach (array_reverse($p) as $par => $val) + foreach (array_reverse($p) as $key => $val) { if (!empty($val)) { + $par = $key[0] == '_' ? $key : '_'.$key; $url .= $delm.urlencode($par).'='.urlencode($val); $delm = '&'; } diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php new file mode 100644 index 0000000..9e970f2 --- /dev/null +++ b/program/include/rcube_addressbook.php @@ -0,0 +1,169 @@ + | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + + +/** + * Abstract skeleton of an address book/repository + * + * @package Addressbook + */ +abstract class rcube_addressbook +{ + /** public properties */ + var $primary_key; + var $readonly = true; + var $ready = false; + var $list_page = 1; + var $page_size = 10; + + /** + * Save a search string for future listings + * + * @param mixed Search params to use in listing method, obtained by get_search_set() + */ + abstract function set_search_set($filter); + + /** + * Getter for saved search properties + * + * @return mixed Search properties used by this class + */ + abstract function get_search_set(); + + /** + * Reset saved results and search parameters + */ + abstract function reset(); + + /** + * List the current set of contact records + * + * @param array List of cols to show + * @param int Only return this number of records, use negative values for tail + * @return array Indexed list of contact records, each a hash array + */ + abstract function list_records($cols=null, $subset=0); + + /** + * Search records + * + * @param array List of fields to search in + * @param string Search value + * @param boolean True if results are requested, False if count only + * @return Indexed list of contact records and 'count' value + */ + abstract function search($fields, $value, $strict=false, $select=true); + + /** + * Count number of available contacts in database + * + * @return object rcube_result_set Result set with values for 'count' and 'first' + */ + abstract function count(); + + /** + * Return the last result set + * + * @return object rcube_result_set Current result set or NULL if nothing selected yet + */ + abstract function get_result(); + + /** + * Get a specific contact record + * + * @param mixed record identifier(s) + * @param boolean True to return record as associative array, otherwise a result set is returned + * @return mixed Result object with all record fields or False if not found + */ + abstract function get_record($id, $assoc=false); + + /** + * Close connection to source + * Called on script shutdown + */ + function close() { } + + /** + * Set internal list page + * + * @param number Page number to list + * @access public + */ + function set_page($page) + { + $this->list_page = (int)$page; + } + + /** + * Set internal page size + * + * @param number Number of messages to display on one page + * @access public + */ + function set_pagesize($size) + { + $this->page_size = (int)$size; + } + + /** + * Create a new contact record + * + * @param array Assoziative array with save data + * @param boolean True to check for duplicates first + * @return The created record ID on success, False on error + */ + function insert($save_data, $check=false) + { + /* empty for read-only address books */ + } + + /** + * Update a specific contact record + * + * @param mixed Record identifier + * @param array Assoziative array with save data + * @return True on success, False on error + */ + function update($id, $save_cols) + { + /* empty for read-only address books */ + } + + /** + * Mark one or more contact records as deleted + * + * @param array Record identifiers + */ + function delete($ids) + { + /* empty for read-only address books */ + } + + /** + * Remove all records from the database + */ + function delete_all() + { + /* empty for read-only address books */ + } + +} + \ No newline at end of file diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php index 56e6d3c..4010dbc 100644 --- a/program/include/rcube_browser.php +++ b/program/include/rcube_browser.php @@ -47,23 +47,23 @@ class rcube_browser $this->safari = ($this->khtml || stristr($HTTP_USER_AGENT, 'safari')); if ($this->ns) { - $test = eregi("mozilla\/([0-9\.]+)", $HTTP_USER_AGENT, $regs); + $test = preg_match('/mozilla\/([0-9.]+)/i', $HTTP_USER_AGENT, $regs); $this->ver = $test ? (float)$regs[1] : 0; } if ($this->mz) { - $test = ereg("rv:([0-9\.]+)", $HTTP_USER_AGENT, $regs); + $test = preg_match('/rv:([0-9.]+)/', $HTTP_USER_AGENT, $regs); $this->ver = $test ? (float)$regs[1] : 0; } if($this->ie) { - $test = eregi("msie ([0-9\.]+)", $HTTP_USER_AGENT, $regs); + $test = preg_match('/msie ([0-9.]+)/i', $HTTP_USER_AGENT, $regs); $this->ver = $test ? (float)$regs[1] : 0; } if ($this->opera) { - $test = eregi("opera ([0-9\.]+)", $HTTP_USER_AGENT, $regs); + $test = preg_match('/opera ([0-9.]+)/i', $HTTP_USER_AGENT, $regs); $this->ver = $test ? (float)$regs[1] : 0; } - if (eregi(" ([a-z]{2})-([a-z]{2})", $HTTP_USER_AGENT, $regs)) + if (preg_match('/ ([a-z]{2})-([a-z]{2})/i', $HTTP_USER_AGENT, $regs)) $this->lang = $regs[1]; else $this->lang = 'en'; diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php index 7be2b0d..b30cf2d 100644 --- a/program/include/rcube_config.php +++ b/program/include/rcube_config.php @@ -51,15 +51,11 @@ class rcube_config ob_start(); // load main config file - if (include(RCMAIL_CONFIG_DIR . '/main.inc.php')) - $this->prop = (array)$rcmail_config; - else + if (!$this->load_from_file(RCMAIL_CONFIG_DIR . '/main.inc.php')) $this->errors[] = 'main.inc.php was not found.'; // load database config - if (include(RCMAIL_CONFIG_DIR . '/db.inc.php')) - $this->prop += (array)$rcmail_config; - else + if (!$this->load_from_file(RCMAIL_CONFIG_DIR . '/db.inc.php')) $this->errors[] = 'db.inc.php was not found.'; // load host-specific configuration @@ -77,11 +73,11 @@ class rcube_config // fix default imap folders encoding foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder) - $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF-7'); + $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP'); if (!empty($this->prop['default_imap_folders'])) foreach ($this->prop['default_imap_folders'] as $n => $folder) - $this->prop['default_imap_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF-7'); + $this->prop['default_imap_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP'); // set PHP error logging according to config if ($this->prop['debug_level'] & 1) { @@ -123,10 +119,30 @@ class rcube_config $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php'; } - if ($fname && is_file(RCMAIL_CONFIG_DIR . '/' . $fname)) { - include(RCMAIL_CONFIG_DIR . '/' . $fname); - $this->prop = array_merge($this->prop, (array)$rcmail_config); + if ($fname) { + $this->load_from_file(RCMAIL_CONFIG_DIR . '/' . $fname); + } + } + + + /** + * Read configuration from a file + * and merge with the already stored config values + * + * @param string Full path to the config file to be loaded + * @return booelan True on success, false on failure + */ + public function load_from_file($fpath) + { + if (is_file($fpath) && is_readable($fpath)) { + include($fpath); + if (is_array($rcmail_config)) { + $this->prop = array_merge($this->prop, $rcmail_config); + return true; + } } + + return false; } @@ -175,28 +191,42 @@ class rcube_config { return $this->prop; } - - + /** - * Return a 24 byte key for the DES encryption + * Return requested DES crypto key. * - * @return string DES encryption key + * @param string Crypto key name + * @return string Crypto key */ - public function get_des_key() + public function get_crypto_key($key) { - $key = !empty($this->prop['des_key']) ? $this->prop['des_key'] : 'rcmail?24BitPwDkeyF**ECB'; - $len = strlen($key); - - // make sure the key is exactly 24 chars long - if ($len<24) - $key .= str_repeat('_', 24-$len); - else if ($len>24) - substr($key, 0, 24); + // Bomb out if the requested key does not exist + if (!array_key_exists($key, $this->prop)) + { + raise_error(array( + 'code' => 500, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Request for unconfigured crypto key \"$key\"" + ), true, true); + } + + $key = $this->prop[$key]; + + // Bomb out if the configured key is not exactly 24 bytes long + if (strlen($key) != 24) + { + raise_error(array( + 'code' => 500, + 'type' => 'php', + 'file' => __FILE__, + 'message' => "Configured crypto key \"$key\" is not exactly 24 bytes long" + ), true, true); + } return $key; } - - + /** * Try to autodetect operating system and find the correct line endings * @@ -207,9 +237,9 @@ class rcube_config // use the configured delimiter for headers if (!empty($this->prop['mail_header_delimiter'])) return $this->prop['mail_header_delimiter']; - else if (strtolower(substr(PHP_OS, 0, 3) == 'win')) + else if (strtolower(substr(PHP_OS, 0, 3)) == 'win') return "\r\n"; - else if (strtolower(substr(PHP_OS, 0, 3) == 'mac')) + else if (strtolower(substr(PHP_OS, 0, 3)) == 'mac') return "\r\n"; else return "\n"; diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php index 65d89ca..a931845 100644 --- a/program/include/rcube_contacts.php +++ b/program/include/rcube_contacts.php @@ -25,7 +25,7 @@ * * @package Addressbook */ -class rcube_contacts +class rcube_contacts extends rcube_addressbook { var $db = null; var $db_name = ''; @@ -59,30 +59,6 @@ class rcube_contacts } - /** - * Set internal list page - * - * @param number Page number to list - * @access public - */ - function set_page($page) - { - $this->list_page = (int)$page; - } - - - /** - * Set internal page size - * - * @param number Number of messages to display on one page - * @access public - */ - function set_pagesize($size) - { - $this->page_size = (int)$size; - } - - /** * Save a search string for future listings * @@ -117,13 +93,6 @@ class rcube_contacts } - /** - * Close connection to source - * Called on script shutdown - */ - function close(){} - - /** * List the current set of contact records * @@ -185,7 +154,7 @@ class rcube_contacts { if ($col == 'ID' || $col == $this->primary_key) { - $ids = !is_array($value) ? split(',', $value) : $value; + $ids = !is_array($value) ? explode(',', $value) : $value; $add_where[] = $this->primary_key.' IN ('.join(',', $ids).')'; } else if ($strict) @@ -233,7 +202,7 @@ class rcube_contacts * * @return Result array or NULL if nothing selected yet */ - function get_result($as_res=true) + function get_result() { return $this->result; } @@ -293,18 +262,18 @@ class rcube_contacts $a_insert_cols[] = $this->db->quoteIdentifier($col); $a_insert_values[] = $this->db->quote($save_data[$col]); } - + if (!$existing->count && !empty($a_insert_cols)) { $this->db->query( "INSERT INTO ".$this->db_name." (user_id, changed, del, ".join(', ', $a_insert_cols).") - VALUES (?, ".$this->db->now().", 0, ".join(', ', $a_insert_values).")", - $this->user_id); + VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")" + ); - $insert_id = $this->db->insert_id(get_sequence_name('contacts')); + $insert_id = $this->db->insert_id('contacts'); } - + return $insert_id; } diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php index 78f6176..6a19703 100644 --- a/program/include/rcube_html_page.php +++ b/program/include/rcube_html_page.php @@ -29,10 +29,10 @@ class rcube_html_page protected $scripts_path = ''; protected $script_files = array(); protected $scripts = array(); - protected $charset = 'UTF-8'; + protected $charset = RCMAIL_CHARSET; - protected $script_tag_file = "\n"; - protected $script_tag = "\n"; + protected $script_tag_file = "\n"; + protected $script_tag = ""; protected $default_template = "\n\n\n"; protected $title = ''; @@ -53,6 +53,9 @@ class rcube_html_page public function include_script($file, $position='head') { static $sa_files = array(); + + if (!preg_match('|^https?://|i', $file) && $file[0] != '/') + $file = $this->scripts_path . $file . (($fs = @filemtime($this->scripts_path . $file)) ? '?s='.$fs : ''); if (in_array($file, $sa_files)) { return; @@ -165,7 +168,7 @@ class rcube_html_page // definition of the code to be placed in the document header and footer if (is_array($this->script_files['head'])) { foreach ($this->script_files['head'] as $file) { - $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file); + $__page_header .= sprintf($this->script_tag_file, $file); } } @@ -180,7 +183,7 @@ class rcube_html_page if (is_array($this->script_files['foot'])) { foreach ($this->script_files['foot'] as $file) { - $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file); + $__page_footer .= sprintf($this->script_tag_file, $file); } } @@ -246,10 +249,22 @@ class rcube_html_page $__page_header = $__page_footer = ''; // correct absolute paths in images and other tags - $output = preg_replace('/(src|href|background)=(["\']?)(\/[a-z0-9_\-]+)/Ui', "\\1=\\2$base_path\\3", $output); + $output = preg_replace('!(src|href|background)=(["\']?)(/[a-z0-9_-]+)!i', "\\1=\\2$base_path\\3", $output); + $output = preg_replace_callback('!(src|href)=(["\']?)([a-z0-9/_.-]+.(css|js))(["\'\s>])!i', array($this, 'add_filemtime'), $output); $output = str_replace('$__skin_path', $base_path, $output); - echo rcube_charset_convert($output, 'UTF-8', $this->charset); + if ($this->charset != RCMAIL_CHARSET) + echo rcube_charset_convert($output, RCMAIL_CHARSET, $this->charset); + else + echo $output; + } + + /** + * Callback function for preg_replace_callback in write() + */ + public function add_filemtime($matches) + { + return sprintf("%s=%s%s?s=%d%s", $matches[1], $matches[2], $matches[3], @filemtime($matches[3]), $matches[5]); } } diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 018bf7a..bc6d40a 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -16,7 +16,7 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: rcube_imap.php 2483 2009-05-15 10:22:29Z thomasb $ + $Id: rcube_imap.php 2884 2009-08-28 08:31:41Z alec $ */ @@ -36,7 +36,7 @@ require_once('lib/tnef_decoder.inc'); * * @package Mail * @author Thomas Bruederli - * @version 1.40 + * @version 1.5 * @link http://ilohamail.org */ class rcube_imap @@ -53,8 +53,10 @@ class rcube_imap var $delimiter = NULL; var $caching_enabled = FALSE; var $default_charset = 'ISO-8859-1'; + var $struct_charset = NULL; var $default_folders = array('INBOX'); var $default_folders_lc = array('inbox'); + var $fetch_add_headers = ''; var $cache = array(); var $cache_keys = array(); var $cache_changes = array(); @@ -68,6 +70,8 @@ class rcube_imap var $debug_level = 1; var $error_code = 0; var $options = array('imap' => 'check'); + + private $host, $user, $pass, $port, $ssl; /** @@ -99,24 +103,31 @@ class rcube_imap // check for Open-SSL support in PHP build if ($use_ssl && extension_loaded('openssl')) $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; - else if ($use_ssl) - { + else if ($use_ssl) { raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'message' => 'Open SSL not available;'), TRUE, FALSE); $port = 143; - } + } $ICL_PORT = $port; $IMAP_USE_INTERNAL_DATE = false; + + $attempt = 0; + do { + $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); + if (!empty($data['pass'])) + $pass = $data['pass']; - $this->conn = iil_Connect($host, $user, $pass, $this->options); - $this->host = $host; - $this->user = $user; + $this->conn = iil_Connect($data['host'], $data['user'], $pass, $this->options); + } while(!$this->conn && $data['retry']); + + $this->host = $data['host']; + $this->user = $data['user']; $this->pass = $pass; $this->port = $port; $this->ssl = $use_ssl; - // print trace mesages + // print trace messages if ($this->conn && ($this->debug_level & 8)) console($this->conn->message); @@ -137,7 +148,7 @@ class rcube_imap if (!empty($this->conn->rootdir)) { $this->set_rootdir($this->conn->rootdir); - $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir); + $this->root_ns = preg_replace('/[.\/]$/', '', $this->conn->rootdir); } } @@ -193,7 +204,7 @@ class rcube_imap */ function set_rootdir($root) { - if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/') + if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') $root = substr($root, 0, -1); $this->root_dir = $root; @@ -252,7 +263,7 @@ class rcube_imap */ function set_mailbox($new_mbox) { - $mailbox = $this->_mod_mailbox($new_mbox); + $mailbox = $this->mod_mailbox($new_mbox); if ($this->mailbox == $mailbox) return; @@ -301,7 +312,7 @@ class rcube_imap if (is_array($str) && $msgs == null) list($str, $msgs, $charset, $sort_field) = $str; if ($msgs != null && !is_array($msgs)) - $msgs = split(',', $msgs); + $msgs = explode(',', $msgs); $this->search_string = $str; $this->search_set = $msgs; @@ -328,7 +339,7 @@ class rcube_imap */ function get_mailbox_name() { - return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : ''; + return $this->conn ? $this->mod_mailbox($this->mailbox, 'out') : ''; } @@ -396,7 +407,7 @@ class rcube_imap foreach ($a_mboxes as $mbox_row) { - $name = $this->_mod_mailbox($mbox_row, 'out'); + $name = $this->mod_mailbox($mbox_row, 'out'); if (strlen($name)) $a_out[] = $name; } @@ -419,7 +430,7 @@ class rcube_imap * @see rcube_imap::list_mailboxes() * @access private */ - function _list_mailboxes($root='', $filter='*') + private function _list_mailboxes($root='', $filter='*') { $a_defaults = $a_out = array(); @@ -428,8 +439,16 @@ class rcube_imap if (is_array($a_mboxes)) return $a_mboxes; - // retrieve list of folders from IMAP server - $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter); + // Give plugins a chance to provide a list of mailboxes + $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',array('root'=>$root,'filter'=>$filter)); + if (isset($data['folders'])) { + $a_folders = $data['folders']; + } + else{ + // retrieve list of folders from IMAP server + $a_folders = iil_C_ListSubscribed($this->conn, $this->mod_mailbox($root), $filter); + } + if (!is_array($a_folders) || !sizeof($a_folders)) $a_folders = array(); @@ -452,7 +471,7 @@ class rcube_imap */ function messagecount($mbox_name='', $mode='ALL', $force=FALSE) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_messagecount($mailbox, $mode, $force); } @@ -463,7 +482,7 @@ class rcube_imap * @access private * @see rcube_imap::messagecount() */ - function _messagecount($mailbox='', $mode='ALL', $force=FALSE) + private function _messagecount($mailbox='', $mode='ALL', $force=FALSE) { $a_mailbox_cache = FALSE; $mode = strtoupper($mode); @@ -496,14 +515,8 @@ class rcube_imap // get message count using SEARCH // not very performant but more precise (using UNDELETED) - $count = 0; $index = $this->_search_index($mailbox, $search_str); - if (is_array($index)) - { - $str = implode(",", $index); - if (!empty($str)) - $count = count($index); - } + $count = is_array($index) ? count($index) : 0; } else { @@ -533,13 +546,14 @@ class rcube_imap * @param int Current page to list * @param string Header field to sort by * @param string Sort order [ASC|DESC] + * @param boolean Number of slice items to extract from result array * @return array Indexed array with message header objects * @access public */ - function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL) + function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; - return $this->_list_headers($mailbox, $page, $sort_field, $sort_order); + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; + return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, false, $slice); } @@ -549,85 +563,91 @@ class rcube_imap * @access private * @see rcube_imap::list_headers */ - function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE) + private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE, $slice=0) { if (!strlen($mailbox)) return array(); // use saved message set if ($this->search_string && $mailbox == $this->mailbox) - return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order); + return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); $this->_set_sort_order($sort_field, $sort_order); - $max = $this->_messagecount($mailbox); - $start_msg = ($this->list_page-1) * $this->page_size; - - list($begin, $end) = $this->_get_message_range($max, $page); - - // mailbox is empty - if ($begin >= $end) - return array(); - - $headers_sorted = FALSE; + $page = $page ? $page : $this->list_page; $cache_key = $mailbox.'.msg'; $cache_status = $this->check_cache_status($mailbox, $cache_key); // cache is OK, we can get all messages from local cache if ($cache_status>0) { + $start_msg = ($page-1) * $this->page_size; $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); - $headers_sorted = TRUE; + $result = array_values($a_msg_headers); + if ($slice) + $result = array_slice($result, -$slice, $slice); + return $result; } // cache is dirty, sync it else if ($this->caching_enabled && $cache_status==-1 && !$recursive) { $this->sync_header_index($mailbox); - return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE); + return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE, $slice); } - else + + // retrieve headers from IMAP + $a_msg_headers = array(); + + if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) { - // retrieve headers from IMAP - if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) - { - $mymsgidx = array_slice ($msg_index, $begin, $end-$begin); - $msgs = join(",", $mymsgidx); - } - else - { - $msgs = sprintf("%d:%d", $begin+1, $end); - $msg_index = range($begin, $end); - } + list($begin, $end) = $this->_get_message_range(count($msg_index), $page); + $max = max($msg_index); + $msg_index = array_slice($msg_index, $begin, $end-$begin); + if ($slice) + $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); - // fetch reuested headers from server - $a_msg_headers = array(); - $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key); + // fetch reqested headers from server + $this->_fetch_headers($mailbox, join(',', $msg_index), $a_msg_headers, $cache_key); + } + else + { + $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted); + + if (empty($a_index)) + return array(); + + asort($a_index); // ASC + $msg_index = array_keys($a_index); + $max = max($msg_index); + list($begin, $end) = $this->_get_message_range(count($msg_index), $page); + $msg_index = array_slice($msg_index, $begin, $end-$begin); - // delete cached messages with a higher index than $max+1 - // Changed $max to $max+1 to fix this bug : #1484295 - $this->clear_message_cache($cache_key, $max + 1); + if ($slice) + $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); - // kick child process to sync cache - // ... + // fetch reqested headers from server + $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); } + // delete cached messages with a higher index than $max+1 + // Changed $max to $max+1 to fix this bug : #1484295 + $this->clear_message_cache($cache_key, $max + 1); + + // kick child process to sync cache + // ... + // return empty array if no messages found - if (!is_array($a_msg_headers) || empty($a_msg_headers)) { + if (!is_array($a_msg_headers) || empty($a_msg_headers)) return array(); - } - - // if not already sorted - if (!$headers_sorted) - { - // use this class for message sorting - $sorter = new rcube_header_sorter(); - $sorter->set_sequence_numbers($msg_index); - $sorter->sort_headers($a_msg_headers); + + // use this class for message sorting + $sorter = new rcube_header_sorter(); + $sorter->set_sequence_numbers($msg_index); + $sorter->sort_headers($a_msg_headers); - if ($this->sort_order == 'DESC') - $a_msg_headers = array_reverse($a_msg_headers); - } + if ($this->sort_order == 'DESC') + $a_msg_headers = array_reverse($a_msg_headers); return array_values($a_msg_headers); } @@ -640,18 +660,20 @@ class rcube_imap * @param int Current page to list * @param string Header field to sort by * @param string Sort order [ASC|DESC] + * @param boolean Number of slice items to extract from result array * @return array Indexed array with message header objects * @access private * @see rcube_imap::list_header_set() */ - function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL) + private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { if (!strlen($mailbox) || empty($this->search_set)) return array(); $msgs = $this->search_set; $a_msg_headers = array(); - $start_msg = ($this->list_page-1) * $this->page_size; + $page = $page ? $page : $this->list_page; + $start_msg = ($page-1) * $this->page_size; $this->_set_sort_order($sort_field, $sort_order); @@ -674,6 +696,9 @@ class rcube_imap // get messages uids for one page $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); + if ($slice) + $msgs = array_slice($msgs, -$slice, $slice); + // fetch headers $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); @@ -690,6 +715,8 @@ class rcube_imap $a_index = $this->message_index('', $this->sort_field, $this->sort_order); // get messages uids for one page... $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); + if ($slice) + $msgs = array_slice($msgs, -$slice, $slice); // ...and fetch headers $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); @@ -715,7 +742,11 @@ class rcube_imap $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); // only return the requested part of the set - return array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); + $a_msg_headers = array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); + if ($slice) + $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); + + return $a_msg_headers; } } } @@ -729,9 +760,9 @@ class rcube_imap * @return array array with two values: first index, last index * @access private */ - function _get_message_range($max, $page) + private function _get_message_range($max, $page) { - $start_msg = ($this->list_page-1) * $this->page_size; + $start_msg = ($page-1) * $this->page_size; if ($page=='all') { @@ -756,7 +787,6 @@ class rcube_imap return array($begin, $end); } - /** * Fetches message headers @@ -766,41 +796,41 @@ class rcube_imap * @param string Message index to fetch * @param array Reference to message headers array * @param array Array with cache index - * @return int Number of deleted messages + * @return int Messages count * @access private */ - function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) + private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) { - // cache is incomplete - $cache_index = $this->get_message_cache_index($cache_key); - - // fetch reuested headers from server - $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs); - $deleted_count = 0; - + // fetch reqested headers from server + $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, false, $this->fetch_add_headers); + if (!empty($a_header_index)) { + // cache is incomplete + $cache_index = $this->get_message_cache_index($cache_key); + foreach ($a_header_index as $i => $headers) - { + { +/* if ($headers->deleted && $this->skip_deleted) { // delete from cache if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid) - $this->remove_message_cache($cache_key, $headers->id); + $this->remove_message_cache($cache_key, $headers->uid); - $deleted_count++; continue; } - +*/ // add message to cache if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) - $this->add_message_cache($cache_key, $headers->id, $headers); + $this->add_message_cache($cache_key, $headers->id, $headers, NULL, + !in_array((string)$headers->uid, $cache_index, true)); $a_msg_headers[$headers->uid] = $headers; } } - - return $deleted_count; + + return count($a_msg_headers); } @@ -816,7 +846,7 @@ class rcube_imap { $this->_set_sort_order($sort_field, $sort_order); - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; // we have a saved search result. get index from there @@ -829,14 +859,14 @@ class rcube_imap if ($this->sort_field && $this->search_sort_field != $this->sort_field) $this->search('', $this->search_string, $this->search_charset, $this->sort_field); - if ($this->sort_order == 'DESC') + if ($this->sort_order == 'DESC') $this->cache[$key] = array_reverse($this->search_set); - else - $this->cache[$key] = $this->search_set; + else + $this->cache[$key] = $this->search_set; } else { - $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field); + $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field, $this->skip_deleted); if ($this->sort_order=="ASC") asort($a_index); @@ -863,8 +893,7 @@ class rcube_imap } // fetch complete message index - $msg_count = $this->_messagecount($mailbox); - if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, ''))) + if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) { if ($this->sort_order == 'DESC') $a_index = array_reverse($a_index); @@ -873,7 +902,7 @@ class rcube_imap } else { - $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field); + $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted); if ($this->sort_order=="ASC") asort($a_index); @@ -894,10 +923,12 @@ class rcube_imap { $cache_key = $mailbox.'.msg'; $cache_index = $this->get_message_cache_index($cache_key); - $msg_count = $this->_messagecount($mailbox); // fetch complete message index - $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID'); + $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); + + if ($a_message_index === false) + return false; foreach ($a_message_index as $id => $uid) { @@ -911,27 +942,32 @@ class rcube_imap // message in cache but in wrong position if (in_array((string)$uid, $cache_index, TRUE)) { - unset($cache_index[$id]); + unset($cache_index[$id]); } // other message at this position if (isset($cache_index[$id])) { - $this->remove_message_cache($cache_key, $id); + $for_remove[] = $cache_index[$id]; unset($cache_index[$id]); } - - // fetch complete headers and add to cache - $headers = iil_C_FetchHeader($this->conn, $mailbox, $id); - $this->add_message_cache($cache_key, $headers->id, $headers); + $for_update[] = $id; } - // those ids that are still in cache_index have been deleted + // clear messages at wrong positions and those deleted that are still in cache_index + if (!empty($for_remove)) + $cache_index = array_merge($cache_index, $for_remove); + if (!empty($cache_index)) - { - foreach ($cache_index as $id => $uid) - $this->remove_message_cache($cache_key, $id); + $this->remove_message_cache($cache_key, $cache_index); + + // fetch complete headers and add to cache + if (!empty($for_update)) { + if ($headers = iil_C_FetchHeader($this->conn, $mailbox, join(',', $for_update), false, $this->fetch_add_headers)) + foreach ($headers as $header) + $this->add_message_cache($cache_key, $header->id, $header, NULL, + in_array((string)$header->uid, (array)$for_remove, true)); } } @@ -951,13 +987,13 @@ class rcube_imap if (!$str) return false; - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; $results = $this->_search_index($mailbox, $str, $charset, $sort_field); // try search with US-ASCII charset (should be supported by server) // only if UTF-8 search is not supported - if (empty($results) && !is_array($results) && !empty($charset) && $charset!='US-ASCII') + if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') { // convert strings to US_ASCII if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) @@ -978,7 +1014,7 @@ class rcube_imap else // strings for conversion not found $res = $str; - $results = $this->search($mbox_name, $res, 'US-ASCII', $sort_field); + $results = $this->search($mbox_name, $res, NULL, $sort_field); } $this->set_search_set($str, $results, $charset, $sort_field); @@ -994,8 +1030,13 @@ class rcube_imap * @access private * @see rcube_imap::search() */ - function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) + private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) { + $orig_criteria = $criteria; + + if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) + $criteria = 'UNDELETED '.$criteria; + if ($sort_field && $this->get_capability('sort')) { $charset = $charset ? $charset : $this->default_charset; @@ -1003,14 +1044,11 @@ class rcube_imap } else $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); - - // clean message list (there might be some empty entries) - if (is_array($a_messages)) - { - foreach ($a_messages as $i => $val) - if (empty($val)) - unset($a_messages[$i]); - } + + // update messagecount cache ? +// $a_mailbox_cache = get_cache('messagecount'); +// $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); +// $this->update_cache('messagecount', $a_mailbox_cache); return $a_messages; } @@ -1055,14 +1093,14 @@ class rcube_imap */ function get_headers($id, $mbox_name=NULL, $is_uid=TRUE, $bodystr=FALSE) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; $uid = $is_uid ? $id : $this->_id2uid($id); // get cached headers if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) return $headers; - $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr); + $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); // write headers cache if ($headers) @@ -1070,7 +1108,7 @@ class rcube_imap if ($headers->uid && $headers->id) $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; - $this->add_message_cache($mailbox.'.msg', $headers->id, $headers); + $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); } return $headers; @@ -1088,29 +1126,30 @@ class rcube_imap function &get_structure($uid, $structure_str='') { $cache_key = $this->mailbox.'.msg'; - $headers = &$this->get_cached_message($cache_key, $uid, true); + $headers = &$this->get_cached_message($cache_key, $uid); // return cached message structure if (is_object($headers) && is_object($headers->structure)) { return $headers->structure; } - // resolve message sequence number - if (!($msg_id = $this->_uid2id($uid))) { - return FALSE; - } - if (!$structure_str) - $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); + $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); $structure = iml_GetRawStructureArray($structure_str); $struct = false; // parse structure and add headers if (!empty($structure)) { - $this->_msg_id = $msg_id; $headers = $this->get_headers($uid); - + $this->_msg_id = $headers->id; + + // set message charset from message headers + if ($headers->charset) + $this->struct_charset = $headers->charset; + else + $this->struct_charset = $this->_structure_charset($structure); + $struct = &$this->_structure_part($structure); $struct->headers = get_object_vars($headers); @@ -1124,7 +1163,7 @@ class rcube_imap // write structure to cache if ($this->caching_enabled) - $this->add_message_cache($cache_key, $msg_id, $headers, $struct); + $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct); } return $struct; @@ -1140,7 +1179,7 @@ class rcube_imap { $struct = new rcube_message_part; $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; - + // multipart if (is_array($part[0])) { @@ -1162,18 +1201,19 @@ class rcube_imap // fetch message headers if message/rfc822 or named part (could contain Content-Location header) if (strtolower($part[$i][0]) == 'message' || (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL'))) { - $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.$i+1 : $i+1; + $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; } - + // pre-fetch headers of all parts (in one command for better performance) if ($part_headers) $part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, $part_headers); $struct->parts = array(); for ($i=0, $count=0; $i 3) + if (is_array($part[$i]) && count($part[$i]) > 3) { $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, - $part_headers[$struct->mime_id ? $struck->mime_id.'.'.$i+1 : $i+1]); + $part_headers[$struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1]); + } return $struct; } @@ -1240,7 +1280,7 @@ class rcube_imap // fetch message headers if message/rfc822 or named part (could contain Content-Location header) if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { if (empty($raw_headers)) - $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id); + $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $struct->mime_id); $struct->headers = $this->_parse_headers($raw_headers) + $struct->headers; } @@ -1263,7 +1303,7 @@ class rcube_imap * @param object rcube_message_part Part object * @param string Part's raw headers */ - function _set_part_filename(&$part, $headers=null) + private function _set_part_filename(&$part, $headers=null) { if (!empty($part->d_parameters['filename'])) $filename_mime = $part->d_parameters['filename']; @@ -1283,7 +1323,7 @@ class rcube_imap // we must fetch and parse headers "manually" if ($i<2) { if (!$headers) - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); $filename_mime = ''; $i = 0; while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1300,7 +1340,7 @@ class rcube_imap } if ($i<2) { if (!$headers) - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1317,7 +1357,7 @@ class rcube_imap } if ($i<2) { if (!$headers) - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); $filename_mime = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1334,7 +1374,7 @@ class rcube_imap } if ($i<2) { if (!$headers) - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id); + $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1355,7 +1395,8 @@ class rcube_imap // decode filename if (!empty($filename_mime)) { $part->filename = rcube_imap::decode_mime_string($filename_mime, - $part->charset ? $part->charset : rc_detect_encoding($filename_mime, $this->default_charset)); + $part->charset ? $part->charset : $this->struct_charset ? $this->struct_charset : + rc_detect_encoding($filename_mime, $this->default_charset)); } else if (!empty($filename_encoded)) { // decode filename according to RFC 2231, Section 4 @@ -1366,8 +1407,25 @@ class rcube_imap $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); } } - - + + + /** + * Get charset name from message structure (first part) + * + * @access private + * @param array Message structure + * @return string Charset name + */ + function _structure_charset($structure) + { + while (is_array($structure)) { + if (is_array($structure[2]) && $structure[2][0] == 'charset') + return $structure[2][1]; + $structure = $structure[0]; + } + } + + /** * Fetch message body of a specific message from the server * @@ -1380,14 +1438,15 @@ class rcube_imap */ function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) { - if (!($msg_id = $this->_uid2id($uid))) - return FALSE; - // get part encoding if not provided if (!is_object($o_part)) { - $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); + $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); $structure = iml_GetRawStructureArray($structure_str); + // error or message not found + if (empty($structure)) + return false; + $part_type = iml_GetPartTypeCode($structure, $part); $o_part = new rcube_message_part; $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other'); @@ -1399,44 +1458,19 @@ class rcube_imap if (!$part) $part = 'TEXT'; - if ($print) - { - $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2); - $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode); - - // we have to decode the part manually before printing - if ($mode == 1) - { - echo $this->mime_decode($body, $o_part->encoding); - $body = true; - } - } - else - { - if ($fp && $o_part->encoding == 'base64') - return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 3, $fp); - else - $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1); + $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, $part, + $o_part->encoding, $print, $fp); - // decode part body - if ($o_part->encoding) - $body = $this->mime_decode($body, $o_part->encoding); + if ($fp || $print) + return true; - // convert charset (if text or message part) - if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') - { - // assume default if no charset specified - if (empty($o_part->charset)) - $o_part->charset = $this->default_charset; + // convert charset (if text or message part) + if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { + // assume default if no charset specified + if (empty($o_part->charset)) + $o_part->charset = $this->default_charset; - $body = rcube_charset_convert($body, $o_part->charset); - } - - if ($fp) - { - fwrite($fp, $body); - return true; - } + $body = rcube_charset_convert($body, $o_part->charset); } return $body; @@ -1453,8 +1487,7 @@ class rcube_imap function &get_body($uid, $part=1) { $headers = $this->get_headers($uid); - return rcube_charset_convert( - $this->mime_decode($this->get_message_part($uid, $part), 'quoted-printable'), + return rcube_charset_convert($this->get_message_part($uid, $part, NULL), $headers->charset ? $headers->charset : $this->default_charset); } @@ -1467,10 +1500,7 @@ class rcube_imap */ function &get_raw_body($uid) { - if (!($msg_id = $this->_uid2id($uid))) - return FALSE; - - return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id); + return iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true); } @@ -1482,12 +1512,7 @@ class rcube_imap */ function &get_raw_headers($uid) { - if (!($msg_id = $this->_uid2id($uid))) - return FALSE; - - $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL); - - return $headers; + return iil_C_FetchPartHeader($this->conn, $this->mailbox, $uid, true); } @@ -1498,10 +1523,7 @@ class rcube_imap */ function print_raw_body($uid) { - if (!($msg_id = $this->_uid2id($uid))) - return FALSE; - - iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2); + iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, NULL, NULL, true); } @@ -1510,40 +1532,27 @@ class rcube_imap * * @param mixed Message UIDs as array or as comma-separated string * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT + * @param string Folder name * @return boolean True on success, False on failure */ - function set_flag($uids, $flag) + function set_flag($uids, $flag, $mbox_name=NULL) { + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; + $flag = strtoupper($flag); - $msg_ids = array(); if (!is_array($uids)) $uids = explode(',',$uids); - foreach ($uids as $uid) { - $msg_ids[$uid] = $this->_uid2id($uid); - } - - if ($flag=='UNDELETED') - $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids))); - else if ($flag=='UNSEEN') - $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids))); - else if ($flag=='UNFLAGGED') - $result = iil_C_UnFlag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), 'FLAGGED'); + if (strpos($flag, 'UN') === 0) + $result = iil_C_UnFlag($this->conn, $mailbox, join(',', $uids), substr($flag, 2)); else - $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag); + $result = iil_C_Flag($this->conn, $mailbox, join(',', $uids), $flag); // reload message headers if cached - $cache_key = $this->mailbox.'.msg'; if ($this->caching_enabled) { - foreach ($msg_ids as $uid => $id) - { - if ($cached_headers = $this->get_cached_message($cache_key, $uid)) - { - $this->remove_message_cache($cache_key, $id); - //$this->get_headers($uid); - } - } + $cache_key = $mailbox.'.msg'; + $this->remove_message_cache($cache_key, $uids); // close and re-open connection // this prevents connection problems with Courier @@ -1551,20 +1560,35 @@ class rcube_imap } // set nr of messages that were flaged - $count = count($msg_ids); + $count = count($uids); // clear message count cache if ($result && $flag=='SEEN') - $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1)); + $this->_set_messagecount($mailbox, 'UNSEEN', $count*(-1)); else if ($result && $flag=='UNSEEN') - $this->_set_messagecount($this->mailbox, 'UNSEEN', $count); + $this->_set_messagecount($mailbox, 'UNSEEN', $count); else if ($result && $flag=='DELETED') - $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1)); + $this->_set_messagecount($mailbox, 'ALL', $count*(-1)); return $result; } + /** + * Remove message flag for one or several messages + * + * @param mixed Message UIDs as array or as comma-separated string + * @param string Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT + * @param string Folder name + * @return boolean True on success, False on failure + * @see set_flag + */ + function unset_flag($uids, $flag, $mbox_name=NULL) + { + return $this->set_flag($uids, 'UN'.$flag, $mbox_name); + } + + /** * Append a mail message (source) to a specific mailbox * @@ -1574,7 +1598,7 @@ class rcube_imap */ function save_message($mbox_name, &$message) { - $mailbox = $this->_mod_mailbox($mbox_name); + $mailbox = $this->mod_mailbox($mbox_name); // make sure mailbox exists if (($mailbox == 'INBOX') || in_array($mailbox, $this->_list_mailboxes())) @@ -1600,8 +1624,8 @@ class rcube_imap */ function move_message($uids, $to_mbox, $from_mbox='') { - $to_mbox = $this->_mod_mailbox($to_mbox); - $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox; + $to_mbox = $this->mod_mailbox($to_mbox); + $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; // make sure mailbox exists if ($to_mbox != 'INBOX' && !in_array($to_mbox, $this->_list_mailboxes())) @@ -1616,48 +1640,33 @@ class rcube_imap $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL); // exit if no message uids are specified - if (!is_array($a_uids)) + if (!is_array($a_uids) || empty($a_uids)) return false; - // convert uids to message ids - $a_mids = array(); - foreach ($a_uids as $uid) - $a_mids[] = $this->_uid2id($uid, $from_mbox); - - $iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox); + $iil_move = iil_C_Move($this->conn, join(',', $a_uids), $from_mbox, $to_mbox); $moved = !($iil_move === false || $iil_move < 0); // send expunge command in order to have the moved message // really deleted from the source mailbox if ($moved) { - // but only when flag_for_deletion is set to false - if (!rcmail::get_instance()->config->get('flag_for_deletion', false)) - { - $this->_expunge($from_mbox, FALSE); - $this->_clear_messagecount($from_mbox); - $this->_clear_messagecount($to_mbox); - } + $this->_expunge($from_mbox, FALSE, $a_uids); + $this->_clear_messagecount($from_mbox); + $this->_clear_messagecount($to_mbox); } // moving failed else if (rcmail::get_instance()->config->get('delete_always', false)) { - return iil_C_Delete($this->conn, $from_mbox, join(',', $a_mids)); + return iil_C_Delete($this->conn, $from_mbox, join(',', $a_uids)); } // remove message ids from search set - if ($moved && $this->search_set && $from_mbox == $this->mailbox) + if ($moved && $this->search_set && $from_mbox == $this->mailbox) { + foreach ($a_uids as $uid) + $a_mids[] = $this->_uid2id($uid, $from_mbox); $this->search_set = array_diff($this->search_set, $a_mids); - + } // update cached message headers $cache_key = $from_mbox.'.msg'; - if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key))) - { - $start_index = 100000; - foreach ($a_uids as $uid) - { - if (($index = array_search($uid, $a_cache_index)) !== FALSE) - $start_index = min($index, $start_index); - } - + if ($moved && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { // clear cache from the lowest index on $this->clear_message_cache($cache_key, $start_index); } @@ -1675,46 +1684,36 @@ class rcube_imap */ function delete_message($uids, $mbox_name='') { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; // convert the list of uids to array $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL); // exit if no message uids are specified - if (!is_array($a_uids)) + if (!is_array($a_uids) || empty($a_uids)) return false; - // convert uids to message ids - $a_mids = array(); - foreach ($a_uids as $uid) - $a_mids[] = $this->_uid2id($uid, $mailbox); - - $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids)); + $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_uids)); // send expunge command in order to have the deleted message // really deleted from the mailbox if ($deleted) { - $this->_expunge($mailbox, FALSE); + $this->_expunge($mailbox, FALSE, $a_uids); $this->_clear_messagecount($mailbox); unset($this->uid_id_map[$mailbox]); } // remove message ids from search set - if ($deleted && $this->search_set && $mailbox == $this->mailbox) + if ($deleted && $this->search_set && $mailbox == $this->mailbox) { + foreach ($a_uids as $uid) + $a_mids[] = $this->_uid2id($uid, $mailbox); $this->search_set = array_diff($this->search_set, $a_mids); - + } + // remove deleted messages from cache $cache_key = $mailbox.'.msg'; - if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key))) - { - $start_index = 100000; - foreach ($a_uids as $uid) - { - if (($index = array_search($uid, $a_cache_index)) !== FALSE) - $start_index = min($index, $start_index); - } - + if ($deleted && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { // clear cache from the lowest index on $this->clear_message_cache($cache_key, $start_index); } @@ -1731,7 +1730,7 @@ class rcube_imap */ function clear_mailbox($mbox_name=NULL) { - $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); if ($msg_count>0) @@ -1763,7 +1762,7 @@ class rcube_imap */ function expunge($mbox_name='', $clear_cache=TRUE) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_expunge($mailbox, $clear_cache); } @@ -1772,11 +1771,20 @@ class rcube_imap * Send IMAP expunge command and clear cache * * @see rcube_imap::expunge() + * @param string Mailbox name + * @param boolean False if cache should not be cleared + * @param string List of UIDs to remove, separated by comma + * @return boolean True on success * @access private */ - function _expunge($mailbox, $clear_cache=TRUE) + private function _expunge($mailbox, $clear_cache=TRUE, $uids=NULL) { - $result = iil_C_Expunge($this->conn, $mailbox); + if ($uids && $this->get_capability('UIDPLUS')) + $a_uids = is_array($uids) ? join(',', $uids) : $uids; + else + $a_uids = NULL; + + $result = iil_C_Expunge($this->conn, $mailbox, $a_uids); if ($result>=0 && $clear_cache) { @@ -1807,12 +1815,12 @@ class rcube_imap return $sa_unsubscribed; // retrieve list of folders from IMAP server - $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*'); + $a_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox($root), '*'); // modify names with root dir foreach ($a_mboxes as $mbox_name) { - $name = $this->_mod_mailbox($mbox_name, 'out'); + $name = $this->mod_mailbox($mbox_name, 'out'); if (strlen($name)) $a_folders[] = $name; } @@ -1884,7 +1892,7 @@ class rcube_imap // reduce mailbox name to 100 chars $name = substr($name, 0, 100); - $abs_name = $this->_mod_mailbox($name); + $abs_name = $this->mod_mailbox($name); $a_mailbox_cache = $this->get_cache('mailboxes'); if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache))) @@ -1913,8 +1921,8 @@ class rcube_imap $name = substr($new_name, 0, 100); // make absolute path - $mailbox = $this->_mod_mailbox($mbox_name); - $abs_name = $this->_mod_mailbox($name); + $mailbox = $this->mod_mailbox($mbox_name); + $abs_name = $this->mod_mailbox($name); // check if mailbox is subscribed $a_subscribed = $this->_list_mailboxes(); @@ -1955,7 +1963,7 @@ class rcube_imap /** * Remove mailboxes from server * - * @param string Mailbox name + * @param string Mailbox name(s) string/array * @return boolean True on success */ function delete_mailbox($mbox_name) @@ -1967,21 +1975,23 @@ class rcube_imap else if (is_string($mbox_name) && strlen($mbox_name)) $a_mboxes = explode(',', $mbox_name); - $all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*'); + $all_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox($root), '*'); if (is_array($a_mboxes)) foreach ($a_mboxes as $mbox_name) { - $mailbox = $this->_mod_mailbox($mbox_name); + $mailbox = $this->mod_mailbox($mbox_name); // unsubscribe mailbox before deleting iil_C_UnSubscribe($this->conn, $mailbox); // send delete command to server $result = iil_C_DeleteFolder($this->conn, $mailbox); - if ($result>=0) + if ($result >= 0) { $deleted = TRUE; - + $this->clear_message_cache($mailbox.'.msg'); + } + foreach ($all_mboxes as $c_mbox) { $regex = preg_quote($mailbox . $this->delimiter, '/'); @@ -1990,18 +2000,17 @@ class rcube_imap { iil_C_UnSubscribe($this->conn, $c_mbox); $result = iil_C_DeleteFolder($this->conn, $c_mbox); - if ($result>=0) + if ($result >= 0) { $deleted = TRUE; - } + $this->clear_message_cache($c_mbox.'.msg'); + } + } } } // clear mailboxlist cache if ($deleted) - { - $this->clear_message_cache($mailbox.'.msg'); $this->clear_cache('mailboxes'); - } return $deleted; } @@ -2012,13 +2021,13 @@ class rcube_imap */ function create_default_folders() { - $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*'); - $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*'); + $a_folders = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), '*'); + $a_subscribed = iil_C_ListSubscribed($this->conn, $this->mod_mailbox(''), '*'); // create default folders if they do not exist foreach ($this->default_folders as $folder) { - $abs_name = $this->_mod_mailbox($folder); + $abs_name = $this->mod_mailbox($folder); if (!in_array_nocase($abs_name, $a_folders)) $this->create_mailbox($folder, TRUE); else if (!in_array_nocase($abs_name, $a_subscribed)) @@ -2051,8 +2060,7 @@ class rcube_imap // read cache if (!isset($this->cache[$key]) && $this->caching_enabled) { - $cache_data = $this->_read_cache_record('IMAP.'.$key); - $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE; + return $this->_read_cache_record($key); } return $this->cache[$key]; @@ -2078,7 +2086,7 @@ class rcube_imap foreach ($this->cache as $key => $data) { if ($this->cache_changes[$key]) - $this->_write_cache_record('IMAP.'.$key, serialize($data)); + $this->_write_cache_record($key, serialize($data)); } } } @@ -2094,7 +2102,7 @@ class rcube_imap if ($key===NULL) { foreach ($this->cache as $key => $data) - $this->_clear_cache_record('IMAP.'.$key); + $this->_clear_cache_record($key); $this->cache = array(); $this->cache_changed = FALSE; @@ -2102,7 +2110,7 @@ class rcube_imap } else { - $this->_clear_cache_record('IMAP.'.$key); + $this->_clear_cache_record($key); $this->cache_changes[$key] = FALSE; unset($this->cache[$key]); } @@ -2111,35 +2119,33 @@ class rcube_imap /** * @access private */ - function _read_cache_record($key) + private function _read_cache_record($key) { - $cache_data = FALSE; - if ($this->db) { // get cached data from DB $sql_result = $this->db->query( - "SELECT cache_id, data + "SELECT cache_id, data, cache_key FROM ".get_table_name('cache')." WHERE user_id=? - AND cache_key=?", - $_SESSION['user_id'], - $key); + AND cache_key LIKE 'IMAP.%'", + $_SESSION['user_id']); - if ($sql_arr = $this->db->fetch_assoc($sql_result)) + while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $cache_data = $sql_arr['data']; - $this->cache_keys[$key] = $sql_arr['cache_id']; + $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); + $this->cache_keys[$sql_key] = $sql_arr['cache_id']; + $this->cache[$sql_key] = $sql_arr['data'] ? unserialize($sql_arr['data']) : FALSE; } } - return $cache_data; + return $this->cache[$key]; } /** * @access private */ - function _write_cache_record($key, $data) + private function _write_cache_record($key, $data) { if (!$this->db) return FALSE; @@ -2153,7 +2159,7 @@ class rcube_imap WHERE user_id=? AND cache_key=?", $_SESSION['user_id'], - $key); + 'IMAP.'.$key); if ($sql_arr = $this->db->fetch_assoc($sql_result)) $this->cache_keys[$key] = $sql_arr['cache_id']; @@ -2171,7 +2177,7 @@ class rcube_imap AND cache_key=?", $data, $_SESSION['user_id'], - $key); + 'IMAP.'.$key); } // add new cache record else @@ -2181,7 +2187,7 @@ class rcube_imap (created, user_id, cache_key, data) VALUES (".$this->db->now().", ?, ?, ?)", $_SESSION['user_id'], - $key, + 'IMAP.'.$key, $data); } } @@ -2189,14 +2195,14 @@ class rcube_imap /** * @access private */ - function _clear_cache_record($key) + private function _clear_cache_record($key) { $this->db->query( "DELETE FROM ".get_table_name('cache')." WHERE user_id=? AND cache_key=?", $_SESSION['user_id'], - $key); + 'IMAP.'.$key); } @@ -2213,8 +2219,8 @@ class rcube_imap * @param string Internal cache key * @return int -3 = off, -2 = incomplete, -1 = dirty */ - function check_cache_status($mailbox, $cache_key) - { + private function check_cache_status($mailbox, $cache_key) + { if (!$this->caching_enabled) return -3; @@ -2224,30 +2230,42 @@ class rcube_imap // console("Cache check: $msg_count !== ".count($cache_index)); - if ($cache_count==$msg_count) - { - // get highest index - $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count"); - $cache_uid = array_pop($cache_index); - - // uids of highest message matches -> cache seems OK - if ($cache_uid == $header->uid) - return 1; + if ($cache_count==$msg_count) { + if ($this->skip_deleted) { + $h_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); + + if (sizeof($h_index) == $cache_count) { + $cache_index = array_flip($cache_index); + foreach ($h_index as $idx => $uid) + unset($cache_index[$uid]); + if (empty($cache_index)) + return 1; + } + return -2; + } else { + // get highest index + $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count"); + $cache_uid = array_pop($cache_index); + + // uids of highest message matches -> cache seems OK + if ($cache_uid == $header->uid) + return 1; + } // cache is dirty return -1; - } + } // if cache count differs less than 10% report as dirty else if (abs($msg_count - $cache_count) < $msg_count/10) return -1; else return -2; - } + } /** * @access private */ - function get_message_cache($key, $from, $to, $sort_field, $sort_order) + private function get_message_cache($key, $from, $to, $sort_field, $sort_order) { $cache_key = "$key:$from:$to:$sort_field:$sort_order"; $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); @@ -2263,8 +2281,7 @@ class rcube_imap FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? - ORDER BY ".$this->db->quoteIdentifier($sort_field)." ". - strtoupper($sort_order), + ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), $from, $to-$from, $_SESSION['user_id'], @@ -2273,30 +2290,28 @@ class rcube_imap while ($sql_arr = $this->db->fetch_assoc($sql_result)) { $uid = $sql_arr['uid']; - $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']); - + $this->cache[$cache_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); + // featch headers if unserialize failed if (empty($this->cache[$cache_key][$uid])) - $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true); + $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); } } - + return $this->cache[$cache_key]; } /** * @access private */ - function &get_cached_message($key, $uid, $struct=false) + private function &get_cached_message($key, $uid) { $internal_key = '__single_msg'; - if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || - ($struct && empty($this->cache[$internal_key][$uid]->structure)))) + if ($this->caching_enabled && !isset($this->cache[$internal_key][$uid])) { - $sql_select = "idx, uid, headers" . ($struct ? ", structure" : ''); $sql_result = $this->db->query( - "SELECT $sql_select + "SELECT idx, headers, structure FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? @@ -2307,9 +2322,10 @@ class rcube_imap if ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']); + $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; + $this->cache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure'])) - $this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']); + $this->cache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); } } @@ -2319,7 +2335,7 @@ class rcube_imap /** * @access private */ - function get_message_cache_index($key, $force=FALSE, $sort_field='idx', $sort_order='ASC') + private function get_message_cache_index($key, $force=FALSE, $sort_field='idx', $sort_order='ASC') { static $sa_message_index = array(); @@ -2349,21 +2365,22 @@ class rcube_imap /** * @access private */ - function add_message_cache($key, $index, $headers, $struct=null) + private function add_message_cache($key, $index, $headers, $struct=null, $force=false) { if (empty($key) || !is_object($headers) || empty($headers->uid)) return; // add to internal (fast) cache - $this->cache['__single_msg'][$headers->uid] = $headers; + $this->cache['__single_msg'][$headers->uid] = clone $headers; $this->cache['__single_msg'][$headers->uid]->structure = $struct; - + // no further caching if (!$this->caching_enabled) return; // check for an existing record (probly headers are cached but structure not) - $sql_result = $this->db->query( + if (!$force) { + $sql_result = $this->db->query( "SELECT message_id FROM ".get_table_name('messages')." WHERE user_id=? @@ -2373,18 +2390,21 @@ class rcube_imap $_SESSION['user_id'], $key, $headers->uid); + if ($sql_arr = $this->db->fetch_assoc($sql_result)) + $message_id = $sql_arr['message_id']; + } // update cache record - if ($sql_arr = $this->db->fetch_assoc($sql_result)) + if ($message_id) { $this->db->query( "UPDATE ".get_table_name('messages')." SET idx=?, headers=?, structure=? WHERE message_id=?", $index, - serialize($headers), - is_object($struct) ? serialize($struct) : NULL, - $sql_arr['message_id'] + serialize($this->db->encode(clone $headers)), + is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL, + $message_id ); } else // insert new record @@ -2397,13 +2417,13 @@ class rcube_imap $key, $index, $headers->uid, - (string)substr($this->decode_header($headers->subject, TRUE), 0, 128), - (string)substr($this->decode_header($headers->from, TRUE), 0, 128), - (string)substr($this->decode_header($headers->to, TRUE), 0, 128), - (string)substr($this->decode_header($headers->cc, TRUE), 0, 128), + (string)mb_substr($this->db->encode($this->decode_header($headers->subject, TRUE)), 0, 128), + (string)mb_substr($this->db->encode($this->decode_header($headers->from, TRUE)), 0, 128), + (string)mb_substr($this->db->encode($this->decode_header($headers->to, TRUE)), 0, 128), + (string)mb_substr($this->db->encode($this->decode_header($headers->cc, TRUE)), 0, 128), (int)$headers->size, - serialize($headers), - is_object($struct) ? serialize($struct) : NULL + serialize($this->db->encode(clone $headers)), + is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL ); } } @@ -2411,25 +2431,24 @@ class rcube_imap /** * @access private */ - function remove_message_cache($key, $index) + private function remove_message_cache($key, $uids) { if (!$this->caching_enabled) return; $this->db->query( "DELETE FROM ".get_table_name('messages')." - WHERE user_id=? - AND cache_key=? - AND idx=?", + WHERE user_id=? + AND cache_key=? + AND uid IN (".$this->db->array2list($uids, 'integer').")", $_SESSION['user_id'], - $key, - $index); + $key); } /** * @access private */ - function clear_message_cache($key, $start_index=1) + private function clear_message_cache($key, $start_index=1) { if (!$this->caching_enabled) return; @@ -2444,7 +2463,28 @@ class rcube_imap $start_index); } + /** + * @access private + */ + private function get_message_cache_index_min($key, $uids=NULL) + { + if (!$this->caching_enabled) + return; + + $sql_result = $this->db->query( + "SELECT MIN(idx) AS minidx + FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=?" + .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : ''), + $_SESSION['user_id'], + $key); + if ($sql_arr = $this->db->fetch_assoc($sql_result)) + return $sql_arr['minidx']; + else + return 0; + } /* -------------------------------- @@ -2608,7 +2648,7 @@ class rcube_imap * * @access private */ - function _decode_mime_string_part($str) + private function _decode_mime_string_part($str) { $a = explode('?', $str); $count = count($a); @@ -2640,16 +2680,11 @@ class rcube_imap * @param string Input string * @param string Part encoding * @return string Decoded string - * @access private */ function mime_decode($input, $encoding='7bit') { switch (strtolower($encoding)) { - case '7bit': - return $input; - break; - case 'quoted-printable': return quoted_printable_decode($input); break; @@ -2657,7 +2692,15 @@ class rcube_imap case 'base64': return base64_decode($input); break; - + + case 'x-uuencode': + case 'x-uue': + case 'uue': + case 'uuencode': + return convert_uudecode($input); + break; + + case '7bit': default: return $input; } @@ -2665,7 +2708,7 @@ class rcube_imap /** - * Convert body charset to UTF-8 according to the ctype_parameters + * Convert body charset to RCMAIL_CHARSET according to the ctype_parameters * * @param string Part body to decode * @param string Charset to convert from @@ -2690,7 +2733,7 @@ class rcube_imap */ function get_id($uid, $mbox_name=NULL) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_uid2id($uid, $mailbox); } @@ -2704,21 +2747,19 @@ class rcube_imap */ function get_uid($id,$mbox_name=NULL) { - $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox; + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_id2uid($id, $mailbox); } - - /* -------------------------------- - * private methods - * --------------------------------*/ - - /** - * @access private + * Modify folder name for input/output according to root dir and namespace + * + * @param string Folder name + * @param string Mode + * @return string Folder name */ - function _mod_mailbox($mbox_name, $mode='in') + function mod_mailbox($mbox_name, $mode='in') { if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX') return $mbox_name; @@ -2731,11 +2772,16 @@ class rcube_imap return $mbox_name; } + + /* -------------------------------- + * private methods + * --------------------------------*/ + /** * Validate the given input and save to local properties * @access private */ - function _set_sort_order($sort_field, $sort_order) + private function _set_sort_order($sort_field, $sort_order) { if ($sort_field != null) $this->sort_field = asciiwords($sort_field); @@ -2747,7 +2793,7 @@ class rcube_imap * Sort mailboxes first by default folders and then in alphabethical order * @access private */ - function _sort_mailbox_list($a_folders) + private function _sort_mailbox_list($a_folders) { $a_out = $a_defaults = $folders = array(); @@ -2762,7 +2808,7 @@ class rcube_imap if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p]) $a_defaults[$p] = $folder; else - $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7')); + $folders[$folder] = mb_strtolower(rcube_charset_convert($folder, 'UTF7-IMAP')); } // sort folders and place defaults on the top @@ -2788,7 +2834,7 @@ class rcube_imap /** * @access private */ - function _rsort($folder, $delimiter, &$list, &$out) + private function _rsort($folder, $delimiter, &$list, &$out) { while (list($key, $name) = each($list)) { if (strpos($name, $folder.$delimiter) === 0) { @@ -2805,7 +2851,7 @@ class rcube_imap /** * @access private */ - function _uid2id($uid, $mbox_name=NULL) + private function _uid2id($uid, $mbox_name=NULL) { if (!$mbox_name) $mbox_name = $this->mailbox; @@ -2819,7 +2865,7 @@ class rcube_imap /** * @access private */ - function _id2uid($id, $mbox_name=NULL) + private function _id2uid($id, $mbox_name=NULL) { if (!$mbox_name) $mbox_name = $this->mailbox; @@ -2841,26 +2887,23 @@ class rcube_imap * Subscribe/unsubscribe a list of mailboxes and update local cache * @access private */ - function _change_subscription($a_mboxes, $mode) + private function _change_subscription($a_mboxes, $mode) { $updated = FALSE; - + if (is_array($a_mboxes)) foreach ($a_mboxes as $i => $mbox_name) { - $mailbox = $this->_mod_mailbox($mbox_name); + $mailbox = $this->mod_mailbox($mbox_name); $a_mboxes[$i] = $mailbox; if ($mode=='subscribe') - $result = iil_C_Subscribe($this->conn, $mailbox); + $updated = iil_C_Subscribe($this->conn, $mailbox); else if ($mode=='unsubscribe') - $result = iil_C_UnSubscribe($this->conn, $mailbox); - - if ($result>=0) - $updated = TRUE; + $updated = iil_C_UnSubscribe($this->conn, $mailbox); } - - // get cached mailbox list + + // get cached mailbox list if ($updated) { $a_mailbox_cache = $this->get_cache('mailboxes'); @@ -2872,7 +2915,7 @@ class rcube_imap $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes); else if ($mode=='unsubscribe') $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes); - + // write mailboxlist to cache $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache)); } @@ -2885,7 +2928,7 @@ class rcube_imap * Increde/decrese messagecount for a specific mailbox * @access private */ - function _set_messagecount($mbox_name, $mode, $increment) + private function _set_messagecount($mbox_name, $mode, $increment) { $a_mailbox_cache = FALSE; $mailbox = $mbox_name ? $mbox_name : $this->mailbox; @@ -2914,7 +2957,7 @@ class rcube_imap * Remove messagecount of a specific mailbox from cache * @access private */ - function _clear_messagecount($mbox_name='') + private function _clear_messagecount($mbox_name='') { $a_mailbox_cache = FALSE; $mailbox = $mbox_name ? $mbox_name : $this->mailbox; @@ -2933,7 +2976,7 @@ class rcube_imap * Split RFC822 header string into an associative array * @access private */ - function _parse_headers($headers) + private function _parse_headers($headers) { $a_headers = array(); $lines = explode("\n", $headers); @@ -2956,7 +2999,7 @@ class rcube_imap /** * @access private */ - function _parse_address_list($str, $decode=true) + private function _parse_address_list($str, $decode=true) { // remove any newlines and carriage returns before $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str)); @@ -3009,6 +3052,13 @@ class rcube_message_part var $d_parameters = array(); var $ctype_parameters = array(); + function __clone() + { + if (isset($this->parts)) + foreach ($this->parts as $idx => $part) + if (is_object($part)) + $this->parts[$idx] = clone $part; + } } @@ -3066,71 +3116,3 @@ class rcube_header_sorter return $posa - $posb; } } - - -/** - * Add quoted-printable encoding to a given string - * - * @param string String to encode - * @param int Add new line after this number of characters - * @param boolean True if spaces should be converted into =20 - * @return string Encoded string - */ -function quoted_printable_encode($input, $line_max=76, $space_conv=false) - { - $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'); - $lines = preg_split("/(?:\r\n|\r|\n)/", $input); - $eol = "\r\n"; - $escape = "="; - $output = ""; - - while( list(, $line) = each($lines)) - { - //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary - $linlen = strlen($line); - $newline = ""; - for($i = 0; $i < $linlen; $i++) - { - $c = substr( $line, $i, 1 ); - $dec = ord( $c ); - if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E - { - $c = "=2E"; - } - if ( $dec == 32 ) - { - if ( $i == ( $linlen - 1 ) ) // convert space at eol only - { - $c = "=20"; - } - else if ( $space_conv ) - { - $c = "=20"; - } - } - else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) // always encode "\t", which is *not* required - { - $h2 = floor($dec/16); - $h1 = floor($dec%16); - $c = $escape.$hex["$h2"].$hex["$h1"]; - } - - if ( (strlen($newline) + strlen($c)) >= $line_max ) // CRLF is not counted - { - $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay - $newline = ""; - // check if newline first character will be point or not - if ( $dec == 46 ) - { - $c = "=2E"; - } - } - $newline .= $c; - } // end of for - $output .= $newline.$eol; - } // end of while - - return trim($output); - } - - diff --git a/program/include/rcube_json_output.php b/program/include/rcube_json_output.php index 0bd3a2b..ea23dc2 100644 --- a/program/include/rcube_json_output.php +++ b/program/include/rcube_json_output.php @@ -29,10 +29,12 @@ class rcube_json_output { private $config; - private $charset = 'UTF-8'; + private $charset = RCMAIL_CHARSET; private $env = array(); private $texts = array(); private $commands = array(); + private $callbacks = array(); + private $message = null; public $type = 'js'; public $ajax_call = true; @@ -66,7 +68,7 @@ class rcube_json_output public function set_pagetitle($title) { $name = $this->config->get('product_name'); - $this->command('set_pagetitle', JQ(empty($name) ? $title : $name.' :: '.$title)); + $this->command('set_pagetitle', empty($name) ? $title : $name.' :: '.$title); } /** @@ -121,7 +123,12 @@ class rcube_json_output */ public function command() { - $this->commands[] = func_get_args(); + $cmd = func_get_args(); + + if (strpos($cmd[0], 'plugin.') === 0) + $this->callbacks[] = $cmd; + else + $this->commands[] = $cmd; } @@ -130,8 +137,11 @@ class rcube_json_output */ public function add_label() { - $arg_list = func_get_args(); - foreach ($arg_list as $i => $name) { + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) + $args = $args[0]; + + foreach ($args as $name) { $this->texts[$name] = rcube_label($name); } } @@ -143,15 +153,19 @@ class rcube_json_output * @param string Message to display * @param string Message type [notice|confirm|error] * @param array Key-value pairs to be replaced in localized text + * @param boolean Override last set message * @uses self::command() */ - public function show_message($message, $type='notice', $vars=null) + public function show_message($message, $type='notice', $vars=null, $override=true) { - $this->command( - 'display_message', - rcube_label(array('name' => $message, 'vars' => $vars)), - $type - ); + if ($override || !$this->message) { + $this->message = $message; + $this->command( + 'display_message', + rcube_label(array('name' => $message, 'vars' => $vars)), + $type + ); + } } /** @@ -196,26 +210,36 @@ class rcube_json_output * @return void * @deprecated */ - public function remote_response($add='', $flush=false) + public function remote_response($add='') { static $s_header_sent = false; if (!$s_header_sent) { $s_header_sent = true; send_nocacheing_headers(); - header('Content-Type: application/x-javascript; charset=' . $this->get_charset()); + header('Content-Type: text/plain; charset=' . $this->get_charset()); print '/** ajax response ['.date('d/M/Y h:i:s O')."] **/\n"; } // unset default env vars unset($this->env['task'], $this->env['action'], $this->env['comm_path']); - // send response code - echo $this->get_js_commands() . $add; + $rcmail = rcmail::get_instance(); + $response = array('action' => $rcmail->action, 'unlock' => (bool)$_REQUEST['_unlock']); + + if (!empty($this->env)) + $response['env'] = $this->env; + + if (!empty($this->texts)) + $response['texts'] = $this->texts; - // flush the output buffer - if ($flush) - flush(); + // send function calls + $response['exec'] = $this->get_js_commands() . $add; + + if (!empty($this->callbacks)) + $response['callbacks'] = $this->callbacks; + + echo json_serialize($response); } @@ -227,14 +251,7 @@ class rcube_json_output private function get_js_commands() { $out = ''; - - if (sizeof($this->env)) - $out .= 'this.set_env('.json_serialize($this->env).");\n"; - foreach($this->texts as $name => $text) { - $out .= sprintf("this.add_label('%s', '%s');\n", $name, JQ($text)); - } - foreach ($this->commands as $i => $args) { $method = array_shift($args); foreach ($args as $i => $arg) { diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index f1bbab6..e071ed0 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -14,7 +14,7 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: rcube_ldap.php 2237 2009-01-17 01:55:39Z till $ + $Id: rcube_ldap.php 2894 2009-08-29 20:56:00Z alec $ */ @@ -24,7 +24,7 @@ * * @package Addressbook */ -class rcube_ldap +class rcube_ldap extends rcube_addressbook { var $conn; var $prop = array(); @@ -52,12 +52,15 @@ class rcube_ldap function __construct($p) { $this->prop = $p; - + foreach ($p as $prop => $value) if (preg_match('/^(.+)_field$/', $prop, $matches)) - $this->fieldmap[$matches[1]] = $value; + $this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value)); + + foreach ($this->prop['required_fields'] as $key => $val) + $this->prop['required_fields'][$key] = $this->_attr_name(strtolower($val)); - $this->sort_col = $p["sort"]; + $this->sort_col = $p['sort']; $this->connect(); } @@ -102,10 +105,10 @@ class rcube_ldap $this->ready = true; // User specific access, generate the proper values to use. - if ($this->prop["user_specific"]) { + if ($this->prop['user_specific']) { // No password set, use the session password if (empty($this->prop['bind_pass'])) { - $this->prop['bind_pass'] = $RCMAIL->decrypt_passwd($_SESSION["password"]); + $this->prop['bind_pass'] = $RCMAIL->decrypt($_SESSION['password']); } // Get the pieces needed for variable replacement. @@ -166,7 +169,7 @@ class rcube_ldap { if ($this->conn) { - @ldap_unbind($this->conn); + ldap_unbind($this->conn); $this->conn = null; } } @@ -244,19 +247,19 @@ class rcube_ldap $filter = $this->prop['filter']; $this->set_search_set($filter); } - + // exec LDAP search if no result resource is stored if ($this->conn && !$this->ldap_result) $this->_exec_search(); // count contacts for this user $this->result = $this->count(); - + // we have a search result resource if ($this->ldap_result && $this->result->count > 0) { - if ($this->sort_col && $this->prop['scope'] !== "base") - @ldap_sort($this->conn, $this->ldap_result, $this->sort_col); + if ($this->sort_col && $this->prop['scope'] !== 'base') + ldap_sort($this->conn, $this->ldap_result, $this->sort_col); $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first; $last_row = $this->result->first + $this->page_size; @@ -381,13 +384,15 @@ class rcube_ldap $res = null; if ($this->conn && $dn) { - $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap)); + $this->ldap_result = ldap_read($this->conn, base64_decode($dn), '(objectclass=*)', array_values($this->fieldmap)); $entry = @ldap_first_entry($this->conn, $this->ldap_result); - + if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) { + $rec = array_change_key_case($rec, CASE_LOWER); + // Add in the dn for the entry. - $rec["dn"] = base64_decode($dn); + $rec['dn'] = base64_decode($dn); $res = $this->_ldap2result($rec); $this->result = new rcube_result_set(1); $this->result->add($res); @@ -408,11 +413,10 @@ class rcube_ldap { // Map out the column names to their LDAP ones to build the new entry. $newentry = array(); - $newentry["objectClass"] = $this->prop["LDAP_Object_Classes"]; + $newentry['objectClass'] = $this->prop['LDAP_Object_Classes']; foreach ($save_cols as $col => $val) { - $fld = ""; $fld = $this->_map_field($col); - if ($fld != "") { + if ($fld && $val) { // The field does exist, add it to the entry. $newentry[$fld] = $val; } // end if @@ -421,15 +425,15 @@ class rcube_ldap // Verify that the required fields are set. // We know that the email address is required as a default of rcube, so // we will default its value into any unfilled required fields. - foreach ($this->prop["required_fields"] as $fld) { + foreach ($this->prop['required_fields'] as $fld) { if (!isset($newentry[$fld])) { - $newentry[$fld] = $newentry[$this->_map_field("email")]; + $newentry[$fld] = $newentry[$this->_map_field('email')]; } // end if } // end foreach // Build the new entries DN. - $dn = $this->prop["LDAP_rdn"]."=".$newentry[$this->prop["LDAP_rdn"]].",".$this->prop['base_dn']; - $res = @ldap_add($this->conn, $dn, $newentry); + $dn = $this->prop['LDAP_rdn'].'='.$newentry[$this->prop['LDAP_rdn']].','.$this->prop['base_dn']; + $res = ldap_add($this->conn, $dn, $newentry); if ($res === FALSE) { return false; } // end if @@ -455,9 +459,8 @@ class rcube_ldap $replacedata = array(); $deletedata = array(); foreach ($save_cols as $col => $val) { - $fld = ""; $fld = $this->_map_field($col); - if ($fld != "") { + if ($fld) { // The field does exist compare it to the ldap record. if ($record[$col] != $val) { // Changed, but find out how. @@ -465,9 +468,9 @@ class rcube_ldap // Field was not set prior, need to add it. $newdata[$fld] = $val; } // end if - elseif ($val == "") { + elseif ($val == '') { // Field supplied is empty, verify that it is not required. - if (!in_array($fld, $this->prop["required_fields"])) { + if (!in_array($fld, $this->prop['required_fields'])) { // It is not, safe to clear. $deletedata[$fld] = $record[$col]; } // end if @@ -480,32 +483,43 @@ class rcube_ldap } // end if } // end foreach - // Update the entry as required. $dn = base64_decode($id); + + // Update the entry as required. if (!empty($deletedata)) { // Delete the fields. - $res = @ldap_mod_del($this->conn, $dn, $deletedata); - if ($res === FALSE) { + if (!ldap_mod_del($this->conn, $dn, $deletedata)) return false; - } // end if } // end if if (!empty($replacedata)) { + // Handle RDN change + if ($replacedata[$this->prop['LDAP_rdn']]) { + $newdn = $this->prop['LDAP_rdn'].'='.$replacedata[$this->prop['LDAP_rdn']].','.$this->prop['base_dn']; + if ($dn != $newdn) { + $newrdn = $this->prop['LDAP_rdn'].'='.$replacedata[$this->prop['LDAP_rdn']]; + unset($replacedata[$this->prop['LDAP_rdn']]); + } + } // Replace the fields. - $res = @ldap_mod_replace($this->conn, $dn, $replacedata); - if ($res === FALSE) { - return false; + if (!empty($replacedata)) { + if (!ldap_mod_replace($this->conn, $dn, $replacedata)) + return false; } // end if } // end if if (!empty($newdata)) { // Add the fields. - $res = @ldap_mod_add($this->conn, $dn, $newdata); - if ($res === FALSE) { + if (!ldap_mod_add($this->conn, $dn, $newdata)) return false; - } // end if } // end if + // Handle RDN change + if (!empty($newrdn)) { + if (@ldap_rename($this->conn, $dn, $newrdn, NULL, TRUE)) + return base64_encode($newdn); + } + return true; } @@ -520,13 +534,13 @@ class rcube_ldap { if (!is_array($ids)) { // Not an array, break apart the encoded DNs. - $dns = explode(",", $ids); + $dns = explode(',', $ids); } // end if foreach ($dns as $id) { $dn = base64_decode($id); // Delete the record. - $res = @ldap_delete($this->conn, $dn); + $res = ldap_delete($this->conn, $dn); if ($res === FALSE) { return false; } // end if @@ -541,12 +555,13 @@ class rcube_ldap * * @access private */ - function _exec_search() + private function _exec_search() { - if ($this->ready && $this->filter) + if ($this->ready) { + $filter = $this->filter ? $this->filter : '(objectclass=*)'; $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list'); - $this->ldap_result = $function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0); + $this->ldap_result = $function($this->conn, $this->prop['base_dn'], $filter, array_values($this->fieldmap), 0, 0); return true; } else @@ -557,8 +572,10 @@ class rcube_ldap /** * @access private */ - function _ldap2result($rec) + private function _ldap2result($rec) { + global $RCMAIL; + $out = array(); if ($rec['dn']) @@ -566,8 +583,12 @@ class rcube_ldap foreach ($this->fieldmap as $rf => $lf) { - if ($rec[$lf]['count']) - $out[$rf] = $rec[$lf][0]; + if ($rec[$lf]['count']) { + if ($rf == 'email' && !strpos($rec[$lf][0], '@')) + $out[$rf] = sprintf('%s@%s', $rec[$lf][0] , $RCMAIL->config->mail_domain($_SESSION['imap_host'])); + else + $out[$rf] = $rec[$lf][0]; + } } return $out; @@ -577,12 +598,29 @@ class rcube_ldap /** * @access private */ - function _map_field($field) + private function _map_field($field) { return $this->fieldmap[$field]; } + /** + * @access private + */ + private function _attr_name($name) + { + // list of known attribute aliases + $aliases = array( + 'gn' => 'givenname', + 'rfc822mailbox' => 'mail', + 'userid' => 'uid', + 'emailaddress' => 'email', + 'pkcs9email' => 'email', + ); + return isset($aliases[$name]) ? $aliases[$name] : $name; + } + + /** * @static */ @@ -591,7 +629,6 @@ class rcube_ldap return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c')); } - } - +?> diff --git a/program/include/rcube_mail_mime.php b/program/include/rcube_mail_mime.php index f59354f..ab93d3a 100644 --- a/program/include/rcube_mail_mime.php +++ b/program/include/rcube_mail_mime.php @@ -180,7 +180,7 @@ class rcube_mail_mime extends Mail_mime } } - $input[$hdr_name] = $hdr_value; + $input[$hdr_name] = wordwrap($hdr_value, 990, "\n", true); // hard limit header length } return $input; diff --git a/program/include/rcube_mdb2.php b/program/include/rcube_mdb2.php index 36b5cc7..51f8be3 100644 --- a/program/include/rcube_mdb2.php +++ b/program/include/rcube_mdb2.php @@ -16,7 +16,7 @@ | Author: Lukas Kahwe Smith | +-----------------------------------------------------------------------+ - $Id: rcube_mdb2.php 2237 2009-01-17 01:55:39Z till $ + $Id: rcube_mdb2.php 2834 2009-08-04 08:22:41Z alec $ */ @@ -106,7 +106,7 @@ class rcube_mdb2 if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) $this->_sqlite_create_database($dbh, $this->sqlite_initials); } - else + else if ($this->db_provider!='mssql') $dbh->setCharset('utf8'); return $dbh; @@ -177,6 +177,17 @@ class rcube_mdb2 } + /** + * Connection state checker + * + * @param boolean True if in connected state + */ + function is_connected() + { + return PEAR::isError($this->db_handle) ? false : true; + } + + /** * Execute a SQL query * @@ -187,6 +198,9 @@ class rcube_mdb2 */ function query() { + if (!$this->is_connected()) + return NULL; + $params = func_get_args(); $query = array_shift($params); @@ -228,7 +242,7 @@ class rcube_mdb2 function _query($query, $offset, $numrows, $params) { // Read or write ? - if (strtolower(trim(substr($query,0,6)))=='select') + if (strtolower(substr(trim($query),0,6))=='select') $mode='r'; else $mode='w'; @@ -307,16 +321,22 @@ class rcube_mdb2 * Get last inserted record ID * For Postgres databases, a sequence name is required * - * @param string Sequence name for increment + * @param string Table name (to find the incremented sequence) * @return mixed ID or FALSE on failure * @access public */ - function insert_id($sequence = '') + function insert_id($table = '') { if (!$this->db_handle || $this->db_mode=='r') return FALSE; - return $this->db_handle->lastInsertID($sequence); + // find sequence name + if ($table && $this->db_provider == 'pgsql') + $table = get_sequence_name($table); + + $id = $this->db_handle->lastInsertID($table); + + return $this->db_handle->isError($id) ? null : $id; } @@ -360,7 +380,7 @@ class rcube_mdb2 */ function _fetch_row($result, $mode) { - if ($result === FALSE || PEAR::isError($result)) + if ($result === FALSE || PEAR::isError($result) || !$this->is_connected()) return FALSE; return $result->fetchRow($mode); @@ -455,6 +475,26 @@ class rcube_mdb2 } + /** + * Return list of elements for use with SQL's IN clause + * + * @param string Input array + * @return string Elements list string + * @access public + */ + function array2list($arr, $type=null) + { + if (!is_array($arr)) + return $this->quote($arr, $type); + + $res = array(); + foreach ($arr as $item) + $res[] = $this->quote($item, $type); + + return implode(',', $res); + } + + /** * Return SQL statement to convert a field value into a unix timestamp * @@ -471,7 +511,7 @@ class rcube_mdb2 break; case 'mssql': - return "datediff(s, '1970-01-01 00:00:00', $field)"; + return "DATEDIFF(second, '19700101', $field) + DATEDIFF(second, GETDATE(), GETUTCDATE())"; default: return "UNIX_TIMESTAMP($field)"; @@ -522,6 +562,54 @@ class rcube_mdb2 } + /** + * Encodes non-UTF-8 characters in string/array/object (recursive) + * + * @param mixed Data to fix + * @return mixed Properly UTF-8 encoded data + * @access public + */ + function encode($input) + { + if (is_object($input)) { + foreach (get_object_vars($input) as $idx => $value) + $input->$idx = $this->encode($value); + return $input; + } + else if (is_array($input)) { + foreach ($input as $idx => $value) + $input[$idx] = $this->encode($value); + return $input; + } + + return utf8_encode($input); + } + + + /** + * Decodes encoded UTF-8 string/object/array (recursive) + * + * @param mixed Input data + * @return mixed Decoded data + * @access public + */ + function decode($input) + { + if (is_object($input)) { + foreach (get_object_vars($input) as $idx => $value) + $input->$idx = $this->decode($value); + return $input; + } + else if (is_array($input)) { + foreach ($input as $idx => $value) + $input[$idx] = $this->decode($value); + return $input; + } + + return utf8_decode($input); + } + + /** * Adds a query result and returns a handle ID * @@ -585,7 +673,9 @@ class rcube_mdb2 $data = file_get_contents($file_name); if (strlen($data)) - sqlite_exec($dbh->connection, $data); + if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) + raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, 'message' => $error), TRUE, FALSE); } @@ -617,8 +707,6 @@ function mdb2_debug_handler(&$db, $scope, $message, $context = array()) { $debug_output = $scope . '('.$db->db_index.'): '; $debug_output .= $message . $db->getOption('log_line_break'); - write_log('sqllog', $debug_output); + write_log('sql', $debug_output); } } - - diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index ec3be4b..7c2457e 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -84,6 +84,9 @@ class rcube_message else { $this->body = $this->imap->get_body($uid); } + + // notify plugins and let them analyze this structured message object + $this->app->plugins->exec_hook('message_load', array('object' => $this)); } @@ -320,8 +323,16 @@ class rcube_message $p->ctype_primary = 'text'; $p->ctype_secondary = 'plain'; $p->body = rcube_label('encryptedmessage'); + $p->size = strlen($p->body); - $this->parts[] = $p; + // maybe some plugins are able to decode this encrypted message part + $data = $this->app->plugins->exec_hook('message_part_encrypted', array('object' => $this, 'struct' => $structure, 'part' => $p)); + if (is_array($data['parts'])) { + $this->parts = array_merge($this->parts, $data['parts']); + } + else if ($data['part']) { + $this->parts[] = $p; + } } // message contains multiple parts else if (is_array($structure->parts) && !empty($structure->parts)) { @@ -338,7 +349,7 @@ class rcube_message // part text/[plain|html] OR message/delivery-status else if (($primary_type == 'text' && ($secondary_type == 'plain' || $secondary_type == 'html') && $mail_part->disposition != 'attachment') || ($primary_type == 'message' && ($secondary_type == 'delivery-status' || $secondary_type == 'disposition-notification'))) { - + // add text part if we're not in alternative mode or if it matches the prefs if (!$this->parse_alternative || ($secondary_type == 'html' && $this->opt['prefer_html']) || @@ -363,7 +374,7 @@ class rcube_message else if ($primary_type == 'protocol') continue; - // part is Microsoft outlook TNEF (winmail.dat) + // part is Microsoft Outlook TNEF (winmail.dat) else if ($primary_type == 'application' && $secondary_type == 'ms-tnef') { foreach ((array)$this->imap->tnef_decode($mail_part, $structure->headers['uid']) as $tnef_part) { $this->mime_parts[$tnef_part->mime_id] = $tnef_part; @@ -371,26 +382,27 @@ class rcube_message } } - // part is file/attachment - else if ($mail_part->disposition == 'attachment' || $mail_part->disposition == 'inline' || + // part is a file/attachment + else if (preg_match('/^(inline|attach)/', $mail_part->disposition) || $mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)) { + // skip apple resource forks if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile') continue; - // part belongs to a related message - if ($message_ctype_secondary == 'related') { + // part belongs to a related message and is linked + if ($message_ctype_secondary == 'related' + && preg_match('!^image/!', $mail_part->mimetype) + && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) { if ($mail_part->headers['content-id']) $mail_part->content_id = preg_replace(array('/^$/'), '', $mail_part->headers['content-id']); if ($mail_part->headers['content-location']) $mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location']; - - if ($mail_part->content_id || $mail_part->content_location) { - $this->inline_parts[] = $mail_part; - } + + $this->inline_parts[] = $mail_part; } - // is regular attachment - else { + // is a regular attachment + else if (preg_match('!^[a-z]+/[a-z0-9-.]+$!i', $mail_part->mimetype)) { if (!$mail_part->filename) $mail_part->filename = 'Part '.$mail_part->mime_id; $this->attachments[] = $mail_part; diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php new file mode 100644 index 0000000..5e37764 --- /dev/null +++ b/program/include/rcube_plugin.php @@ -0,0 +1,245 @@ + | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * Plugin interface class + * + * @package Core + */ +abstract class rcube_plugin +{ + public $ID; + public $api; + public $task; + protected $home; + protected $urlbase; + + /** + * Default constructor. + */ + public function __construct($api) + { + $this->ID = get_class($this); + $this->api = $api; + $this->home = $api->dir . DIRECTORY_SEPARATOR . $this->ID; + $this->urlbase = $api->url . $this->ID . '/'; + } + + /** + * Initialization method, needs to be implemented by the plugin itself + */ + abstract function init(); + + /** + * Load local config file from plugins directory. + * The loaded values are patched over the global configuration. + * + * @param string Config file name relative to the plugin's folder + * @return boolean True on success, false on failure + */ + public function load_config($fname = 'config.inc.php') + { + $fpath = $this->home.'/'.$fname; + $rcmail = rcmail::get_instance(); + if (!$rcmail->config->load_from_file($fpath)) { + raise_error(array('code' => 527, 'type' => 'php', 'message' => "Failed to load config from $fpath"), true, false); + return false; + } + + return true; + } + + /** + * Register a callback function for a specific (server-side) hook + * + * @param string Hook name + * @param mixed Callback function as string or array with object reference and method name + */ + public function add_hook($hook, $callback) + { + $this->api->register_hook($hook, $callback); + } + + /** + * Load localized texts from the plugins dir + * + * @param string Directory to search in + * @param mixed Make texts also available on the client (array with list or true for all) + */ + public function add_texts($dir, $add2client = false) + { + $domain = $this->ID; + + $lang = $_SESSION['language']; + $locdir = slashify(realpath(slashify($this->home) . $dir)); + $texts = array(); + + foreach (array('en_US', $lang) as $lng) { + @include($locdir . $lng . '.inc'); + $texts = (array)$labels + (array)$messages + (array)$texts; + } + + // prepend domain to text keys and add to the application texts repository + if (!empty($texts)) { + $add = array(); + foreach ($texts as $key => $value) + $add[$domain.'.'.$key] = $value; + + $rcmail = rcmail::get_instance(); + $rcmail->load_language($lang, $add); + + // add labels to client + if ($add2client) { + $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add); + $rcmail->output->add_label($js_labels); + } + } + } + + /** + * Wrapper for rcmail::gettext() adding the plugin ID as domain + * + * @return string Localized text + * @see rcmail::gettext() + */ + public function gettext($p) + { + return rcmail::get_instance()->gettext($p, $this->ID); + } + + /** + * Register this plugin to be responsible for a specific task + * + * @param string Task name (only characters [a-z0-9_.-] are allowed) + */ + public function register_task($task) + { + if ($task != asciiwords($task)) { + raise_error(array('code' => 526, 'type' => 'php', 'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false); + } + else if (in_array(rcmail::$main_tasks, $task)) { + raise_error(array('code' => 526, 'type' => 'php', 'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false); + } + else { + rcmail::$main_tasks[] = $task; + } + } + + /** + * Register a handler for a specific client-request action + * + * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction + * + * @param string Action name (should be unique) + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_action($action, $callback) + { + $this->api->register_action($action, $this->ID, $callback); + } + + /** + * Register a handler function for a template object + * + * When parsing a template for display, tags like + * will be replaced by the return value if the registered callback function. + * + * @param string Object name (should be unique and start with 'plugin.') + * @param mixed Callback function as string or array with object reference and method name + */ + public function register_handler($name, $callback) + { + $this->api->register_handler($name, $this->ID, $callback); + } + + /** + * Make this javascipt file available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_script($fn) + { + $this->api->include_script($this->resource_url($fn)); + } + + /** + * Make this stylesheet available on the client + * + * @param string File path; absolute or relative to the plugin directory + */ + public function include_stylesheet($fn) + { + $this->api->include_stylesheet($this->resource_url($fn)); + } + + /** + * Append a button to a certain container + * + * @param array Hash array with named parameters (as used in skin templates) + * @param string Container name where the buttons should be added to + * @see rcube_remplate::button() + */ + public function add_button($p, $container) + { + if ($this->api->output->type == 'html') { + // fix relative paths + foreach (array('imagepas', 'imageact', 'imagesel') as $key) + if ($p[$key]) + $p[$key] = $this->api->url . $this->resource_url($p[$key]); + + $this->api->add_content($this->api->output->button($p), $container); + } + } + + /** + * Generate an absolute URL to the given resource within the current + * plugin directory + * + * @param string The file name + * @return string Absolute URL to the given resource + */ + public function url($fn) + { + return $this->api->url . $this->resource_url($fn); + } + + /** + * Make the given file name link into the plugin directory + */ + private function resource_url($fn) + { + if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) + return $this->ID . '/' . $fn; + else + return $fn; + } + + /** + * Callback function for array_map + */ + private function label_map_callback($key) + { + return $this->ID.'.'.$key; + } + + +} + diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php new file mode 100644 index 0000000..75f1cc4 --- /dev/null +++ b/program/include/rcube_plugin_api.php @@ -0,0 +1,329 @@ + | + +-----------------------------------------------------------------------+ + + $Id: $ + +*/ + +/** + * The plugin loader and global API + * + * @package Core + */ +class rcube_plugin_api +{ + static private $instance; + + public $dir; + public $url = 'plugins/'; + public $output; + + public $handlers = array(); + private $plugins = array(); + private $actions = array(); + private $actionmap = array(); + private $objectsmap = array(); + private $template_contents = array(); + + private $required_plugins = array('filesystem_attachments'); + private $active_hook = false; + + /** + * This implements the 'singleton' design pattern + * + * @return object rcube_plugin_api The one and only instance if this class + */ + static function get_instance() + { + if (!self::$instance) { + self::$instance = new rcube_plugin_api(); + } + + return self::$instance; + } + + + /** + * Private constructor + */ + private function __construct() + { + $this->dir = INSTALL_PATH . $this->url; + } + + + /** + * Load and init all enabled plugins + * + * This has to be done after rcmail::load_gui() or rcmail::init_json() + * was called because plugins need to have access to rcmail->output + */ + public function init() + { + $rcmail = rcmail::get_instance(); + $this->output = $rcmail->output; + + $plugins_dir = dir($this->dir); + $plugins_enabled = (array)$rcmail->config->get('plugins', array()); + + foreach ($plugins_enabled as $plugin_name) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + + if (file_exists($fn)) { + include($fn); + + // instantiate class if exists + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance and task specification + if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task))) { + $plugin->init(); + $this->plugins[] = $plugin; + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false); + } + } + else { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false); + } + } + + // check existance of all required core plugins + foreach ($this->required_plugins as $plugin_name) { + $loaded = false; + foreach ($this->plugins as $plugin) { + if ($plugin instanceof $plugin_name) { + $loaded = true; + break; + } + } + + // load required core plugin if no derivate was found + if (!$loaded) { + $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + if (file_exists($fn)) { + include_once($fn); + + if (class_exists($plugin_name, false)) { + $plugin = new $plugin_name($this); + // check inheritance + if (is_subclass_of($plugin, 'rcube_plugin')) { + if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) { + $plugin->init(); + $this->plugins[] = $plugin; + } + $loaded = true; + } + } + } + } + + // trigger fatal error if still not loaded + if (!$loaded) { + raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true); + } + } + + // register an internal hook + $this->register_hook('template_container', array($this, 'template_container_hook')); + + // maybe also register a shudown function which triggers shutdown functions of all plugin objects + + + // call imap_init right now + // (should actually be done in rcmail::imap_init() but plugins are not initialized then) + if ($rcmail->imap) { + $hook = $this->exec_hook('imap_init', array('fetch_headers' => $rcmail->imap->fetch_add_headers)); + if ($hook['fetch_headers']) + $rcmail->imap->fetch_add_headers = $hook['fetch_headers']; + } + } + + + /** + * Allows a plugin object to register a callback for a certain hook + * + * @param string Hook name + * @param mixed String with global function name or array($obj, 'methodname') + */ + public function register_hook($hook, $callback) + { + if (is_callable($callback)) + $this->handlers[$hook][] = $callback; + else + raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false); + } + + + /** + * Triggers a plugin hook. + * This is called from the application and executes all registered handlers + * + * @param string Hook name + * @param array Named arguments (key->value pairs) + * @return array The (probably) altered hook arguments + */ + public function exec_hook($hook, $args = array()) + { + $args += array('abort' => false); + $this->active_hook = $hook; + + foreach ((array)$this->handlers[$hook] as $callback) { + $ret = call_user_func($callback, $args); + if ($ret && is_array($ret)) + $args = $ret + $args; + + if ($args['abort']) + break; + } + + $this->active_hook = false; + return $args; + } + + + /** + * Let a plugin register a handler for a specific request + * + * @param string Action name (_task=mail&_action=plugin.foo) + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_action($action, $owner, $callback) + { + // check action name + if (strpos($action, 'plugin.') !== 0) + $action = 'plugin.'.$action; + + // can register action only if it's not taken or registered by myself + if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) { + $this->actions[$action] = $callback; + $this->actionmap[$action] = $owner; + } + else { + raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false); + } + } + + + /** + * This method handles requests like _task=mail&_action=plugin.foo + * It executes the callback function that was registered with the given action. + * + * @param string Action name + */ + public function exec_action($action) + { + if (isset($this->actions[$action])) { + call_user_func($this->actions[$action]); + } + else { + raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true); + } + } + + + /** + * Register a handler function for template objects + * + * @param string Object name + * @param string Plugin name that registers this action + * @param mixed Callback: string with global function name or array($obj, 'methodname') + */ + public function register_handler($name, $owner, $callback) + { + // check name + if (strpos($name, 'plugin.') !== 0) + $name = 'plugin.'.$name; + + // can register handler only if it's not taken or registered by myself + if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) { + $this->output->add_handler($name, $callback); + $this->objectsmap[$name] = $owner; + } + else { + raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false); + } + } + + + /** + * Check if a plugin hook is currently processing. + * Mainly used to prevent loops and recursion. + * + * @param string Hook to check (optional) + * @return boolean True if any/the given hook is currently processed, otherwise false + */ + public function is_processing($hook = null) + { + return $this->active_hook && (!$hook || $this->active_hook == $hook); + } + + /** + * Include a plugin script file in the current HTML page + */ + public function include_script($fn) + { + if ($this->output->type == 'html') { + $src = $this->resource_url($fn); + $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src))); + } + } + + /** + * Include a plugin stylesheet in the current HTML page + */ + public function include_stylesheet($fn) + { + if ($this->output->type == 'html') { + $src = $this->resource_url($fn); + $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src))); + } + } + + /** + * Save the given HTML content to be added to a template container + */ + public function add_content($html, $container) + { + $this->template_contents[$container] .= $html . "\n"; + } + + /** + * Callback for template_container hooks + */ + private function template_container_hook($attrib) + { + $container = $attrib['name']; + return array('content' => $attrib['content'] . $this->template_contents[$container]); + } + + /** + * Make the given file name link into the plugins directory + */ + private function resource_url($fn) + { + if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) + return $this->url . $fn; + else + return $fn; + } + +} + diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index ccf30d2..e99d324 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -15,7 +15,7 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: rcube_shared.inc 2483 2009-05-15 10:22:29Z thomasb $ + $Id: rcube_shared.inc 2789 2009-07-23 12:14:17Z alec $ */ @@ -170,7 +170,7 @@ function json_serialize($var) foreach ($var as $key => $value) { // enclose key with quotes if it is not variable-name conform - if (!ereg("^[_a-zA-Z]{1}[_a-zA-Z0-9]*$", $key) || is_js_reserved_word($key)) + if (!preg_match('/^[_a-zA-Z]{1}[_a-zA-Z0-9]*$/', $key) || is_js_reserved_word($key)) $key = "'$key'"; $pairs[] = sprintf("%s%s", $is_assoc ? "$key:" : '', json_serialize($value)); @@ -185,7 +185,6 @@ function json_serialize($var) return $var ? '1' : '0'; else return "'".JQ($var)."'"; - } @@ -209,9 +208,9 @@ function array2js($arr, $type='') */ function in_array_nocase($needle, $haystack) { - $needle = rc_strtolower($needle); + $needle = mb_strtolower($needle); foreach ($haystack as $value) - if ($needle===rc_strtolower($value)) + if ($needle===mb_strtolower($value)) return true; return false; @@ -227,7 +226,7 @@ function in_array_nocase($needle, $haystack) function get_boolean($str) { $str = strtolower($str); - if(in_array($str, array('false', '0', 'no', 'nein', ''), TRUE)) + if (in_array($str, array('false', '0', 'no', 'nein', ''), TRUE)) return FALSE; else return TRUE; @@ -238,12 +237,12 @@ function get_boolean($str) * Parse a human readable string for a number of bytes * * @param string Input string - * @return int Number of bytes + * @return float Number of bytes */ function parse_bytes($str) { if (is_numeric($str)) - return intval($str); + return floatval($str); if (preg_match('/([0-9]+)([a-z])/i', $str, $regs)) { @@ -262,7 +261,7 @@ function parse_bytes($str) } } - return intval($bytes); + return floatval($bytes); } /** @@ -338,81 +337,11 @@ function make_absolute_url($path, $base_url) return $abs_path; } - -/** - * Wrapper function for strlen - */ -function rc_strlen($str) -{ - if (function_exists('mb_strlen')) - return mb_strlen($str); - else - return strlen($str); -} - -/** - * Wrapper function for strtolower - */ -function rc_strtolower($str) -{ - if (function_exists('mb_strtolower')) - return mb_strtolower($str); - else - return strtolower($str); -} - -/** - * Wrapper function for strtoupper - */ -function rc_strtoupper($str) -{ - if (function_exists('mb_strtoupper')) - return mb_strtoupper($str); - else - return strtoupper($str); -} - -/** - * Wrapper function for substr - */ -function rc_substr($str, $start, $len=null) -{ - if (function_exists('mb_substr')) - return mb_substr($str, $start, $len); - else - return substr($str, $start, $len); -} - -/** - * Wrapper function for strpos - */ -function rc_strpos($haystack, $needle, $offset=0) -{ - if (function_exists('mb_strpos')) - return mb_strpos($haystack, $needle, $offset); - else - return strpos($haystack, $needle, $offset); -} - -/** - * Wrapper function for strrpos - */ -function rc_strrpos($haystack, $needle, $offset=0) -{ - if (function_exists('mb_strrpos')) - return mb_strrpos($haystack, $needle, $offset); - else - return strrpos($haystack, $needle, $offset); -} - /** * Wrapper function for wordwrap */ function rc_wordwrap($string, $width=75, $break="\n", $cut=false) { - if (!function_exists('mb_substr') || !function_exists('mb_strlen')) - return wordwrap($string, $width, $break, $cut); - $para = explode($break, $string); $string = ''; while (count($para)) { @@ -425,7 +354,7 @@ function rc_wordwrap($string, $width=75, $break="\n", $cut=false) if ($newlen <= $width) { $string .= ($len ? ' ' : '').$line; - $len += ($len ? 1 : 0) + $l; + $len += (1 + $l); } else { if ($l > $width) { if ($cut) { @@ -478,30 +407,6 @@ function rc_request_header($name) } -/** - * Replace the middle part of a string with ... - * if it is longer than the allowed length - * - * @param string Input string - * @param int Max. length - * @param string Replace removed chars with this - * @return string Abbreviated string - */ -function abbreviate_string($str, $maxlength, $place_holder='...') -{ - $length = rc_strlen($str); - $first_part_length = floor($maxlength/2) - rc_strlen($place_holder); - - if ($length > $maxlength) - { - $second_starting_location = $length - $maxlength + $first_part_length + 1; - $str = rc_substr($str, 0, $first_part_length) . $place_holder . rc_substr($str, $second_starting_location, $length); - } - - return $str; -} - - /** * Make sure the string ends with a slash */ @@ -579,6 +484,29 @@ function get_offset_time($offset_str, $factor=1) } +/** + * Replace the middle part of a string with ... + * if it is longer than the allowed length + * + * @param string Input string + * @param int Max. length + * @param string Replace removed chars with this + * @return string Abbreviated string + */ +function abbreviate_string($str, $maxlength, $place_holder='...') +{ + $length = mb_strlen($str); + $first_part_length = floor($maxlength/2) - mb_strlen($place_holder); + + if ($length > $maxlength) + { + $second_starting_location = $length - $maxlength + $first_part_length + 1; + $str = mb_substr($str, 0, $first_part_length) . $place_holder . mb_substr($str, $second_starting_location, $length); + } + + return $str; +} + /** * A method to guess the mime_type of an attachment. * @@ -683,4 +611,42 @@ function rcube_explode_quoted_string($delimiter, $string) return $result; } + +/** + * mbstring replacement functions + */ + +if (!extension_loaded('mbstring')) +{ + function mb_strlen($str) + { + return strlen($str); + } + + function mb_strtolower($str) + { + return strtolower($str); + } + + function mb_strtoupper($str) + { + return strtoupper($str); + } + + function mb_substr($str, $start, $len=null) + { + return substr($str, $start, $len); + } + + function mb_strpos($haystack, $needle, $offset=0) + { + return strpos($haystack, $needle, $offset); + } + + function mb_strrpos($haystack, $needle, $offset=0) + { + return strrpos($haystack, $needle, $offset); + } +} + ?> diff --git a/program/include/rcube_smtp.inc b/program/include/rcube_smtp.inc deleted file mode 100644 index f4995d8..0000000 --- a/program/include/rcube_smtp.inc +++ /dev/null @@ -1,349 +0,0 @@ - | - +-----------------------------------------------------------------------+ - - $Id: rcube_smtp.inc 2291 2009-02-13 10:44:49Z alec $ - -*/ - -/** - * SMTP delivery functions - * - * @package Mail - */ - -// include required PEAR classes -require_once('Net/SMTP.php'); - - -// define headers delimiter -define('SMTP_MIME_CRLF', "\r\n"); - -$SMTP_CONN = null; - -/** - * Function for sending mail using SMTP. - * - * @param string Sender e-Mail address - * - * @param mixed Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of recipients, - * each RFC822 valid. This may contain recipients not - * specified in the headers, for Bcc:, resending - * messages, etc. - * - * @param mixed The message headers to send with the mail - * Either as an associative array or a finally - * formatted string - * - * @param string The full text of the message body, including any Mime parts, etc. - * - * @return bool Returns TRUE on success, or FALSE on error - */ -function smtp_mail($from, $recipients, &$headers, &$body, &$response) - { - global $SMTP_CONN, $CONFIG, $RCMAIL; - $smtp_timeout = null; - $smtp_host = $CONFIG['smtp_server']; - $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25; - $smtp_host_url = parse_url($CONFIG['smtp_server']); - - // overwrite port - if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) - { - $smtp_host = $smtp_host_url['host']; - $smtp_port = $smtp_host_url['port']; - } - - // re-write smtp host - if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) - $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']); - - - // create Net_SMTP object and connect to server - if (!is_object($SMTP_CONN)) - { - if (!empty($CONFIG['smtp_helo_host'])) - $helo_host = $CONFIG['smtp_helo_host']; - else if (!empty($_SERVER['SERVER_NAME'])) - $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); - else - $helo_host = 'localhost'; - - $SMTP_CONN = new Net_SMTP($smtp_host, $smtp_port, $helo_host); - - // try to connect to server and exit on failure - $result = $SMTP_CONN->connect($smtp_timeout); - if (PEAR::isError($result)) - { - $SMTP_CONN = null; - $response[] = "Connection failed: ".$result->getMessage(); - return FALSE; - } - - // attempt to authenticate to the SMTP server - if ($CONFIG['smtp_user'] && $CONFIG['smtp_pass']) - { - if (strstr($CONFIG['smtp_user'], '%u')) - $smtp_user = str_replace('%u', $_SESSION['username'], $CONFIG['smtp_user']); - else - $smtp_user = $CONFIG['smtp_user']; - - if (strstr($CONFIG['smtp_pass'], '%p')) - $smtp_pass = str_replace('%p', $RCMAIL->decrypt_passwd($_SESSION['password']), $CONFIG['smtp_pass']); - else - $smtp_pass = $CONFIG['smtp_pass']; - - $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type']; - $result = $SMTP_CONN->auth($smtp_user, $smtp_pass, $smtp_auth_type); - - if (PEAR::isError($result)) - { - smtp_reset(); - $response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')'; - return FALSE; - } - } - } - - - // prepare message headers as string - if (is_array($headers)) - { - $headerElements = smtp_prepare_headers($headers); - if (!$headerElements) - { - smtp_reset(); - return FALSE; - } - - list($from, $text_headers) = $headerElements; - } - else if (is_string($headers)) - $text_headers = $headers; - else - { - smtp_reset(); - $response[] .= "Invalid message headers"; - return FALSE; - } - - // exit if no from address is given - if (!isset($from)) - { - smtp_reset(); - $response[] .= "No From address has been provided"; - return FALSE; - } - - - // set From: address - if (PEAR::isError($SMTP_CONN->mailFrom($from))) - { - smtp_reset(); - $response[] .= "Failed to set sender '$from'"; - return FALSE; - } - - - // prepare list of recipients - $recipients = smtp_parse_rfc822($recipients); - if (PEAR::isError($recipients)) - { - smtp_reset(); - return FALSE; - } - - - // set mail recipients - foreach ($recipients as $recipient) - { - if (PEAR::isError($SMTP_CONN->rcptTo($recipient))) - { - smtp_reset(); - $response[] .= "Failed to add recipient '$recipient'"; - return FALSE; - } - } - - - // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data - // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. - // We are still forced to make another copy here for a couple ticks so we don't really - // get to save a copy in the method call. - $data = $text_headers . "\r\n" . $body; - - // unset old vars to save data and so we can pass into SMTP_CONN->data by reference. - unset($text_headers, $body); - - // Send the message's headers and the body as SMTP data. - if (PEAR::isError($SMTP_CONN->data($data))) - { - smtp_reset(); - $response[] .= "Failed to send data"; - return FALSE; - } - - $response[] = join(': ', $SMTP_CONN->getResponse()); - return TRUE; - } - - - -/** - * Reset the global SMTP connection - * @access public - */ -function smtp_reset() - { - global $SMTP_CONN; - - if (is_object($SMTP_CONN)) - { - $SMTP_CONN->rset(); - smtp_disconnect(); - } - } - - - -/** - * Disconnect the global SMTP connection and destroy object - * @access public - */ -function smtp_disconnect() - { - global $SMTP_CONN; - - if (is_object($SMTP_CONN)) - { - $SMTP_CONN->disconnect(); - $SMTP_CONN = null; - } - } - - -/** - * Take an array of mail headers and return a string containing - * text usable in sending a message. - * - * @param array $headers The array of headers to prepare, in an associative - * array, where the array key is the header name (ie, - * 'Subject'), and the array value is the header - * value (ie, 'test'). The header produced from those - * values would be 'Subject: test'. - * - * @return mixed Returns false if it encounters a bad address, - * otherwise returns an array containing two - * elements: Any From: address found in the headers, - * and the plain text version of the headers. - * @access private - */ -function smtp_prepare_headers($headers) - { - $lines = array(); - $from = null; - - foreach ($headers as $key => $value) - { - if (strcasecmp($key, 'From') === 0) - { - $addresses = smtp_parse_rfc822($value); - - if (is_array($addresses)) - $from = $addresses[0]; - - // Reject envelope From: addresses with spaces. - if (strstr($from, ' ')) - return FALSE; - - - $lines[] = $key . ': ' . $value; - } - else if (strcasecmp($key, 'Received') === 0) - { - $received = array(); - if (is_array($value)) - { - foreach ($value as $line) - $received[] = $key . ': ' . $line; - } - else - { - $received[] = $key . ': ' . $value; - } - - // Put Received: headers at the top. Spam detectors often - // flag messages with Received: headers after the Subject: - // as spam. - $lines = array_merge($received, $lines); - } - - else - { - // If $value is an array (i.e., a list of addresses), convert - // it to a comma-delimited string of its elements (addresses). - if (is_array($value)) - $value = implode(', ', $value); - - $lines[] = $key . ': ' . $value; - } - } - - return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF); - } - - - -/** - * Take a set of recipients and parse them, returning an array of - * bare addresses (forward paths) that can be passed to sendmail - * or an smtp server with the rcpt to: command. - * - * @param mixed Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of recipients, - * each RFC822 valid. - * - * @return array An array of forward paths (bare addresses). - * @access private - */ -function smtp_parse_rfc822($recipients) - { - // if we're passed an array, assume addresses are valid and implode them before parsing. - if (is_array($recipients)) - $recipients = implode(', ', $recipients); - - $addresses = array(); - $recipients = rcube_explode_quoted_string(',', $recipients); - - reset($recipients); - while (list($k, $recipient) = each($recipients)) - { - $a = explode(" ", $recipient); - while (list($k2, $word) = each($a)) - { - if ((strpos($word, "@") > 0) && (strpos($word, "\"")===false)) - { - $word = ereg_replace('^<|>$', '', trim($word)); - if (in_array($word, $addresses)===false) - array_push($addresses, $word); - } - } - } - return $addresses; - } - -?> diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php new file mode 100644 index 0000000..9253468 --- /dev/null +++ b/program/include/rcube_smtp.php @@ -0,0 +1,394 @@ + | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +// define headers delimiter +define('SMTP_MIME_CRLF', "\r\n"); + +class rcube_smtp { + + private $conn = null; + private $response; + private $error; + + + /** + * Object constructor + * + * @param + */ + function __construct() + { + } + + + /** + * SMTP Connection and authentication + * + * @return bool Returns true on success, or false on error + */ + public function connect() + { + $RCMAIL = rcmail::get_instance(); + + // disconnect/destroy $this->conn + $this->disconnect(); + + // reset error/response var + $this->error = $this->response = null; + + // let plugins alter smtp connection config + $CONFIG = $RCMAIL->plugins->exec_hook('smtp_connect', array( + 'smtp_server' => $RCMAIL->config->get('smtp_server'), + 'smtp_port' => $RCMAIL->config->get('smtp_port', 25), + 'smtp_user' => $RCMAIL->config->get('smtp_user'), + 'smtp_pass' => $RCMAIL->config->get('smtp_pass'), + 'smtp_auth_type' => $RCMAIL->config->get('smtp_auth_type'), + 'smtp_helo_host' => $RCMAIL->config->get('smtp_helo_host'), + 'smtp_timeout' => $RCMAIL->config->get('smtp_timeout'), + )); + + $smtp_host = str_replace('%h', $_SESSION['imap_host'], $CONFIG['smtp_server']); + // when called from Installer it's possible to have empty $smtp_host here + if (!$smtp_host) $smtp_host = 'localhost'; + $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25; + $smtp_host_url = parse_url($smtp_host); + + // overwrite port + if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) + { + $smtp_host = $smtp_host_url['host']; + $smtp_port = $smtp_host_url['port']; + } + + // re-write smtp host + if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) + $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']); + + if (!empty($CONFIG['smtp_helo_host'])) + $helo_host = $CONFIG['smtp_helo_host']; + else if (!empty($_SERVER['SERVER_NAME'])) + $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']); + else + $helo_host = 'localhost'; + + $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host); + + if($RCMAIL->config->get('smtp_debug')) + $this->conn->setDebug(true, array($this, 'debug_handler')); + + // try to connect to server and exit on failure + $result = $this->conn->connect($smtp_timeout); + if (PEAR::isError($result)) + { + $this->response[] = "Connection failed: ".$result->getMessage(); + $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code)); + $this->conn = null; + return false; + } + + $smtp_user = str_replace('%u', $_SESSION['username'], $CONFIG['smtp_user']); + $smtp_pass = str_replace('%p', $RCMAIL->decrypt($_SESSION['password']), $CONFIG['smtp_pass']); + $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type']; + + // attempt to authenticate to the SMTP server + if ($smtp_user && $smtp_pass) + { + $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type); + if (PEAR::isError($result)) + { + $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code)); + $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')'; + $this->reset(); + $this->disconnect(); + return false; + } + } + + return true; + } + + + /** + * Function for sending mail + * + * @param string Sender e-Mail address + * + * @param mixed Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param mixed The message headers to send with the mail + * Either as an associative array or a finally + * formatted string + * + * @param string The full text of the message body, including any Mime parts, etc. + * + * @return bool Returns true on success, or false on error + */ + public function send_mail($from, $recipients, &$headers, &$body) + { + if (!is_object($this->conn)) + return false; + + // prepare message headers as string + if (is_array($headers)) + { + if (!($headerElements = $this->_prepare_headers($headers))) { + $this->reset(); + return false; + } + + list($from, $text_headers) = $headerElements; + } + else if (is_string($headers)) + $text_headers = $headers; + else + { + $this->reset(); + $this->response[] .= "Invalid message headers"; + return false; + } + + // exit if no from address is given + if (!isset($from)) + { + $this->reset(); + $this->response[] .= "No From address has been provided"; + return false; + } + + // set From: address + if (PEAR::isError($this->conn->mailFrom($from))) + { + $this->error = array('label' => 'smtpfromerror', 'vars' => array('from' => $from, 'code' => $this->conn->_code)); + $this->response[] .= "Failed to set sender '$from'"; + $this->reset(); + return false; + } + + // prepare list of recipients + $recipients = $this->_parse_rfc822($recipients); + if (PEAR::isError($recipients)) + { + $this->error = array('label' => 'smtprecipientserror'); + $this->reset(); + return false; + } + + // set mail recipients + foreach ($recipients as $recipient) + { + if (PEAR::isError($this->conn->rcptTo($recipient))) { + $this->error = array('label' => 'smtptoerror', 'vars' => array('to' => $recipient, 'code' => $this->conn->_code)); + $this->response[] .= "Failed to add recipient '$recipient'"; + $this->reset(); + return false; + } + } + + // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data + // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. + // We are still forced to make another copy here for a couple ticks so we don't really + // get to save a copy in the method call. + $data = $text_headers . "\r\n" . $body; + + // unset old vars to save data and so we can pass into SMTP_CONN->data by reference. + unset($text_headers, $body); + + // Send the message's headers and the body as SMTP data. + if (PEAR::isError($result = $this->conn->data($data))) + { + $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $result->getMessage())); + $this->response[] .= "Failed to send data"; + $this->reset(); + return false; + } + + $this->response[] = join(': ', $this->conn->getResponse()); + return true; + } + + + /** + * Reset the global SMTP connection + * @access public + */ + public function reset() + { + if (is_object($this->conn)) + $this->conn->rset(); + } + + + /** + * Disconnect the global SMTP connection + * @access public + */ + public function disconnect() + { + if (is_object($this->conn)) { + $this->conn->disconnect(); + $this->conn = null; + } + } + + + /** + * This is our own debug handler for the SMTP connection + * @access public + */ + public function debug_handler(&$smtp, $message) + { + write_log('smtp', preg_replace('/\r\n$/', '', $message)); + } + + + /** + * Get error message + * @access public + */ + public function get_error() + { + return $this->error; + } + + + /** + * Get server response messages array + * @access public + */ + public function get_response() + { + return $this->response; + } + + + /** + * Take an array of mail headers and return a string containing + * text usable in sending a message. + * + * @param array $headers The array of headers to prepare, in an associative + * array, where the array key is the header name (ie, + * 'Subject'), and the array value is the header + * value (ie, 'test'). The header produced from those + * values would be 'Subject: test'. + * + * @return mixed Returns false if it encounters a bad address, + * otherwise returns an array containing two + * elements: Any From: address found in the headers, + * and the plain text version of the headers. + * @access private + */ + private function _prepare_headers($headers) + { + $lines = array(); + $from = null; + + foreach ($headers as $key => $value) + { + if (strcasecmp($key, 'From') === 0) + { + $addresses = $this->_parse_rfc822($value); + + if (is_array($addresses)) + $from = $addresses[0]; + + // Reject envelope From: addresses with spaces. + if (strstr($from, ' ')) + return false; + + $lines[] = $key . ': ' . $value; + } + else if (strcasecmp($key, 'Received') === 0) + { + $received = array(); + if (is_array($value)) + { + foreach ($value as $line) + $received[] = $key . ': ' . $line; + } + else + { + $received[] = $key . ': ' . $value; + } + + // Put Received: headers at the top. Spam detectors often + // flag messages with Received: headers after the Subject: + // as spam. + $lines = array_merge($received, $lines); + } + else + { + // If $value is an array (i.e., a list of addresses), convert + // it to a comma-delimited string of its elements (addresses). + if (is_array($value)) + $value = implode(', ', $value); + + $lines[] = $key . ': ' . $value; + } + } + + return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF); + } + + /** + * Take a set of recipients and parse them, returning an array of + * bare addresses (forward paths) that can be passed to sendmail + * or an smtp server with the rcpt to: command. + * + * @param mixed Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. + * + * @return array An array of forward paths (bare addresses). + * @access private + */ + private function _parse_rfc822($recipients) + { + // if we're passed an array, assume addresses are valid and implode them before parsing. + if (is_array($recipients)) + $recipients = implode(', ', $recipients); + + $addresses = array(); + $recipients = rcube_explode_quoted_string(',', $recipients); + + reset($recipients); + while (list($k, $recipient) = each($recipients)) + { + $a = explode(" ", $recipient); + while (list($k2, $word) = each($a)) + { + if ((strpos($word, "@") > 0) && (strpos($word, "\"")===false)) + { + $word = preg_replace('/^<|>$/', '', trim($word)); + if (in_array($word, $addresses)===false) + array_push($addresses, $word); + } + } + } + return $addresses; + } + +} + +?> diff --git a/program/include/rcube_string_replacer.php b/program/include/rcube_string_replacer.php index fe082a5..2625fdf 100644 --- a/program/include/rcube_string_replacer.php +++ b/program/include/rcube_string_replacer.php @@ -28,10 +28,20 @@ class rcube_string_replacer { public static $pattern = '/##str_replacement\[([0-9]+)\]##/'; - + public $mailto_pattern; + public $link_pattern; private $values = array(); + function __construct() + { + $url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;'; + $url_chars_within = '\?\.~,!'; + + $this->link_pattern = "/([\w]+:\/\/|\Wwww\.)([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i"; + $this->mailto_pattern = "/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i"; + } + /** * Add a string to the internal list * @@ -64,16 +74,17 @@ class rcube_string_replacer $i = -1; $scheme = strtolower($matches[1]); - if ($scheme == 'http' || $scheme == 'https' || $scheme == 'ftp') { - $url = $matches[1] . '://' . $matches[2]; - $i = $this->add(html::a(array('href' => $url, 'target' => "_blank"), Q($url))); + if (preg_match('!^(http|ftp|file)s?://!', $scheme)) { + $url = $matches[1] . $matches[2]; + $i = $this->add(html::a(array('href' => $url, 'target' => '_blank'), Q($url))); } - else if ($matches[2] == 'www.') { - $url = $matches[2] . $matches[3]; - $i = $this->add($matches[1] . html::a(array('href' => 'http://' . $url, 'target' => "_blank"), Q($url))); + else if (preg_match('/^(\W)www\.$/', $matches[1], $m)) { + $url = 'www.' . $matches[2]; + $i = $this->add($m[1] . html::a(array('href' => 'http://' . $url, 'target' => '_blank'), Q($url))); } - return $i >= 0 ? $this->get_replacement($i) : ''; + // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes. + return $i >= 0 ? $this->get_replacement($i) : $matches[0]; } /** diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php index 1e732ca..3d894b5 100755 --- a/program/include/rcube_template.php +++ b/program/include/rcube_template.php @@ -34,6 +34,7 @@ class rcube_template extends rcube_html_page var $config; var $framed = false; var $pagetitle = ''; + var $message = null; var $env = array(); var $js_env = array(); var $js_commands = array(); @@ -58,6 +59,7 @@ class rcube_template extends rcube_html_page //$this->framed = $framed; $this->set_env('task', $task); + $this->set_env('request_token', $this->app->get_request_token()); // load the correct skin (in case user-defined) $this->set_skin($this->config['skin']); @@ -66,11 +68,12 @@ class rcube_template extends rcube_html_page $javascript = 'var '.JS_OBJECT_NAME.' = new rcube_webmail();'; // don't wait for page onload. Call init at the bottom of the page (delayed) - $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');"; + $javascript_foot = '$(document).ready(function(){ '.JS_OBJECT_NAME.'.init(); });'; $this->add_script($javascript, 'head_top'); $this->add_script($javascript_foot, 'foot'); $this->scripts_path = 'program/js/'; + $this->include_script('jquery-1.3.min.js'); $this->include_script('common.js'); $this->include_script('app.js'); @@ -199,7 +202,9 @@ class rcube_template extends rcube_html_page */ public function command() { - $this->js_commands[] = func_get_args(); + $cmd = func_get_args(); + if (strpos($cmd[0], 'plugin.') === false) + $this->js_commands[] = $cmd; } @@ -208,8 +213,11 @@ class rcube_template extends rcube_html_page */ public function add_label() { - $arg_list = func_get_args(); - foreach ($arg_list as $i => $name) { + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) + $args = $args[0]; + + foreach ($args as $name) { $this->command('add_label', $name, rcube_label($name)); } } @@ -221,14 +229,18 @@ class rcube_template extends rcube_html_page * @param string Message to display * @param string Message type [notice|confirm|error] * @param array Key-value pairs to be replaced in localized text + * @param boolean Override last set message * @uses self::command() */ - public function show_message($message, $type='notice', $vars=NULL) + public function show_message($message, $type='notice', $vars=null, $override=true) { - $this->command( - 'display_message', - rcube_label(array('name' => $message, 'vars' => $vars)), - $type); + if ($override || !$this->message) { + $this->message = $message; + $this->command( + 'display_message', + rcube_label(array('name' => $message, 'vars' => $vars)), + $type); + } } @@ -276,6 +288,11 @@ class rcube_template extends rcube_html_page public function send($templ = null, $exit = true) { if ($templ != 'iframe') { + // prevent from endless loops + if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) { + raise_error(array('code' => 505, 'type' => 'php', 'message' => 'Recursion alert: ignoring output->send()'), true, false); + return; + } $this->parse($templ, false); } else { @@ -283,6 +300,10 @@ class rcube_template extends rcube_html_page $this->write(); } + // set output asap + ob_flush(); + flush(); + if ($exit) { exit; } @@ -305,6 +326,10 @@ class rcube_template extends rcube_html_page $js = $this->framed ? "if(window.parent) {\n" : ''; $js .= $this->get_js_commands() . ($this->framed ? ' }' : ''); $this->add_script($js, 'head_top'); + + // make sure all
tags have a valid request token + $template = preg_replace_callback('/]+)>/Ui', array($this, 'alter_form_tag'), $template); + $this->footer = preg_replace_callback('/]+)>/Ui', array($this, 'alter_form_tag'), $this->footer); // call super method parent::write($template, $this->config['skin_path']); @@ -323,6 +348,20 @@ class rcube_template extends rcube_html_page private function parse($name = 'main', $exit = true) { $skin_path = $this->config['skin_path']; + $plugin = false; + + $temp = explode(".", $name, 2); + if (count($temp) > 1) { + $plugin = $temp[0]; + $name = $temp[1]; + $skin_dir = $plugin . '/skins/' . $this->config['skin']; + $skin_path = $this->app->plugins->dir . $skin_dir; + if (!is_dir($skin_path)) { // fallback to default skin + $skin_dir = $plugin . '/skins/default'; + $skin_path = $this->app->plugins->dir . $skin_dir; + } + } + $path = "$skin_path/templates/$name.html"; // read template file @@ -336,20 +375,30 @@ class rcube_template extends rcube_html_page ), true, true); return false; } + + // replace all path references to plugins/... with the configured plugins dir + // and /this/ to the current plugin skin directory + if ($plugin) { + $templ = preg_replace(array('/\bplugins\//', '/(["\']?)\/this\//'), array($this->app->plugins->url, '\\1'.$this->app->plugins->url.$skin_dir.'/'), $templ); + } // parse for specialtags $output = $this->parse_conditions($templ); $output = $this->parse_xml($output); + + // trigger generic hook where plugins can put additional content to the page + $hook = $this->app->plugins->exec_hook("render_page", array('template' => $name, 'content' => $output)); // add debug console if ($this->config['debug_level'] & 8) { - $this->add_footer('
+ $this->add_footer('' ); } - $output = $this->parse_with_globals($output); - $this->write(trim($output), $skin_path); + + $output = $this->parse_with_globals($hook['content']); + $this->write(trim($output)); if ($exit) { exit; } @@ -375,9 +424,9 @@ class rcube_template extends rcube_html_page $parent = $this->framed || preg_match('/^parent\./', $method); $out .= sprintf( "%s.%s(%s);\n", - ($parent ? 'parent.' : '') . JS_OBJECT_NAME, - preg_replace('/^parent\./', '', $method), - implode(',', $args) + ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME, + preg_replace('/^parent\./', '', $method), + implode(',', $args) ); } @@ -404,6 +453,7 @@ class rcube_template extends rcube_html_page */ private function parse_with_globals($input) { + $GLOBALS['__version'] = Q(RCMAIL_VERSION); $GLOBALS['__comm_path'] = Q($this->app->comm_path); return preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input); } @@ -469,7 +519,24 @@ class rcube_template extends rcube_html_page */ private function check_condition($condition) { - return eval("return (".$this->parse_expression($condition).");"); + return eval("return (".$this->parse_expression($condition).");"); + } + + + /** + * + */ + private function alter_form_tag($matches) + { + $out = $matches[0]; + $attrib = parse_attrib_string($matches[1]); + + if (strtolower($attrib['method']) == 'post') { + $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token())); + $out .= "\n" . $hidden->show(); + } + + return $out; } @@ -487,14 +554,16 @@ class rcube_template extends rcube_html_page '/config:([a-z0-9_]+)(:([a-z0-9_]+))?/i', '/env:([a-z0-9_]+)/i', '/request:([a-z0-9_]+)/i', - '/cookie:([a-z0-9_]+)/i' + '/cookie:([a-z0-9_]+)/i', + '/browser:([a-z0-9_]+)/i' ), array( "\$_SESSION['\\1']", "\$this->app->config->get('\\1',get_boolean('\\3'))", "\$this->env['\\1']", "get_input_value('\\1', RCUBE_INPUT_GPC)", - "\$_COOKIE['\\1']" + "\$_COOKIE['\\1']", + "\$this->browser->{'\\1'}" ), $expression); } @@ -511,37 +580,21 @@ class rcube_template extends rcube_html_page */ private function parse_xml($input) { - return preg_replace_callback('/]+)>/Ui', array($this, 'xml_command_callback'), $input); + return preg_replace_callback('/]+)>/Ui', array($this, 'xml_command'), $input); } /** - * This is a callback function for preg_replace_callback (see #1485286) - * It's only purpose is to reconfigure parameters for xml_command, so that the signature isn't disturbed - */ - private function xml_command_callback($matches) - { - $str_attrib = isset($matches[2]) ? $matches[2] : ''; - $add_attrib = isset($matches[3]) ? $matches[3] : array(); - - $command = $matches[1]; - //matches[0] is the entire matched portion of the string - - return $this->xml_command($command, $str_attrib, $add_attrib); - } - - - /** - * Convert a xml command tag into real content + * Callback function for parsing an xml command tag + * and turn it into real html content * - * @param string Tag command: object,button,label, etc. - * @param string Attribute string + * @param array Matches array of preg_replace_callback * @return string Tag/Object content */ - private function xml_command($command, $str_attrib, $add_attrib = array()) + private function xml_command($matches) { - $command = strtolower($command); - $attrib = parse_attrib_string($str_attrib) + $add_attrib; + $command = strtolower($matches[1]); + $attrib = parse_attrib_string($matches[2]); // empty output if required condition is not met if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) { @@ -572,67 +625,71 @@ class rcube_template extends rcube_html_page $incl = $this->include_php($path); } else { - $incl = file_get_contents($path); - } + $incl = file_get_contents($path); + } + $incl = $this->parse_conditions($incl); return $this->parse_xml($incl); } break; case 'plugin.include': - //rcube::tfk_debug(var_export($this->config['skin_path'], true)); - $path = realpath($this->config['skin_path'].$attrib['file']); - if (!$path) { - //rcube::tfk_debug("Does not exist:"); - //rcube::tfk_debug($this->config['skin_path']); - //rcube::tfk_debug($attrib['file']); - //rcube::tfk_debug($path); - } - $incl = file_get_contents($path); - if ($incl) { - return $this->parse_xml($incl); + $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib); + return $hook['content']; + break; + + // define a container block + case 'container': + if ($attrib['name'] && $attrib['id']) { + $this->command('gui_container', $attrib['name'], $attrib['id']); + // let plugins insert some content here + $hook = $this->app->plugins->exec_hook("template_container", $attrib); + return $hook['content']; } break; // return code for a specific application object case 'object': $object = strtolower($attrib['name']); + $content = ''; // we are calling a class/method if (($handler = $this->object_handlers[$object]) && is_array($handler)) { if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) || (is_string($handler[0]) && class_exists($handler[0]))) - return call_user_func($handler, $attrib); + $content = call_user_func($handler, $attrib); } + // execute object handler function else if (function_exists($handler)) { - // execute object handler function - return call_user_func($handler, $attrib); + $content = call_user_func($handler, $attrib); } - - if ($object=='productname') { + else if ($object == 'productname') { $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail'; - return Q($name); + $content = Q($name); } - if ($object=='version') { + else if ($object == 'version') { $ver = (string)RCMAIL_VERSION; if (is_file(INSTALL_PATH . '.svn/entries')) { if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs)) $ver .= ' [SVN r'.$regs[1].']'; } - return $ver; + $content = Q($ver); } - if ($object=='steptitle') { - return Q($this->get_pagetitle()); + else if ($object == 'steptitle') { + $content = Q($this->get_pagetitle()); } - if ($object=='pagetitle') { + else if ($object == 'pagetitle') { $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : ''; $title .= $this->get_pagetitle(); - return Q($title); + $content = Q($title); } - break; + + // exec plugin hooks for this template object + $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content)); + return $hook['content']; // return code for a specified eval expression case 'exp': - $value = $this->parse_expression($attrib['expression']); + $value = $this->parse_expression($attrib['expression']); return eval("return Q($value);"); // return variable @@ -660,6 +717,9 @@ class rcube_template extends rcube_html_page case 'cookie': $value = htmlspecialchars($_COOKIE[$name]); break; + case 'browser': + $value = $this->browser->{$name}; + break; } if (is_array($value)) { @@ -702,7 +762,7 @@ class rcube_template extends rcube_html_page static $s_button_count = 100; // these commands can be called directly via url - $a_static_commands = array('compose', 'list'); + $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities'); if (!($attrib['command'] || $attrib['name'])) { return ''; @@ -741,18 +801,18 @@ class rcube_template extends rcube_html_page } // get localized text for labels and titles if ($attrib['title']) { - $attrib['title'] = Q(rcube_label($attrib['title'])); + $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain'])); } if ($attrib['label']) { - $attrib['label'] = Q(rcube_label($attrib['label'])); + $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain'])); } if ($attrib['alt']) { - $attrib['alt'] = Q(rcube_label($attrib['alt'])); + $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain'])); } + // set title to alt attribute for IE browsers - if ($this->browser->ie && $attrib['title'] && !$attrib['alt']) { - $attrib['alt'] = $attrib['title']; - unset($attrib['title']); + if ($this->browser->ie && !$attrib['title'] && $attrib['alt']) { + $attrib['title'] = $attrib['alt']; } // add empty alt attribute for XHTML compatibility @@ -781,7 +841,7 @@ class rcube_template extends rcube_html_page $attrib['href'] = rcmail_url($attrib['command']); } else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) { - $attrib['href'] = $this->env['permaurl']; + $attrib['href'] = $this->env['permaurl']; } } @@ -797,35 +857,6 @@ class rcube_template extends rcube_html_page $attrib['prop'] ); } - if ($command && $attrib['imageover']) { - $attrib['onmouseover'] = sprintf( - "return %s.button_over('%s','%s')", - JS_OBJECT_NAME, - $command, - $attrib['id'] - ); - $attrib['onmouseout'] = sprintf( - "return %s.button_out('%s','%s')", - JS_OBJECT_NAME, - $command, - $attrib['id'] - ); - } - - if ($command && $attrib['imagesel']) { - $attrib['onmousedown'] = sprintf( - "return %s.button_sel('%s','%s')", - JS_OBJECT_NAME, - $command, - $attrib['id'] - ); - $attrib['onmouseup'] = sprintf( - "return %s.button_out('%s','%s')", - JS_OBJECT_NAME, - $command, - $attrib['id'] - ); - } $out = ''; @@ -834,19 +865,18 @@ class rcube_template extends rcube_html_page $attrib_str = html::attrib_string( $attrib, array( - 'style', 'class', 'id', 'width', - 'height', 'border', 'hspace', - 'vspace', 'align', 'alt', 'tabindex' + 'style', 'class', 'id', 'width', 'height', 'border', 'hspace', + 'vspace', 'align', 'alt', 'tabindex', 'title' ) ); $btn_content = sprintf('', $this->abs_url($attrib['image']), $attrib_str); if ($attrib['label']) { $btn_content .= ' '.$attrib['label']; } - $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title', 'target'); + $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target'); } else if ($attrib['type']=='link') { - $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command']; + $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']); $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target'); } else if ($attrib['type']=='input') { @@ -859,8 +889,7 @@ class rcube_template extends rcube_html_page $attrib_str = html::attrib_string( $attrib, array( - 'type', 'value', 'onclick', - 'id', 'class', 'style', 'tabindex' + 'type', 'value', 'onclick', 'id', 'class', 'style', 'tabindex' ) ); $out = sprintf('', $attrib_str); @@ -887,7 +916,7 @@ class rcube_template extends rcube_html_page */ public function form_tag($attrib, $content = null) { - if ($this->framed) { + if ($this->framed || !empty($_REQUEST['_framed'])) { $hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1')); $hidden = $hiddenfield->show(); } @@ -897,7 +926,36 @@ class rcube_template extends rcube_html_page return html::tag('form', $attrib + array('action' => "./", 'method' => "get"), - $hidden . $content); + $hidden . $content, + array('id','class','style','name','method','action','enctype','onsubmit')); + } + + + /** + * Build a form tag with a unique request token + * + * @param array Named tag parameters including 'action' and 'task' values which will be put into hidden fields + * @param string Form content + * @return string HTML code for the form + */ + public function request_form($attrib, $content = '') + { + $hidden = new html_hiddenfield(); + if ($attrib['task']) { + $hidden->add(array('name' => '_task', 'value' => $attrib['task'])); + } + if ($attrib['action']) { + $hidden->add(array('name' => '_action', 'value' => $attrib['action'])); + } + + unset($attrib['task'], $attrib['request']); + $attrib['action'] = './'; + + // we already have a
tag + if ($attrib['form']) + return $hidden->show() . $content; + else + return $this->form_tag($attrib, $hidden->show() . $content); } @@ -941,11 +999,17 @@ class rcube_template extends rcube_html_page $default_host = $this->config['default_host']; $_SESSION['temp'] = true; + + // save original url + $url = get_input_value('_url', RCUBE_INPUT_POST); + if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING'])) + $url = $_SERVER['QUERY_STRING']; $input_user = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30) + $attrib); $input_pass = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'size' => 30) + $attrib); $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login')); $input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_')); + $input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url)); $input_host = null; if (is_array($default_host)) { @@ -985,6 +1049,7 @@ class rcube_template extends rcube_html_page $out = $input_action->show(); $out .= $input_tzone->show(); + $out .= $input_url->show(); $out .= $table->show(); // surround html output with a form tag diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php index 17debeb..54a76c5 100644 --- a/program/include/rcube_user.php +++ b/program/include/rcube_user.php @@ -135,12 +135,12 @@ class rcube_user * Get default identity of this user * * @param int Identity ID. If empty, the default identity is returned - * @return array Hash array with all cols of the + * @return array Hash array with all cols of the identity record */ function get_identity($id = null) { - $sql_result = $this->list_identities($id ? sprintf('AND identity_id=%d', $id) : ''); - return $this->db->fetch_assoc($sql_result); + $result = $this->list_identities($id ? sprintf('AND identity_id=%d', $id) : ''); + return $result[0]; } @@ -160,7 +160,12 @@ class rcube_user ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC", $this->ID); - return $sql_result; + $result = array(); + while ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $result[] = $sql_arr; + } + + return $result; } @@ -176,23 +181,24 @@ class rcube_user if (!$this->ID) return false; - $write_sql = array(); + $query_cols = $query_params = array(); foreach ((array)$data as $col => $value) { - $write_sql[] = sprintf("%s=%s", - $this->db->quoteIdentifier($col), - $this->db->quote($value)); + $query_cols[] = $this->db->quoteIdentifier($col) . '=?'; + $query_params[] = $value; } - - $this->db->query( - "UPDATE ".get_table_name('identities')." - SET ".join(', ', $write_sql)." + $query_params[] = $iid; + $query_params[] = $this->ID; + + $sql = "UPDATE ".get_table_name('identities')." + SET ".join(', ', $query_cols)." WHERE identity_id=? AND user_id=? - AND del<>1", - $iid, - $this->ID); + AND del<>1"; + + call_user_func_array(array($this->db, 'query'), + array_merge(array($sql), $query_params)); return $this->db->affected_rows(); } @@ -213,16 +219,19 @@ class rcube_user foreach ((array)$data as $col => $value) { $insert_cols[] = $this->db->quoteIdentifier($col); - $insert_values[] = $this->db->quote($value); + $insert_values[] = $value; } + $insert_cols[] = 'user_id'; + $insert_values[] = $this->ID; - $this->db->query( - "INSERT INTO ".get_table_name('identities')." - (user_id, ".join(', ', $insert_cols).") - VALUES (?, ".join(', ', $insert_values).")", - $this->ID); + $sql = "INSERT INTO ".get_table_name('identities')." + (".join(', ', $insert_cols).") + VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")"; - return $this->db->insert_id(get_sequence_name('identities')); + call_user_func_array(array($this->db, 'query'), + array_merge(array($sql), $insert_values)); + + return $this->db->insert_id('identities'); } @@ -346,49 +355,93 @@ class rcube_user */ static function create($user, $host) { + $user_name = ''; $user_email = ''; $rcmail = rcmail::get_instance(); - $dbh = $rcmail->get_dbh(); // try to resolve user in virtuser table and file if (!strpos($user, '@')) { - if ($email_list = self::user2email($user, false)) - $user_email = $email_list[0]; + if ($email_list = self::user2email($user, false, true)) + $user_email = is_array($email_list[0]) ? $email_list[0][0] : $email_list[0]; } - + + $data = $rcmail->plugins->exec_hook('create_user', + array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email)); + + // plugin aborted this operation + if ($data['abort']) + return false; + + $user_name = $data['user_name']; + $user_email = $data['user_email']; + + $dbh = $rcmail->get_dbh(); + $dbh->query( "INSERT INTO ".get_table_name('users')." (created, last_login, username, mail_host, alias, language) VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)", strip_newlines($user), strip_newlines($host), - strip_newlines($user_email), + strip_newlines($data['alias'] ? $data['alias'] : $user_email), $_SESSION['language']); - if ($user_id = $dbh->insert_id(get_sequence_name('users'))) + if ($user_id = $dbh->insert_id('users')) { + // create rcube_user instance to make plugin hooks work + $user_instance = new rcube_user($user_id); + $rcmail->user = $user_instance; + $mail_domain = $rcmail->config->mail_domain($host); if ($user_email=='') $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain); - $user_name = $user != $user_email ? $user : ''; + if ($user_name == '') { + $user_name = $user != $user_email ? $user : ''; + } if (empty($email_list)) - $email_list[] = strip_newlines($user_email); + $email_list[] = strip_newlines($user_email); + // identities_level check + else if (count($email_list) > 1 && $rcmail->config->get('identities_level', 0) > 1) + $email_list = array($email_list[0]); - // also create new identity records + // create new identities records $standard = 1; - foreach ($email_list as $email) { - $dbh->query( - "INSERT INTO ".get_table_name('identities')." - (user_id, del, standard, name, email) - VALUES (?, 0, ?, ?, ?)", - $user_id, - $standard, - strip_newlines($user_name), - preg_replace('/^@/', $user . '@', $email)); - $standard = 0; + foreach ($email_list as $row) { + if (is_array($row)) { + $email = $row[0]; + $name = $row[1] ? $row[1] : $user_name; + } + else { + $email = $row; + $name = $user_name; + } + + $plugin = $rcmail->plugins->exec_hook('create_identity', array( + 'login' => true, + 'record' => array( + 'user_id' => $user_id, + 'name' => strip_newlines($name), + 'email' => $email, + 'standard' => $standard, + 'signature' => '', + ), + )); + + if (!$plugin['abort'] && $plugin['record']['email']) { + $dbh->query( + "INSERT INTO ".get_table_name('identities')." + (user_id, del, standard, name, email, signature) + VALUES (?, 0, ?, ?, ?, ?)", + $user_id, + $plugin['record']['standard'], + $plugin['record']['name'] != NULL ? $plugin['record']['name'] : '', + $plugin['record']['email'], + $plugin['record']['signature']); + } + $standard = 0; } } else @@ -401,7 +454,7 @@ class rcube_user 'message' => "Failed to create new user"), true, false); } - return $user_id ? new rcube_user($user_id) : false; + return $user_id ? $user_instance : false; } @@ -413,7 +466,7 @@ class rcube_user */ static function email2user($email) { - $r = self::findinvirtual('^' . quotemeta($email) . '[[:space:]]'); + $r = self::findinvirtual('/^' . preg_quote($email, '/') . '\s/'); for ($i=0; $iquery(preg_replace('/%u/', $dbh->escapeSimple($user), $virtuser_query)); while ($sql_arr = $dbh->fetch_array($sql_result)) if (strpos($sql_arr[0], '@')) { - $result[] = $sql_arr[0]; - if ($first) - return $result[0]; - } + $result[] = ($extended && count($sql_arr) > 1) ? $sql_arr : $sql_arr[0]; + if ($first) + return $result[0]; + } } // File lookup - $r = self::findinvirtual('[[:space:]]' . quotemeta($user) . '[[:space:]]*$'); + $r = self::findinvirtual('/\s' . preg_quote($user, '/') . '\s*$/'); for ($i=0; $i $attr) { if ((list($key, $value) = explode('=', $attr)) && $value) { if ($key == 'ENCODING') { - # add next line(s) to value string if QP line end detected - while ($value == 'QUOTED-PRINTABLE' && ereg('=$', $lines[$i])) + // add next line(s) to value string if QP line end detected + while ($value == 'QUOTED-PRINTABLE' && preg_match('/=$/', $lines[$i])) $line[2] .= "\n" . $lines[++$i]; $line[2] = self::decode_value($line[2], $value); @@ -280,17 +280,16 @@ class rcube_vcard $entry[strtolower($key)] = array_merge((array)$entry[strtolower($key)], (array)self::vcard_unquote($value, ',')); } else if ($attrid > 0) { - $entry[$key] = true; # true means attr without =value + $entry[$key] = true; // true means attr without =value } } - $entry[0] = self::vcard_unquote($line[2]); + $entry = array_merge($entry, (array)self::vcard_unquote($line[2])); $data[$field][] = count($entry) > 1 ? $entry : $entry[0]; } } unset($data['VERSION']); - return $data; } @@ -360,7 +359,7 @@ class rcube_vcard if (is_int($attrname)) $value[] = $attrvalues; elseif ($attrvalues === true) - $attr .= ";$attrname"; # true means just tag, not tag=value, as in PHOTO;BASE64:... + $attr .= ";$attrname"; // true means just tag, not tag=value, as in PHOTO;BASE64:... else { foreach((array)$attrvalues as $attrvalue) $attr .= ";$attrname=" . self::vcard_quote($attrvalue, ','); diff --git a/program/include/session.inc b/program/include/session.inc index b1ff1d2..35bd1b8 100644 --- a/program/include/session.inc +++ b/program/include/session.inc @@ -15,10 +15,11 @@ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ - $Id: session.inc 2237 2009-01-17 01:55:39Z till $ + $Id: session.inc 2573 2009-05-29 19:10:24Z alec $ */ +$GLOBALS['rcube_session_unsets'] = array(); function rcube_sess_open($save_path, $session_name) { @@ -39,10 +40,6 @@ function rcube_sess_read($key) $DB = rcmail::get_instance()->get_dbh(); - if ($DB->is_error()) { - return false; - } - $sql_result = $DB->query( "SELECT vars, ip, " . $DB->unixtimestamp('changed') . " AS changed FROM " . get_table_name('session') . " @@ -66,23 +63,23 @@ function rcube_sess_write($key, $vars) { $DB = rcmail::get_instance()->get_dbh(); - if ($DB->is_error()) { - return false; - } + $now = $DB->fromunixtime(time()); $sql_result = $DB->query( - "SELECT 1 FROM " . get_table_name('session') . " - WHERE sess_id=?", - $key); + "SELECT vars FROM " . get_table_name('session') . " + WHERE sess_id=?", $key); - $now = $DB->fromunixtime(time()); + if ($sql_arr = $DB->fetch_assoc($sql_result)) { + + $a_oldvars = rcube_sess_unserialize($sql_arr['vars']); + foreach ((array)$GLOBALS['rcube_session_unsets'] as $k) + unset($a_oldvars[$k]); - if ($DB->num_rows($sql_result)) { $DB->query( "UPDATE " . get_table_name('session') . " SET vars=?, changed= " . $now . " WHERE sess_id=?", - $vars, + rcube_sess_serialize(array_merge($a_oldvars, rcube_sess_unserialize($vars))), $key); } else { @@ -95,20 +92,129 @@ function rcube_sess_write($key, $vars) (string)$_SERVER['REMOTE_ADDR']); } + $GLOBALS['rcube_session_unsets'] = array(); + return true; +} + + +// unset session variable +function rcube_sess_unset($var=NULL) +{ + if (empty($var)) + return rcube_sess_destroy(session_id()); + + $GLOBALS['rcube_session_unsets'][] = $var; + unset($_SESSION[$var]); + return true; } +// serialize session data +function rcube_sess_serialize($vars) +{ + $data = ''; + if (is_array($vars)) + foreach ($vars as $var=>$value) + $data .= $var.'|'.serialize($value); + else + $data = 'b:0;'; + return $data; +} + + +// unserialize session data +// http://www.php.net/manual/en/function.session-decode.php#56106 +function rcube_sess_unserialize($str) +{ + $str = (string)$str; + $endptr = strlen($str); + $p = 0; + + $serialized = ''; + $items = 0; + $level = 0; + + while ($p < $endptr) { + $q = $p; + while ($str[$q] != '|') + if (++$q >= $endptr) break 2; + + if ($str[$p] == '!') { + $p++; + $has_value = false; + } else { + $has_value = true; + } + + $name = substr($str, $p, $q - $p); + $q++; + + $serialized .= 's:' . strlen($name) . ':"' . $name . '";'; + + if ($has_value) { + for (;;) { + $p = $q; + switch (strtolower($str[$q])) { + case 'n': /* null */ + case 'b': /* boolean */ + case 'i': /* integer */ + case 'd': /* decimal */ + do $q++; + while ( ($q < $endptr) && ($str[$q] != ';') ); + $q++; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) break 2; + break; + case 'r': /* reference */ + $q+= 2; + for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q]; + $q++; + $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */ + if ($level == 0) break 2; + break; + case 's': /* string */ + $q+=2; + for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q]; + $q+=2; + $q+= (int)$length + 2; + $serialized .= substr($str, $p, $q - $p); + if ($level == 0) break 2; + break; + case 'a': /* array */ + case 'o': /* object */ + do $q++; + while ( ($q < $endptr) && ($str[$q] != '{') ); + $q++; + $level++; + $serialized .= substr($str, $p, $q - $p); + break; + case '}': /* end of array|object */ + $q++; + $serialized .= substr($str, $p, $q - $p); + if (--$level == 0) break 2; + break; + default: + return false; + } + } + } else { + $serialized .= 'N;'; + $q+= 2; + } + $items++; + $p = $q; + } + + return unserialize( 'a:' . $items . ':{' . $serialized . '}' ); +} + + // handler for session_destroy() function rcube_sess_destroy($key) { - $rcmail = rcmail::get_instance(); - $DB = $rcmail->get_dbh(); + $DB = rcmail::get_instance()->get_dbh(); - if ($DB->is_error()) { - return false; - } - $DB->query("DELETE FROM " . get_table_name('session') . " WHERE sess_id=?", $key); return true; @@ -121,10 +227,6 @@ function rcube_sess_gc($maxlifetime) $rcmail = rcmail::get_instance(); $DB = $rcmail->get_dbh(); - if ($DB->is_error()) { - return false; - } - // just delete all expired sessions $DB->query("DELETE FROM " . get_table_name('session') . " WHERE changed < " . $DB->fromunixtime(time() - $maxlifetime)); diff --git a/program/js/app.js b/program/js/app.js index da37e12..5d1903f 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1,3336 +1,4257 @@ -var rcube_webmail_client; -function rcube_webmail(){ -this.env=new Object(); -this.labels=new Object(); -this.buttons=new Object(); -this.gui_objects=new Object(); -this.commands=new Object(); -this.onloads=new Array(); -rcube_webmail_client=this; -this.ref="rcube_webmail_client"; -var _1=this; -this.dblclick_time=500; -this.message_time=3000; -this.identifier_expr=new RegExp("[^0-9a-z-_]","gi"); -this.mimetypes=new Array("text/plain","text/html","text/xml","image/jpeg","image/gif","image/png","application/x-javascript","application/pdf","application/x-shockwave-flash"); -this.env.keep_alive=60; -this.env.request_timeout=180; -this.env.draft_autosave=0; -this.env.comm_path="./"; -this.env.bin_path="./bin/"; -this.env.blankpage="program/blank.gif"; -this.set_env=function(p,_3){ -if(p!=null&&typeof (p)=="object"&&!_3){ -for(var n in p){ -this.env[n]=p[n]; -} -}else{ -this.env[p]=_3; -} -}; -this.add_label=function(_5,_6){ -this.labels[_5]=_6; -}; -this.register_button=function(_7,id,_9,_a,_b,_c){ -if(!this.buttons[_7]){ -this.buttons[_7]=new Array(); -} -var _d={id:id,type:_9}; -if(_a){ -_d.act=_a; -} -if(_b){ -_d.sel=_b; -} -if(_c){ -_d.over=_c; -} -this.buttons[_7][this.buttons[_7].length]=_d; -}; -this.gui_object=function(_e,id){ -this.gui_objects[_e]=id; -}; -this.add_onload=function(f){ -this.onloads[this.onloads.length]=f; -}; -this.init=function(){ -var p=this; -this.task=this.env.task; -if(!bw.dom||!bw.xmlhttp_test()){ -this.goto_url("error","_code=0x199"); -return; -} -for(var n in this.gui_objects){ -this.gui_objects[n]=rcube_find_object(this.gui_objects[n]); -} -if(this.env.framed&&parent.rcmail&&parent.rcmail.set_busy){ -parent.rcmail.set_busy(false); -} -this.enable_command("logout","mail","addressbook","settings",true); -if(this.env.permaurl){ -this.enable_command("permaurl",true); -} -switch(this.task){ -case "mail": -if(this.gui_objects.messagelist){ -this.message_list=new rcube_list_widget(this.gui_objects.messagelist,{multiselect:true,draggable:true,keyboard:true,dblclick_time:this.dblclick_time}); -this.message_list.row_init=function(o){ -p.init_message_row(o); -}; -this.message_list.addEventListener("dblclick",function(o){ -p.msglist_dbl_click(o); -}); -this.message_list.addEventListener("keypress",function(o){ -p.msglist_keypress(o); -}); -this.message_list.addEventListener("select",function(o){ -p.msglist_select(o); -}); -this.message_list.addEventListener("dragstart",function(o){ -p.drag_start(o); -}); -this.message_list.addEventListener("dragmove",function(o,e){ -p.drag_move(e); -}); -this.message_list.addEventListener("dragend",function(o){ -p.drag_active=false; -}); -this.message_list.init(); -this.enable_command("toggle_status","toggle_flag",true); -if(this.gui_objects.mailcontframe){ -this.gui_objects.mailcontframe.onmousedown=function(e){ -return p.click_on_list(e); -}; -document.onmouseup=function(e){ -return p.doc_mouse_up(e); -}; -}else{ -this.message_list.focus(); -} -} -if(this.env.coltypes){ -this.set_message_coltypes(this.env.coltypes); -} -this.enable_command("list","checkmail","compose","add-contact","search","reset-search","collapse-folder",true); -if(this.env.search_text!=null&&document.getElementById("quicksearchbox")!=null){ -document.getElementById("quicksearchbox").value=this.env.search_text; -} -if(this.env.action=="show"||this.env.action=="preview"){ -this.enable_command("show","reply","reply-all","forward","moveto","delete","mark","viewsource","print","load-attachment","load-headers",true); -if(this.env.next_uid){ -this.enable_command("nextmessage",true); -this.enable_command("lastmessage",true); -} -if(this.env.prev_uid){ -this.enable_command("previousmessage",true); -this.enable_command("firstmessage",true); -} -} -if(this.env.trash_mailbox&&this.env.mailbox!=this.env.trash_mailbox){ -this.set_alttext("delete","movemessagetotrash"); -} -if(this.env.action=="preview"&&this.env.framed&&parent.rcmail){ -this.enable_command("compose","add-contact",false); -parent.rcmail.show_contentframe(true); -} -if((this.env.action=="show"||this.env.action=="preview")&&this.env.blockedobjects){ -if(this.gui_objects.remoteobjectsmsg){ -this.gui_objects.remoteobjectsmsg.style.display="block"; -} -this.enable_command("load-images","always-load",true); -} -if(this.env.action=="compose"){ -this.enable_command("add-attachment","send-attachment","remove-attachment","send",true); -if(this.env.spellcheck){ -this.env.spellcheck.spelling_state_observer=function(s){ -_1.set_spellcheck_state(s); -}; -this.set_spellcheck_state("ready"); -if(rcube_find_object("_is_html").value=="1"){ -this.display_spellcheck_controls(false); -} -} -if(this.env.drafts_mailbox){ -this.enable_command("savedraft",true); -} -document.onmouseup=function(e){ -return p.doc_mouse_up(e); -}; -} -if(this.env.messagecount){ -this.enable_command("select-all","select-none","expunge",true); -} -if(this.purge_mailbox_test()){ -this.enable_command("purge",true); -} -this.set_page_buttons(); -if(this.env.action=="compose"){ -this.init_messageform(); -} -if(this.env.action=="print"){ -window.print(); -} -if(this.gui_objects.mailboxlist){ -this.env.unread_counts={}; -this.gui_objects.folderlist=this.gui_objects.mailboxlist; -this.http_request("getunread",""); -} -if(this.env.mdn_request&&this.env.uid){ -var _1f="_uid="+this.env.uid+"&_mbox="+urlencode(this.env.mailbox); -if(confirm(this.get_label("mdnrequest"))){ -this.http_post("sendmdn",_1f); -}else{ -this.http_post("mark",_1f+"&_flag=mdnsent"); -} -} -break; -case "addressbook": -if(this.gui_objects.contactslist){ -this.contact_list=new rcube_list_widget(this.gui_objects.contactslist,{multiselect:true,draggable:true,keyboard:true}); -this.contact_list.addEventListener("keypress",function(o){ -p.contactlist_keypress(o); -}); -this.contact_list.addEventListener("select",function(o){ -p.contactlist_select(o); -}); -this.contact_list.addEventListener("dragstart",function(o){ -p.drag_start(o); -}); -this.contact_list.addEventListener("dragmove",function(o,e){ -p.drag_move(e); -}); -this.contact_list.addEventListener("dragend",function(o){ -p.drag_active=false; -}); -this.contact_list.init(); -if(this.env.cid){ -this.contact_list.highlight_row(this.env.cid); -} -if(this.gui_objects.contactslist.parentNode){ -this.gui_objects.contactslist.parentNode.onmousedown=function(e){ -return p.click_on_list(e); -}; -document.onmouseup=function(e){ -return p.doc_mouse_up(e); -}; -}else{ -this.contact_list.focus(); -} -} -this.set_page_buttons(); -if(this.env.address_sources&&this.env.address_sources[this.env.source]&&!this.env.address_sources[this.env.source].readonly){ -this.enable_command("add",true); -} -if(this.env.cid){ -this.enable_command("show","edit",true); -} -if((this.env.action=="add"||this.env.action=="edit")&&this.gui_objects.editform){ -this.enable_command("save",true); -}else{ -this.enable_command("search","reset-search","moveto","import",true); -} -if(this.contact_list&&this.contact_list.rowcount>0){ -this.enable_command("export",true); -} -this.enable_command("list",true); -break; -case "settings": -this.enable_command("preferences","identities","save","folders",true); -if(this.env.action=="identities"||this.env.action=="edit-identity"||this.env.action=="add-identity"){ -this.enable_command("add",this.env.identities_level<2); -this.enable_command("delete","edit",true); -} -if(this.env.action=="edit-identity"||this.env.action=="add-identity"){ -this.enable_command("save",true); -} -if(this.env.action=="folders"){ -this.enable_command("subscribe","unsubscribe","create-folder","rename-folder","delete-folder",true); -} -if(this.gui_objects.identitieslist){ -this.identity_list=new rcube_list_widget(this.gui_objects.identitieslist,{multiselect:false,draggable:false,keyboard:false}); -this.identity_list.addEventListener("select",function(o){ -p.identity_select(o); -}); -this.identity_list.init(); -this.identity_list.focus(); -if(this.env.iid){ -this.identity_list.highlight_row(this.env.iid); -} -} -if(this.gui_objects.subscriptionlist){ -this.init_subscription_list(); -} -break; -case "login": -var _29=rcube_find_object("rcmloginuser"); -var _2a=rcube_find_object("rcmloginpwd"); -var _2b=rcube_find_object("rcmlogintz"); -if(_29){ -_29.onkeyup=function(e){ -return rcmail.login_user_keyup(e); -}; -} -if(_29&&_29.value==""){ -_29.focus(); -}else{ -if(_2a){ -_2a.focus(); -} -} -if(_2b){ -_2b.value=new Date().getTimezoneOffset()/-60; -} -this.enable_command("login",true); -break; -default: -break; -} -this.enable_command("logout",true); -this.loaded=true; -if(this.pending_message){ -this.display_message(this.pending_message[0],this.pending_message[1]); -} -this.start_keepalive(); -for(var i=0;i=0){ -this.set_env("flagged_col",_32+1); -} -} -if(this.env.flagged_col&&(row.flagged_icon=row.obj.cells[this.env.flagged_col].childNodes[0])&&row.flagged_icon.nodeName=="IMG"){ -var p=this; -row.flagged_icon.id="flaggedicn_"+row.uid; -row.flagged_icon._row=row.obj; -row.flagged_icon.onmousedown=function(e){ -p.command("toggle_flag",this); -}; -} -}; -this.init_messageform=function(){ -if(!this.gui_objects.messageform){ -return false; -} -var _34=rcube_find_object("_from"); -var _35=rcube_find_object("_to"); -var _36=rcube_find_object("_cc"); -var _37=rcube_find_object("_bcc"); -var _38=rcube_find_object("_replyto"); -var _39=rcube_find_object("_subject"); -var _3a=rcube_find_object("_message"); -var _3b=rcube_find_object("_draft_saveid"); -if(_35){ -this.init_address_input_events(_35); -} -if(_36){ -this.init_address_input_events(_36); -} -if(_37){ -this.init_address_input_events(_37); -} -if(_34&&_34.type=="select-one"&&(!_3b||_3b.value=="")&&rcube_find_object("_is_html").value!="1"){ -this.change_identity(_34); -} -if(_35&&_35.value==""){ -_35.focus(); -}else{ -if(_39&&_39.value==""){ -_39.focus(); -}else{ -if(_3a){ -this.set_caret2start(_3a); -} -} -} -this.compose_field_hash(true); -this.auto_save_start(); -}; -this.init_address_input_events=function(obj){ -var _3d=function(e){ -return _1.ksearch_keypress(e,this); -}; -if(obj.addEventListener){ -obj.addEventListener(bw.safari?"keydown":"keypress",_3d,false); -}else{ -obj.onkeydown=_3d; -} -obj.setAttribute("autocomplete","off"); -}; -this.command=function(_3f,_40,obj){ -if(obj&&obj.blur){ -obj.blur(); -} -if(this.busy){ -return false; -} -if(!this.commands[_3f]){ -if(this.env.framed&&parent.rcmail&&parent.rcmail.command){ -parent.rcmail.command(_3f,_40); -} -return false; -} -if(this.task=="mail"&&this.env.action=="compose"&&(_3f=="list"||_3f=="mail"||_3f=="addressbook"||_3f=="settings")){ -if(this.cmp_hash!=this.compose_field_hash()&&!confirm(this.get_label("notsentwarning"))){ -return false; -} -} -switch(_3f){ -case "login": -if(this.gui_objects.loginform){ -this.gui_objects.loginform.submit(); -} -break; -case "logout": -this.goto_url("logout","",true); -break; -case "mail": -case "addressbook": -case "settings": -this.switch_task(_3f); -break; -case "permaurl": -if(obj&&obj.href&&obj.target){ -return true; -}else{ -if(this.env.permaurl){ -parent.location.href=this.env.permaurl; -} -} -break; -case "list": -if(this.task=="mail"){ -if(this.env.search_request<0||(_40!=""&&(this.env.search_request&&_40!=this.env.mailbox))){ -this.reset_qsearch(); -} -this.list_mailbox(_40); -if(this.env.trash_mailbox){ -this.set_alttext("delete",this.env.mailbox!=this.env.trash_mailbox?"movemessagetotrash":"deletemessage"); -} -}else{ -if(this.task=="addressbook"){ -if(this.env.search_request<0||(this.env.search_request&&_40!=this.env.source)){ -this.reset_qsearch(); -} -this.list_contacts(_40); -this.enable_command("add",(this.env.address_sources&&!this.env.address_sources[_40].readonly)); -} -} -break; -case "load-headers": -this.load_headers(obj); -break; -case "sort": -var _42=_40.split("_"); -var _43=_42[0]; -var _44=_42[1]?_42[1].toUpperCase():null; -var _45; -if(_44==null){ -if(this.env.sort_col==_43){ -_44=this.env.sort_order=="ASC"?"DESC":"ASC"; -}else{ -_44=this.env.sort_order; -} -} -if(this.env.sort_col==_43&&this.env.sort_order==_44){ -break; -} -if(_45=document.getElementById("rcm"+this.env.sort_col)){ -this.set_classname(_45,"sorted"+(this.env.sort_order.toUpperCase()),false); -} -if(_45=document.getElementById("rcm"+_43)){ -this.set_classname(_45,"sorted"+_44,true); -} -this.env.sort_col=_43; -this.env.sort_order=_44; -this.list_mailbox("","",_43+"_"+_44); -break; -case "nextpage": -this.list_page("next"); -break; -case "lastpage": -this.list_page("last"); -break; -case "previouspage": -this.list_page("prev"); -break; -case "firstpage": -this.list_page("first"); -break; -case "expunge": -if(this.env.messagecount){ -this.expunge_mailbox(this.env.mailbox); -} -break; -case "purge": -case "empty-mailbox": -if(this.env.messagecount){ -this.purge_mailbox(this.env.mailbox); -} -break; -case "show": -if(this.task=="mail"){ -var uid=this.get_single_uid(); -if(uid&&(!this.env.uid||uid!=this.env.uid)){ -if(this.env.mailbox==this.env.drafts_mailbox){ -this.goto_url("compose","_draft_uid="+uid+"&_mbox="+urlencode(this.env.mailbox),true); -}else{ -this.show_message(uid); -} -} -}else{ -if(this.task=="addressbook"){ -var cid=_40?_40:this.get_single_cid(); -if(cid&&!(this.env.action=="show"&&cid==this.env.cid)){ -this.load_contact(cid,"show"); -} -} -} -break; -case "add": -if(this.task=="addressbook"){ -this.load_contact(0,"add"); -}else{ -if(this.task=="settings"){ -this.identity_list.clear_selection(); -this.load_identity(0,"add-identity"); -} -} -break; -case "edit": -var cid; -if(this.task=="addressbook"&&(cid=this.get_single_cid())){ -this.load_contact(cid,"edit"); -}else{ -if(this.task=="settings"&&_40){ -this.load_identity(_40,"edit-identity"); -} -} -break; -case "save-identity": -case "save": -if(this.gui_objects.editform){ -var _48=rcube_find_object("_pagesize"); -var _49=rcube_find_object("_name"); -var _4a=rcube_find_object("_email"); -if(_48&&isNaN(parseInt(_48.value))){ -alert(this.get_label("nopagesizewarning")); -_48.focus(); -break; -}else{ -if(_49&&_49.value==""){ -alert(this.get_label("nonamewarning")); -_49.focus(); -break; -}else{ -if(_4a&&!rcube_check_email(_4a.value)){ -alert(this.get_label("noemailwarning")); -_4a.focus(); -break; -} -} -} -this.gui_objects.editform.submit(); -} -break; -case "delete": -if(this.task=="mail"){ -this.delete_messages(); -}else{ -if(this.task=="addressbook"){ -this.delete_contacts(); -}else{ -if(this.task=="settings"){ -this.delete_identity(); -} -} -} -break; -case "move": -case "moveto": -if(this.task=="mail"){ -this.move_messages(_40); -}else{ -if(this.task=="addressbook"&&this.drag_active){ -this.copy_contact(null,_40); -} -} -break; -case "mark": -if(_40){ -this.mark_message(_40); -} -break; -case "toggle_status": -if(_40&&!_40._row){ -break; -} -var uid; -var _4b="read"; -if(_40._row.uid){ -uid=_40._row.uid; -if(this.message_list.rows[uid].deleted){ -_4b="undelete"; -}else{ -if(!this.message_list.rows[uid].unread){ -_4b="unread"; -} -} -} -this.mark_message(_4b,uid); -break; -case "toggle_flag": -if(_40&&!_40._row){ -break; -} -var uid; -var _4b="flagged"; -if(_40._row.uid){ -uid=_40._row.uid; -if(this.message_list.rows[uid].flagged){ -_4b="unflagged"; -} -} -this.mark_message(_4b,uid); -break; -case "always-load": -if(this.env.uid&&this.env.sender){ -this.add_contact(urlencode(this.env.sender)); -window.setTimeout(function(){ -_1.command("load-images"); -},300); -break; -} -case "load-images": -if(this.env.uid){ -this.show_message(this.env.uid,true,this.env.action=="preview"); -} -break; -case "load-attachment": -var _4c="_mbox="+urlencode(this.env.mailbox)+"&_uid="+this.env.uid+"&_part="+_40.part; -if(this.env.uid&&_40.mimetype&&find_in_array(_40.mimetype,this.mimetypes)>=0){ -if(_40.mimetype=="text/html"){ -_4c+="&_safe=1"; -} -this.attachment_win=window.open(this.env.comm_path+"&_action=get&"+_4c+"&_frame=1","rcubemailattachment"); -if(this.attachment_win){ -window.setTimeout(function(){ -_1.attachment_win.focus(); -},10); -break; -} -} -this.goto_url("get",_4c+"&_download=1",false); -break; -case "select-all": -this.message_list.select_all(_40); -break; -case "select-none": -this.message_list.clear_selection(); -break; -case "nextmessage": -if(this.env.next_uid){ -this.show_message(this.env.next_uid,false,this.env.action=="preview"); -} -break; -case "lastmessage": -if(this.env.last_uid){ -this.show_message(this.env.last_uid); -} -break; -case "previousmessage": -if(this.env.prev_uid){ -this.show_message(this.env.prev_uid,false,this.env.action=="preview"); -} -break; -case "firstmessage": -if(this.env.first_uid){ -this.show_message(this.env.first_uid); -} -break; -case "checkmail": -this.check_for_recent(true); -break; -case "compose": -var url=this.env.comm_path+"&_action=compose"; -if(this.task=="mail"){ -url+="&_mbox="+urlencode(this.env.mailbox); -if(this.env.mailbox==this.env.drafts_mailbox){ -var uid; -if(uid=this.get_single_uid()){ -url+="&_draft_uid="+uid; -} -}else{ -if(_40){ -url+="&_to="+urlencode(_40); -} -} -}else{ -if(this.task=="addressbook"){ -if(_40&&_40.indexOf("@")>0){ -url=this.get_task_url("mail",url); -this.redirect(url+"&_to="+urlencode(_40)); -break; -} -var _4e=new Array(); -if(_40){ -_4e[_4e.length]=_40; -}else{ -if(this.contact_list){ -var _4f=this.contact_list.get_selection(); -for(var n=0;n<_4f.length;n++){ -_4e[_4e.length]=_4f[n]; -} -} -} -if(_4e.length){ -this.http_request("mailto","_cid="+urlencode(_4e.join(","))+"&_source="+urlencode(this.env.source),true); -} -break; -} -} -url=url.replace(/&_framed=1/,""); -this.redirect(url); -break; -case "spellcheck": -if(window.tinyMCE&&tinyMCE.get("compose-body")){ -tinyMCE.execCommand("mceSpellCheck",true); -}else{ -if(this.env.spellcheck&&this.env.spellcheck.spellCheck&&this.spellcheck_ready){ -this.env.spellcheck.spellCheck(this.env.spellcheck.check_link); -this.set_spellcheck_state("checking"); -} -} -break; -case "savedraft": -self.clearTimeout(this.save_timer); -if(!this.gui_objects.messageform){ -break; -} -if(!this.env.drafts_mailbox||this.cmp_hash==this.compose_field_hash()){ -break; -} -this.set_busy(true,"savingmessage"); -var _51=this.gui_objects.messageform; -_51.target="savetarget"; -_51._draft.value="1"; -_51.submit(); -break; -case "send": -if(!this.gui_objects.messageform){ -break; -} -if(!this.check_compose_input()){ -break; -} -self.clearTimeout(this.save_timer); -this.set_busy(true,"sendingmessage"); -var _51=this.gui_objects.messageform; -_51.target="savetarget"; -_51._draft.value=""; -_51.submit(); -clearTimeout(this.request_timer); -break; -case "add-attachment": -this.show_attachment_form(true); -case "send-attachment": -self.clearTimeout(this.save_timer); -this.upload_file(_40); -break; -case "remove-attachment": -this.remove_attachment(_40); -break; -case "reply-all": -case "reply": -var uid; -if(uid=this.get_single_uid()){ -this.goto_url("compose","_reply_uid="+uid+"&_mbox="+urlencode(this.env.mailbox)+(_3f=="reply-all"?"&_all=1":""),true); -} -break; -case "forward": -var uid; -if(uid=this.get_single_uid()){ -this.goto_url("compose","_forward_uid="+uid+"&_mbox="+urlencode(this.env.mailbox),true); -} -break; -case "print": -var uid; -if(uid=this.get_single_uid()){ -_1.printwin=window.open(this.env.comm_path+"&_action=print&_uid="+uid+"&_mbox="+urlencode(this.env.mailbox)+(this.env.safemode?"&_safe=1":"")); -if(this.printwin){ -window.setTimeout(function(){ -_1.printwin.focus(); -},20); -if(this.env.action!="show"){ -this.mark_message("read",uid); -} -} -} -break; -case "viewsource": -var uid; -if(uid=this.get_single_uid()){ -_1.sourcewin=window.open(this.env.comm_path+"&_action=viewsource&_uid="+this.env.uid+"&_mbox="+urlencode(this.env.mailbox)); -if(this.sourcewin){ -window.setTimeout(function(){ -_1.sourcewin.focus(); -},20); -} -} -break; -case "add-contact": -this.add_contact(_40); -break; -case "search": -if(!_40&&this.gui_objects.qsearchbox){ -_40=this.gui_objects.qsearchbox.value; -} -if(_40){ -this.qsearch(_40); -break; -} -case "reset-search": -var s=this.env.search_request; -this.reset_qsearch(); -if(s&&this.env.mailbox){ -this.list_mailbox(this.env.mailbox); -}else{ -if(s&&this.task=="addressbook"){ -this.list_contacts(this.env.source); -} -} -break; -case "import": -if(this.env.action=="import"&&this.gui_objects.importform){ -var _53=document.getElementById("rcmimportfile"); -if(_53&&!_53.value){ -alert(this.get_label("selectimportfile")); -break; -} -this.gui_objects.importform.submit(); -this.set_busy(true,"importwait"); -this.lock_form(this.gui_objects.importform,true); -}else{ -this.goto_url("import"); -} -break; -case "export": -if(this.contact_list.rowcount>0){ -var _54=(this.env.source?"_source="+urlencode(this.env.source)+"&":""); -if(this.env.search_request){ -_54+="_search="+this.env.search_request; -} -this.goto_url("export",_54); -} -break; -case "collapse-folder": -if(_40){ -this.collapse_folder(_40); -} -break; -case "preferences": -this.goto_url(""); -break; -case "identities": -this.goto_url("identities"); -break; -case "delete-identity": -this.delete_identity(); -case "folders": -this.goto_url("folders"); -break; -case "subscribe": -this.subscribe_folder(_40); -break; -case "unsubscribe": -this.unsubscribe_folder(_40); -break; -case "create-folder": -this.create_folder(_40); -break; -case "rename-folder": -this.rename_folder(_40); -break; -case "delete-folder": -this.delete_folder(_40); -break; -} -return obj?false:true; -}; -this.enable_command=function(){ -var _55=arguments; -if(!_55.length){ -return -1; -} -var _56; -var _57=_55[_55.length-1]; -for(var n=0;n<_55.length-1;n++){ -_56=_55[n]; -this.commands[_56]=_57; -this.set_button(_56,(_57?"act":"pas")); -} -return true; -}; -this.set_busy=function(a,_5a){ -if(a&&_5a){ -var msg=this.get_label(_5a); -if(msg==_5a){ -msg="Loading..."; -} -this.display_message(msg,"loading",true); -}else{ -if(!a){ -this.hide_message(); -} -} -this.busy=a; -if(this.gui_objects.editform){ -this.lock_form(this.gui_objects.editform,a); -} -if(this.request_timer){ -clearTimeout(this.request_timer); -} -if(a&&this.env.request_timeout){ -this.request_timer=window.setTimeout(function(){ -_1.request_timed_out(); -},this.env.request_timeout*1000); -} -}; -this.get_label=function(_5c){ -if(this.labels[_5c]){ -return this.labels[_5c]; -}else{ -return _5c; -} -}; -this.switch_task=function(_5d){ -if(this.task===_5d&&_5d!="mail"){ -return; -} -var url=this.get_task_url(_5d); -if(_5d=="mail"){ -url+="&_mbox=INBOX"; -} -this.redirect(url); -}; -this.get_task_url=function(_5f,url){ -if(!url){ -url=this.env.comm_path; -} -return url.replace(/_task=[a-z]+/,"_task="+_5f); -}; -this.request_timed_out=function(){ -this.set_busy(false); -this.display_message("Request timed out!","error"); -}; -this.doc_mouse_up=function(e){ -var _62,li; -if(this.message_list){ -if(!rcube_mouse_is_over(e,this.message_list.list)){ -this.message_list.blur(); -} -_62=this.env.mailboxes; -}else{ -if(this.contact_list){ -if(!rcube_mouse_is_over(e,this.contact_list.list)){ -this.contact_list.blur(); -} -_62=this.env.address_sources; -}else{ -if(this.ksearch_value){ -this.ksearch_blur(); -} -} -} -if(this.drag_active&&_62&&this.env.last_folder_target){ -this.set_classname(this.get_folder_li(this.env.last_folder_target),"droptarget",false); -this.command("moveto",_62[this.env.last_folder_target].id); -this.env.last_folder_target=null; -} -}; -this.drag_start=function(_64){ -this.initialBodyScrollTop=bw.ie?0:window.pageYOffset; -this.initialMailBoxScrollTop=document.getElementById("mailboxlist-container").scrollTop; -var _65=this.task=="mail"?this.env.mailboxes:this.env.address_sources; -this.drag_active=true; -if(this.preview_timer){ -clearTimeout(this.preview_timer); -} -if(this.gui_objects.folderlist&&_65){ -var li,pos,_64,_68; -_64=rcube_find_object(this.task=="mail"?"mailboxlist":"directorylist"); -pos=rcube_get_object_pos(_64); -this.env.folderlist_coords={x1:pos.x,y1:pos.y,x2:pos.x+_64.offsetWidth,y2:pos.y+_64.offsetHeight}; -this.env.folder_coords=new Array(); -for(var k in _65){ -if(li=this.get_folder_li(k)){ -pos=rcube_get_object_pos(li.firstChild); -if(_68=li.firstChild.offsetHeight){ -this.env.folder_coords[k]={x1:pos.x,y1:pos.y,x2:pos.x+li.firstChild.offsetWidth,y2:pos.y+_68}; -} -} -} -} -}; -this.drag_move=function(e){ -if(this.gui_objects.folderlist&&this.env.folder_coords){ -var _6b=bw.ie?-document.documentElement.scrollTop:this.initialBodyScrollTop; -var _6c=this.initialMailBoxScrollTop-document.getElementById("mailboxlist-container").scrollTop; -var _6d=-_6c-_6b; -var li,pos,_70; -_70=rcube_event.get_mouse_pos(e); -pos=this.env.folderlist_coords; -_70.y+=_6d; -if(_70.x=pos.x2||_70.y=pos.y2){ -if(this.env.last_folder_target){ -this.set_classname(this.get_folder_li(this.env.last_folder_target),"droptarget",false); -this.env.last_folder_target=null; -} -return; -} -for(var k in this.env.folder_coords){ -pos=this.env.folder_coords[k]; -if(this.check_droptarget(k)&&((_70.x>=pos.x1)&&(_70.x=pos.y1)&&(_70.y0)&&li.nextSibling.getElementsByTagName("ul")[0].style&&(li.nextSibling.getElementsByTagName("ul")[0].style.display!="none")){ -li.nextSibling.getElementsByTagName("ul")[0].style.display="none"; -li.nextSibling.getElementsByTagName("ul")[0].style.display=""; -} -this.http_post("save-pref","_name=collapsed_folders&_value="+urlencode(this.env.collapsed_folders)); -this.set_unread_count_display(id,false); -} -}; -this.click_on_list=function(e){ -if(this.gui_objects.qsearchbox){ -this.gui_objects.qsearchbox.blur(); -} -if(this.message_list){ -this.message_list.focus(); -}else{ -if(this.contact_list){ -this.contact_list.focus(); -} -} -var _77; -if(_77=this.get_folder_li()){ -this.set_classname(_77,"unfocused",true); -} -return rcube_event.get_button(e)==2?true:rcube_event.cancel(e); -}; -this.msglist_select=function(_78){ -if(this.preview_timer){ -clearTimeout(this.preview_timer); -} -var _79=_78.selection.length==1; -if(this.env.mailbox==this.env.drafts_mailbox){ -this.enable_command("reply","reply-all","forward",false); -this.enable_command("show","print",_79); -this.enable_command("delete","moveto","mark",(_78.selection.length>0?true:false)); -}else{ -this.enable_command("show","reply","reply-all","forward","print",_79); -this.enable_command("delete","moveto","mark",(_78.selection.length>0?true:false)); -} -if(_79&&this.env.contentframe&&!_78.multi_selecting){ -this.preview_timer=window.setTimeout(function(){ -_1.msglist_get_preview(); -},200); -}else{ -if(this.env.contentframe){ -this.show_contentframe(false); -} -} -}; -this.msglist_dbl_click=function(_7a){ -if(this.preview_timer){ -clearTimeout(this.preview_timer); -} -var uid=_7a.get_single_selection(); -if(uid&&this.env.mailbox==this.env.drafts_mailbox){ -this.goto_url("compose","_draft_uid="+uid+"&_mbox="+urlencode(this.env.mailbox),true); -}else{ -if(uid){ -this.show_message(uid,false,false); -} -} -}; -this.msglist_keypress=function(_7c){ -if(_7c.key_pressed==_7c.ENTER_KEY){ -this.command("show"); -}else{ -if(_7c.key_pressed==_7c.DELETE_KEY){ -this.command("delete"); -}else{ -if(_7c.key_pressed==_7c.BACKSPACE_KEY){ -this.command("delete"); -}else{ -_7c.shiftkey=false; -} -} -} -}; -this.msglist_get_preview=function(){ -var uid=this.get_single_uid(); -if(uid&&this.env.contentframe&&!this.drag_active){ -this.show_message(uid,false,true); -}else{ -if(this.env.contentframe){ -this.show_contentframe(false); -} -} -}; -this.check_droptarget=function(id){ -if(this.task=="mail"){ -return (this.env.mailboxes[id]&&this.env.mailboxes[id].id!=this.env.mailbox&&!this.env.mailboxes[id].virtual); -}else{ -if(this.task=="addressbook"){ -return (id!=this.env.source&&this.env.address_sources[id]&&!this.env.address_sources[id].readonly); -}else{ -if(this.task=="settings"){ -return (id!=this.env.folder); -} -} -} -}; -this.show_message=function(id,_80,_81){ -if(!id){ -return; -} -var _82=""; -var _83=_81?"preview":"show"; -var _84=window; -if(_81&&this.env.contentframe&&window.frames&&window.frames[this.env.contentframe]){ -_84=window.frames[this.env.contentframe]; -_82="&_framed=1"; -} -if(_80){ -_82="&_safe=1"; -} -if(this.env.search_request){ -_82+="&_search="+this.env.search_request; -} -var url="&_action="+_83+"&_uid="+id+"&_mbox="+urlencode(this.env.mailbox)+_82; -if(_83=="preview"&&String(_84.location.href).indexOf(url)>=0){ -this.show_contentframe(true); -}else{ -this.set_busy(true,"loading"); -_84.location.href=this.env.comm_path+url; -if(_83=="preview"&&this.message_list&&this.message_list.rows[id]&&this.message_list.rows[id].unread){ -this.set_message(id,"unread",false); -if(this.env.unread_counts[this.env.mailbox]){ -this.env.unread_counts[this.env.mailbox]-=1; -this.set_unread_count(this.env.mailbox,this.env.unread_counts[this.env.mailbox],this.env.mailbox=="INBOX"); -} -} -} -}; -this.show_contentframe=function(_86){ -var frm; -if(this.env.contentframe&&(frm=rcube_find_object(this.env.contentframe))){ -if(!_86&&window.frames[this.env.contentframe]){ -if(window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)<0){ -window.frames[this.env.contentframe].location.href=this.env.blankpage; -} -}else{ -if(!bw.safari&&!bw.konq){ -frm.style.display=_86?"block":"none"; -} -} -} -if(!_86&&this.busy){ -this.set_busy(false); -} -}; -this.list_page=function(_88){ -if(_88=="next"){ -_88=this.env.current_page+1; -} -if(_88=="last"){ -_88=this.env.pagecount; -} -if(_88=="prev"&&this.env.current_page>1){ -_88=this.env.current_page-1; -} -if(_88=="first"&&this.env.current_page>1){ -_88=1; -} -if(_88>0&&_88<=this.env.pagecount){ -this.env.current_page=_88; -if(this.task=="mail"){ -this.list_mailbox(this.env.mailbox,_88); -}else{ -if(this.task=="addressbook"){ -this.list_contacts(this.env.source,_88); -} -} -} -}; -this.filter_mailbox=function(_89){ -var _8a; -if(this.gui_objects.qsearchbox){ -_8a=this.gui_objects.qsearchbox.value; -} -this.message_list.clear(); -this.env.current_page=1; -this.set_busy(true,"searching"); -this.http_request("search","_filter="+_89+(_8a?"&_q="+urlencode(_8a):"")+(this.env.mailbox?"&_mbox="+urlencode(this.env.mailbox):""),true); -}; -this.list_mailbox=function(_8b,_8c,_8d){ -this.last_selected=0; -var _8e=""; -var _8f=window; -if(!_8b){ -_8b=this.env.mailbox; -} -if(_8d){ -_8e+="&_sort="+_8d; -} -if(this.env.search_request){ -_8e+="&_search="+this.env.search_request; -} -if(!_8c&&_8b!=this.env.mailbox){ -_8c=1; -this.env.current_page=_8c; -if(this.message_list){ -this.message_list.clear_selection(); -} -this.show_contentframe(false); -} -if(_8b!=this.env.mailbox||(_8b==this.env.mailbox&&!_8c&&!_8d)){ -_8e+="&_refresh=1"; -} -this.select_folder(_8b,this.env.mailbox); -this.env.mailbox=_8b; -if(this.gui_objects.messagelist){ -this.list_mailbox_remote(_8b,_8c,_8e); -return; -} -if(this.env.contentframe&&window.frames&&window.frames[this.env.contentframe]){ -_8f=window.frames[this.env.contentframe]; -_8e+="&_framed=1"; -} -if(_8b){ -this.set_busy(true,"loading"); -_8f.location.href=this.env.comm_path+"&_mbox="+urlencode(_8b)+(_8c?"&_page="+_8c:"")+_8e; -} -}; -this.list_mailbox_remote=function(_90,_91,_92){ -this.message_list.clear(); -var url="_mbox="+urlencode(_90)+(_91?"&_page="+_91:""); -this.set_busy(true,"loading"); -this.http_request("list",url+_92,true); -}; -this.expunge_mailbox=function(_94){ -var _95=false; -var _96=""; -if(_94==this.env.mailbox){ -_95=true; -this.set_busy(true,"loading"); -_96="&_reload=1"; -} -var url="_mbox="+urlencode(_94); -this.http_post("expunge",url+_96,_95); -}; -this.purge_mailbox=function(_98){ -var _99=false; -var _9a=""; -if(!confirm(this.get_label("purgefolderconfirm"))){ -return false; -} -if(_98==this.env.mailbox){ -_99=true; -this.set_busy(true,"loading"); -_9a="&_reload=1"; -} -var url="_mbox="+urlencode(_98); -this.http_post("purge",url+_9a,_99); -return true; -}; -this.purge_mailbox_test=function(){ -return (this.env.messagecount&&(this.env.mailbox==this.env.trash_mailbox||this.env.mailbox==this.env.junk_mailbox||this.env.mailbox.match("^"+RegExp.escape(this.env.trash_mailbox)+RegExp.escape(this.env.delimiter))||this.env.mailbox.match("^"+RegExp.escape(this.env.junk_mailbox)+RegExp.escape(this.env.delimiter)))); -}; -this.set_message_icon=function(uid){ -var _9d; -var _9e=this.message_list.rows; -if(!_9e[uid]){ -return false; -} -if(_9e[uid].deleted&&this.env.deletedicon){ -_9d=this.env.deletedicon; -}else{ -if(_9e[uid].replied&&this.env.repliedicon){ -if(_9e[uid].forwarded&&this.env.forwardedrepliedicon){ -_9d=this.env.forwardedrepliedicon; -}else{ -_9d=this.env.repliedicon; -} -}else{ -if(_9e[uid].forwarded&&this.env.forwardedicon){ -_9d=this.env.forwardedicon; -}else{ -if(_9e[uid].unread&&this.env.unreadicon){ -_9d=this.env.unreadicon; -}else{ -if(this.env.messageicon){ -_9d=this.env.messageicon; -} -} -} -} -} -if(_9d&&_9e[uid].icon){ -_9e[uid].icon.src=_9d; -} -_9d=""; -if(_9e[uid].flagged&&this.env.flaggedicon){ -_9d=this.env.flaggedicon; -}else{ -if(!_9e[uid].flagged&&this.env.unflaggedicon){ -_9d=this.env.unflaggedicon; -} -} -if(_9e[uid].flagged_icon&&_9d){ -_9e[uid].flagged_icon.src=_9d; -} -}; -this.set_message_status=function(uid,_a0,_a1){ -var _a2=this.message_list.rows; -if(!_a2[uid]){ -return false; -} -if(_a0=="unread"){ -_a2[uid].unread=_a1; -}else{ -if(_a0=="deleted"){ -_a2[uid].deleted=_a1; -}else{ -if(_a0=="replied"){ -_a2[uid].replied=_a1; -}else{ -if(_a0=="forwarded"){ -_a2[uid].forwarded=_a1; -}else{ -if(_a0=="flagged"){ -_a2[uid].flagged=_a1; -} -} -} -} -} -this.env.messages[uid]=_a2[uid]; -}; -this.set_message=function(uid,_a4,_a5){ -var _a6=this.message_list.rows; -if(!_a6[uid]){ -return false; -} -if(_a4){ -this.set_message_status(uid,_a4,_a5); -} -if(_a6[uid].unread&&_a6[uid].classname.indexOf("unread")<0){ -_a6[uid].classname+=" unread"; -this.set_classname(_a6[uid].obj,"unread",true); -}else{ -if(!_a6[uid].unread&&_a6[uid].classname.indexOf("unread")>=0){ -_a6[uid].classname=_a6[uid].classname.replace(/\s*unread/,""); -this.set_classname(_a6[uid].obj,"unread",false); -} -} -if(_a6[uid].deleted&&_a6[uid].classname.indexOf("deleted")<0){ -_a6[uid].classname+=" deleted"; -this.set_classname(_a6[uid].obj,"deleted",true); -}else{ -if(!_a6[uid].deleted&&_a6[uid].classname.indexOf("deleted")>=0){ -_a6[uid].classname=_a6[uid].classname.replace(/\s*deleted/,""); -this.set_classname(_a6[uid].obj,"deleted",false); -} -} -if(_a6[uid].flagged&&_a6[uid].classname.indexOf("flagged")<0){ -_a6[uid].classname+=" flagged"; -this.set_classname(_a6[uid].obj,"flagged",true); -}else{ -if(!_a6[uid].flagged&&_a6[uid].classname.indexOf("flagged")>=0){ -_a6[uid].classname=_a6[uid].classname.replace(/\s*flagged/,""); -this.set_classname(_a6[uid].obj,"flagged",false); -} -} -this.set_message_icon(uid); -}; -this.move_messages=function(_a7){ -if(!_a7||_a7==this.env.mailbox||(!this.env.uid&&(!this.message_list||!this.message_list.get_selection().length))){ -return; -} -var _a8=false; -var _a9="&_target_mbox="+urlencode(_a7)+"&_from="+(this.env.action?this.env.action:""); -if(this.env.action=="show"){ -_a8=true; -this.set_busy(true,"movingmessage"); -}else{ -if(!this.env.flag_for_deletion){ -this.show_contentframe(false); -} -} -this.enable_command("reply","reply-all","forward","delete","mark","print",false); -this._with_selected_messages("moveto",_a8,_a9,(this.env.flag_for_deletion?false:true)); -}; -this.delete_messages=function(){ -var _aa=this.message_list?this.message_list.get_selection():new Array(); -if(!this.env.uid&&!_aa.length){ -return; -} -if(this.env.trash_mailbox&&String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase()){ -if(this.message_list&&this.message_list.shiftkey){ -if(confirm(this.get_label("deletemessagesconfirm"))){ -this.permanently_remove_messages(); -} -}else{ -this.move_messages(this.env.trash_mailbox); -} -}else{ -if(this.env.trash_mailbox&&String(this.env.mailbox).toLowerCase()==String(this.env.trash_mailbox).toLowerCase()){ -this.permanently_remove_messages(); -}else{ -if(!this.env.trash_mailbox&&this.env.flag_for_deletion){ -this.mark_message("delete"); -if(this.env.action=="show"){ -this.command("nextmessage","",this); -}else{ -if(_aa.length==1){ -this.message_list.select_next(); -} -} -}else{ -if(!this.env.trash_mailbox){ -this.permanently_remove_messages(); -} -} -} -} -}; -this.permanently_remove_messages=function(){ -if(!this.env.uid&&(!this.message_list||!this.message_list.get_selection().length)){ -return; -} -this.show_contentframe(false); -this._with_selected_messages("delete",false,"&_from="+(this.env.action?this.env.action:""),true); -}; -this._with_selected_messages=function(_ab,_ac,_ad,_ae){ -var _af=new Array(); -if(this.env.uid){ -_af[0]=this.env.uid; -}else{ -var _b0=this.message_list.get_selection(); -var _b1=this.message_list.rows; -var id; -for(var n=0;n<_b0.length;n++){ -id=_b0[n]; -_af[_af.length]=id; -if(_ae){ -this.message_list.remove_row(id,(n==_b0.length-1)); -}else{ -this.set_message_status(id,"deleted",true); -if(this.env.read_when_deleted){ -this.set_message_status(id,"unread",false); -} -this.set_message(id); -} -} -} -if(this.env.search_request){ -_ad+="&_search="+this.env.search_request; -} -this.http_post(_ab,"_uid="+_af.join(",")+"&_mbox="+urlencode(this.env.mailbox)+_ad,_ac); -}; -this.mark_message=function(_b4,uid){ -var _b6=new Array(); -var _b7=new Array(); -var _b8=this.message_list?this.message_list.get_selection():new Array(); -if(uid){ -_b6[0]=uid; -}else{ -if(this.env.uid){ -_b6[0]=this.env.uid; -}else{ -if(this.message_list){ -for(var n=0;n<_b8.length;n++){ -_b6[_b6.length]=_b8[n]; -} -} -} -} -if(!this.message_list){ -_b7=_b6; -}else{ -for(var id,n=0;n<_b6.length;n++){ -id=_b6[n]; -if((_b4=="read"&&this.message_list.rows[id].unread)||(_b4=="unread"&&!this.message_list.rows[id].unread)||(_b4=="delete"&&!this.message_list.rows[id].deleted)||(_b4=="undelete"&&this.message_list.rows[id].deleted)||(_b4=="flagged"&&!this.message_list.rows[id].flagged)||(_b4=="unflagged"&&this.message_list.rows[id].flagged)){ -_b7[_b7.length]=id; -} -} -} -if(!_b7.length){ -return; -} -switch(_b4){ -case "read": -case "unread": -this.toggle_read_status(_b4,_b7); -break; -case "delete": -case "undelete": -this.toggle_delete_status(_b7); -break; -case "flagged": -case "unflagged": -this.toggle_flagged_status(_b4,_b6); -break; -} -}; -this.toggle_read_status=function(_bb,_bc){ -for(var i=0;i<_bc.length;i++){ -this.set_message(_bc[i],"unread",(_bb=="unread"?true:false)); -} -this.http_post("mark","_uid="+_bc.join(",")+"&_flag="+_bb); -}; -this.toggle_flagged_status=function(_be,_bf){ -for(var i=0;i<_bf.length;i++){ -this.set_message(_bf[i],"flagged",(_be=="flagged"?true:false)); -} -this.http_post("mark","_uid="+_bf.join(",")+"&_flag="+_be); -}; -this.toggle_delete_status=function(_c1){ -var _c2=this.message_list?this.message_list.rows:new Array(); -if(_c1.length==1){ -if(!_c2.length||(_c2[_c1[0]]&&!_c2[_c1[0]].deleted)){ -this.flag_as_deleted(_c1); -}else{ -this.flag_as_undeleted(_c1); -} -return true; -} -var _c3=true; -for(var i=0;i<_c1.length;i++){ -uid=_c1[i]; -if(_c2[uid]){ -if(!_c2[uid].deleted){ -_c3=false; -break; -} -} -} -if(_c3){ -this.flag_as_undeleted(_c1); -}else{ -this.flag_as_deleted(_c1); -} -return true; -}; -this.flag_as_undeleted=function(_c5){ -for(var i=0;i<_c5.length;i++){ -this.set_message(_c5[i],"deleted",false); -} -this.http_post("mark","_uid="+_c5.join(",")+"&_flag=undelete"); -return true; -}; -this.flag_as_deleted=function(_c7){ -var _c8=""; -var _c9=new Array(); -var _ca=this.message_list?this.message_list.rows:new Array(); -for(var i=0;i<_c7.length;i++){ -uid=_c7[i]; -if(_ca[uid]){ -this.set_message(uid,"deleted",true); -if(_ca[uid].unread){ -_c9[_c9.length]=uid; -} -} -} -if(_c9.length){ -_c8="&_ruid="+_c9.join(","); -} -this.http_post("mark","_uid="+_c7.join(",")+"&_flag=delete"+_c8); -return true; -}; -this.flag_deleted_as_read=function(_cc){ -var _cd; -var _ce=this.message_list?this.message_list.rows:new Array(); -var str=String(_cc); -var _d0=new Array(); -_d0=str.split(","); -for(var uid,i=0;i<_d0.length;i++){ -uid=_d0[i]; -if(_ce[uid]){ -this.set_message(uid,"unread",false); -} -} -}; -this.login_user_keyup=function(e){ -var key=rcube_event.get_keycode(e); -var elm; -if((key==13)&&(elm=rcube_find_object("_pass"))){ -elm.focus(); -return false; -} -}; -this.check_compose_input=function(){ -var _d6=rcube_find_object("_to"); -var _d7=rcube_find_object("_cc"); -var _d8=rcube_find_object("_bcc"); -var _d9=rcube_find_object("_from"); -var _da=rcube_find_object("_subject"); -var _db=rcube_find_object("_message"); -if(_d9.type=="text"&&!rcube_check_email(_d9.value,true)){ -alert(this.get_label("nosenderwarning")); -_d9.focus(); -return false; -} -var _dc=_d6.value?_d6.value:(_d7.value?_d7.value:_d8.value); -if(!rcube_check_email(_dc.replace(/^\s+/,"").replace(/[\s,;]+$/,""),true)){ -alert(this.get_label("norecipientwarning")); -_d6.focus(); -return false; -} -if(_da&&_da.value==""){ -var _dd=prompt(this.get_label("nosubjectwarning"),this.get_label("nosubject")); -if(!_dd&&_dd!==""){ -_da.focus(); -return false; -}else{ -_da.value=_dd?_dd:this.get_label("nosubject"); -} -} -if((!window.tinyMCE||!tinyMCE.get("compose-body"))&&_db.value==""&&!confirm(this.get_label("nobodywarning"))){ -_db.focus(); -return false; -}else{ -if(window.tinyMCE&&tinyMCE.get("compose-body")&&!tinyMCE.get("compose-body").getContent()&&!confirm(this.get_label("nobodywarning"))){ -tinyMCE.get("compose-body").focus(); -return false; -} -} -this.stop_spellchecking(); -return true; -}; -this.stop_spellchecking=function(){ -if(this.env.spellcheck&&!this.spellcheck_ready){ -exec_event(this.env.spellcheck.check_link,"click"); -this.set_spellcheck_state("ready"); -} -}; -this.display_spellcheck_controls=function(vis){ -if(this.env.spellcheck){ -if(!vis){ -this.stop_spellchecking(); -} -this.env.spellcheck.check_link.style.visibility=vis?"visible":"hidden"; -this.env.spellcheck.switch_lan_pic.style.visibility=vis?"visible":"hidden"; -} -}; -this.set_spellcheck_state=function(s){ -this.spellcheck_ready=(s=="check_spelling"||s=="ready"); -this.enable_command("spellcheck",this.spellcheck_ready); -}; -this.set_draft_id=function(id){ -var f; -if(f=rcube_find_object("_draft_saveid")){ -f.value=id; -} -}; -this.auto_save_start=function(){ -if(this.env.draft_autosave){ -this.save_timer=self.setTimeout(function(){ -_1.command("savedraft"); -},this.env.draft_autosave*1000); -} -this.busy=false; -}; -this.compose_field_hash=function(_e2){ -var _e3=rcube_find_object("_to"); -var _e4=rcube_find_object("_cc"); -var _e5=rcube_find_object("_bcc"); -var _e6=rcube_find_object("_subject"); -var _e7,_e8; -var str=""; -if(_e3&&_e3.value){ -str+=_e3.value+":"; -} -if(_e4&&_e4.value){ -str+=_e4.value+":"; -} -if(_e5&&_e5.value){ -str+=_e5.value+":"; -} -if(_e6&&_e6.value){ -str+=_e6.value+":"; -} -if(_e7=tinyMCE.get("compose-body")){ -str+=_e7.getContent(); -}else{ -_e8=rcube_find_object("_message"); -str+=_e8.value; -} -if(_e2){ -this.cmp_hash=str; -} -return str; -}; -this.change_identity=function(obj){ -if(!obj||!obj.options){ -return false; -} -var id=obj.options[obj.selectedIndex].value; -var _ec=rcube_find_object("_message"); -var _ed=_ec?_ec.value:""; -var _ee=(rcube_find_object("_is_html").value=="1"); -var sig,p; -if(!this.env.identity){ -this.env.identity=id; -} -if(!_ee){ -if(this.env.identity&&this.env.signatures&&this.env.signatures[this.env.identity]){ -if(this.env.signatures[this.env.identity]["is_html"]){ -sig=this.env.signatures[this.env.identity]["plain_text"]; -}else{ -sig=this.env.signatures[this.env.identity]["text"]; -} -if(sig.indexOf("-- ")!=0){ -sig="-- \n"+sig; -} -p=_ed.lastIndexOf(sig); -if(p>=0){ -_ed=_ed.substring(0,p-1)+_ed.substring(p+sig.length,_ed.length); -} -} -_ed=_ed.replace(/[\r\n]+$/,""); -if(this.env.signatures&&this.env.signatures[id]){ -sig=this.env.signatures[id]["text"]; -if(this.env.signatures[id]["is_html"]){ -sig=this.env.signatures[id]["plain_text"]; -} -if(sig.indexOf("-- ")!=0){ -sig="-- \n"+sig; -} -_ed+="\n\n"+sig; -} -}else{ -var _f1=tinyMCE.get("compose-body"); -if(this.env.signatures){ -var _f2=_f1.dom.get("_rc_sig"); -var _f3=""; -var _f4=true; -if(!_f2){ -if(bw.ie){ -_f1.getBody().appendChild(_f1.getDoc().createElement("br")); -} -_f2=_f1.getDoc().createElement("div"); -_f2.setAttribute("id","_rc_sig"); -_f1.getBody().appendChild(_f2); -} -if(this.env.signatures[id]){ -_f3=this.env.signatures[id]["text"]; -_f4=this.env.signatures[id]["is_html"]; -if(_f3){ -if(_f4&&this.env.signatures[id]["plain_text"].indexOf("-- ")!=0){ -_f3="

--

"+_f3; -}else{ -if(!_f4&&_f3.indexOf("-- ")!=0){ -_f3="-- \n"+_f3; -} -} -} -} -if(_f4){ -_f2.innerHTML=_f3; -}else{ -_f2.innerHTML="
"+_f3+"
"; -} -} -} -if(_ec){ -_ec.value=_ed; -} -this.env.identity=id; -return true; -}; -this.show_attachment_form=function(a){ -if(!this.gui_objects.uploadbox){ -return false; -} -var elm,_f7; -if(elm=this.gui_objects.uploadbox){ -if(a&&(_f7=this.gui_objects.attachmentlist)){ -var pos=rcube_get_object_pos(_f7); -var _f9=pos.x; -var top=pos.y+_f7.offsetHeight+10; -elm.style.top=top+"px"; -elm.style.left=_f9+"px"; -} -elm.style.visibility=a?"visible":"hidden"; -} -try{ -if(!a&&this.gui_objects.attachmentform!=this.gui_objects.messageform){ -this.gui_objects.attachmentform.reset(); -} -} -catch(e){ -} -return true; -}; -this.upload_file=function(_fb){ -if(!_fb){ -return false; -} -var _fc=false; -for(var n=0;n<_fb.elements.length;n++){ -if(_fb.elements[n].type=="file"&&_fb.elements[n].value){ -_fc=true; -break; -} -} -if(_fc){ -var ts=new Date().getTime(); -var _ff="rcmupload"+ts; -if(document.all){ -var html=""; -document.body.insertAdjacentHTML("BeforeEnd",html); -}else{ -var _101=document.createElement("IFRAME"); -_101.name=_ff; -_101.style.border="none"; -_101.style.width=0; -_101.style.height=0; -_101.style.visibility="hidden"; -document.body.appendChild(_101); -} -_fb.target=_ff; -_fb.action=this.env.comm_path+"&_action=upload"; -_fb.setAttribute("enctype","multipart/form-data"); -_fb.submit(); -} -this.gui_objects.attachmentform=_fb; -return true; -}; -this.add2attachment_list=function(name,_103){ -if(!this.gui_objects.attachmentlist){ -return false; -} -var li=document.createElement("LI"); -li.id=name; -li.innerHTML=_103; -this.gui_objects.attachmentlist.appendChild(li); -return true; -}; -this.remove_from_attachment_list=function(name){ -if(!this.gui_objects.attachmentlist){ -return false; -} -var list=this.gui_objects.attachmentlist.getElementsByTagName("li"); -for(i=0;i/g,">").replace(/##([^%]+)%%/g,"$1"); -li.onmouseover=function(){ -_1.ksearch_select(this); -}; -li.onmouseup=function(){ -_1.ksearch_click(this); -}; -li._rcm_id=i; -ul.appendChild(li); -} -ul.firstChild.setAttribute("id","rcmksearchSelected"); -this.set_classname(ul.firstChild,"selected",true); -this.ksearch_selected=0; -var pos=rcube_get_object_pos(this.ksearch_input); -this.ksearch_pane.move(pos.x,pos.y+this.ksearch_input.offsetHeight); -this.ksearch_pane.show(1); -}else{ -this.ksearch_hide(); -} -}; -this.ksearch_click=function(node){ -if(this.ksearch_input){ -this.ksearch_input.focus(); -} -this.insert_recipient(node._rcm_id); -this.ksearch_hide(); -}; -this.ksearch_blur=function(){ -if(this.ksearch_timer){ -clearTimeout(this.ksearch_timer); -} -this.ksearch_value=""; -this.ksearch_input=null; -this.ksearch_hide(); -}; -this.ksearch_hide=function(){ -this.ksearch_selected=null; -if(this.ksearch_pane){ -this.ksearch_pane.show(0); -} -}; -this.contactlist_keypress=function(list){ -if(list.key_pressed==list.DELETE_KEY){ -this.command("delete"); -} -}; -this.contactlist_select=function(list){ -if(this.preview_timer){ -clearTimeout(this.preview_timer); -} -var id,_12b,_1=this; -if(id=list.get_single_selection()){ -this.preview_timer=window.setTimeout(function(){ -_1.load_contact(id,"show"); -},200); -}else{ -if(this.env.contentframe){ -this.show_contentframe(false); -} -} -this.enable_command("compose",list.selection.length>0); -this.enable_command("edit",(id&&this.env.address_sources&&!this.env.address_sources[this.env.source].readonly)?true:false); -this.enable_command("delete",list.selection.length&&this.env.address_sources&&!this.env.address_sources[this.env.source].readonly); -return false; -}; -this.list_contacts=function(src,page){ -var _12e=""; -var _12f=window; -if(!src){ -src=this.env.source; -} -if(page&&this.current_page==page&&src==this.env.source){ -return false; -} -if(src!=this.env.source){ -page=1; -this.env.current_page=page; -this.reset_qsearch(); -} -this.select_folder(src,this.env.source); -this.env.source=src; -if(this.gui_objects.contactslist){ -this.list_contacts_remote(src,page); -return; -} -if(this.env.contentframe&&window.frames&&window.frames[this.env.contentframe]){ -_12f=window.frames[this.env.contentframe]; -_12e="&_framed=1"; -} -if(this.env.search_request){ -_12e+="&_search="+this.env.search_request; -} -this.set_busy(true,"loading"); -_12f.location.href=this.env.comm_path+(src?"&_source="+urlencode(src):"")+(page?"&_page="+page:"")+_12e; -}; -this.list_contacts_remote=function(src,page){ -this.contact_list.clear(true); -this.show_contentframe(false); -this.enable_command("delete","compose",false); -var url=(src?"_source="+urlencode(src):"")+(page?(src?"&":"")+"_page="+page:""); -this.env.source=src; -if(this.env.search_request){ -url+="&_search="+this.env.search_request; -} -this.set_busy(true,"loading"); -this.http_request("list",url,true); -}; -this.load_contact=function(cid,_134,_135){ -var _136=""; -var _137=window; -if(this.env.contentframe&&window.frames&&window.frames[this.env.contentframe]){ -_136="&_framed=1"; -_137=window.frames[this.env.contentframe]; -this.show_contentframe(true); -}else{ -if(_135){ -return false; -} -} -if(_134&&(cid||_134=="add")&&!this.drag_active){ -this.set_busy(true); -_137.location.href=this.env.comm_path+"&_action="+_134+"&_source="+urlencode(this.env.source)+"&_cid="+urlencode(cid)+_136; -} -return true; -}; -this.copy_contact=function(cid,to){ -if(!cid){ -cid=this.contact_list.get_selection().join(","); -} -if(to!=this.env.source&&cid&&this.env.address_sources[to]&&!this.env.address_sources[to].readonly){ -this.http_post("copy","_cid="+urlencode(cid)+"&_source="+urlencode(this.env.source)+"&_to="+urlencode(to)); -} -}; -this.delete_contacts=function(){ -var _13a=this.contact_list.get_selection(); -if(!(_13a.length||this.env.cid)||!confirm(this.get_label("deletecontactconfirm"))){ -return; -} -var _13b=new Array(); -var qs=""; -if(this.env.cid){ -_13b[_13b.length]=this.env.cid; -}else{ -var id; -for(var n=0;n<_13a.length;n++){ -id=_13a[n]; -_13b[_13b.length]=id; -this.contact_list.remove_row(id,(n==_13a.length-1)); -} -if(_13a.length==1){ -this.show_contentframe(false); -} -} -if(this.env.search_request){ -qs+="&_search="+this.env.search_request; -} -this.http_post("delete","_cid="+urlencode(_13b.join(","))+"&_source="+urlencode(this.env.source)+"&_from="+(this.env.action?this.env.action:"")+qs); -return true; -}; -this.update_contact_row=function(cid,_140){ -var row; -if(this.contact_list.rows[cid]&&(row=this.contact_list.rows[cid].obj)){ -for(var c=0;c<_140.length;c++){ -if(row.cells[c]){ -row.cells[c].innerHTML=_140[c]; -} -} -return true; -} -return false; -}; -this.init_subscription_list=function(){ -var p=this; -this.subscription_list=new rcube_list_widget(this.gui_objects.subscriptionlist,{multiselect:false,draggable:true,keyboard:false,toggleselect:true}); -this.subscription_list.addEventListener("select",function(o){ -p.subscription_select(o); -}); -this.subscription_list.addEventListener("dragstart",function(o){ -p.drag_active=true; -}); -this.subscription_list.addEventListener("dragend",function(o){ -p.subscription_move_folder(o); -}); -this.subscription_list.row_init=function(row){ -var _148=row.obj.getElementsByTagName("A"); -if(_148[0]){ -_148[0].onclick=function(){ -p.rename_folder(row.id); -return false; -}; -} -if(_148[1]){ -_148[1].onclick=function(){ -p.delete_folder(row.id); -return false; -}; -} -row.obj.onmouseover=function(){ -p.focus_subscription(row.id); -}; -row.obj.onmouseout=function(){ -p.unfocus_subscription(row.id); -}; -}; -this.subscription_list.init(); -}; -this.identity_select=function(list){ -var id; -if(id=list.get_single_selection()){ -this.load_identity(id,"edit-identity"); -} -}; -this.load_identity=function(id,_14c){ -if(_14c=="edit-identity"&&(!id||id==this.env.iid)){ -return false; -} -var _14d=""; -var _14e=window; -if(this.env.contentframe&&window.frames&&window.frames[this.env.contentframe]){ -_14d="&_framed=1"; -_14e=window.frames[this.env.contentframe]; -document.getElementById(this.env.contentframe).style.visibility="inherit"; -} -if(_14c&&(id||_14c=="add-identity")){ -this.set_busy(true); -_14e.location.href=this.env.comm_path+"&_action="+_14c+"&_iid="+id+_14d; -} -return true; -}; -this.delete_identity=function(id){ -var _150=this.identity_list.get_selection(); -if(!(_150.length||this.env.iid)){ -return; -} -if(!id){ -id=this.env.iid?this.env.iid:_150[0]; -} -this.goto_url("delete-identity","_iid="+id,true); -return true; -}; -this.focus_subscription=function(id){ -var row,_153; -var reg=RegExp("["+RegExp.escape(this.env.delimiter)+"]?[^"+RegExp.escape(this.env.delimiter)+"]+$"); -if(this.drag_active&&this.env.folder&&(row=document.getElementById(id))){ -if(this.env.subscriptionrows[id]&&(_153=this.env.subscriptionrows[id][0])){ -if(this.check_droptarget(_153)&&!this.env.subscriptionrows[this.get_folder_row_id(this.env.folder)][2]&&(_153!=this.env.folder.replace(reg,""))&&(!_153.match(new RegExp("^"+RegExp.escape(this.env.folder+this.env.delimiter))))){ -this.set_env("dstfolder",_153); -this.set_classname(row,"droptarget",true); -} -}else{ -if(this.env.folder.match(new RegExp(RegExp.escape(this.env.delimiter)))){ -this.set_env("dstfolder",this.env.delimiter); -this.set_classname(this.subscription_list.frame,"droptarget",true); -} -} -} -}; -this.unfocus_subscription=function(id){ -var row; -this.set_env("dstfolder",null); -if(this.env.subscriptionrows[id]&&(row=document.getElementById(id))){ -this.set_classname(row,"droptarget",false); -}else{ -this.set_classname(this.subscription_list.frame,"droptarget",false); -} -}; -this.subscription_select=function(list){ -var id,_159; -if((id=list.get_single_selection())&&this.env.subscriptionrows["rcmrow"+id]&&(_159=this.env.subscriptionrows["rcmrow"+id][0])){ -this.set_env("folder",_159); -}else{ -this.set_env("folder",null); -} -if(this.gui_objects.createfolderhint){ -this.gui_objects.createfolderhint.innerHTML=this.env.folder?this.get_label("addsubfolderhint"):""; -} -}; -this.subscription_move_folder=function(list){ -var reg=RegExp("["+RegExp.escape(this.env.delimiter)+"]?[^"+RegExp.escape(this.env.delimiter)+"]+$"); -if(this.env.folder&&this.env.dstfolder&&(this.env.dstfolder!=this.env.folder)&&(this.env.dstfolder!=this.env.folder.replace(reg,""))){ -var reg=new RegExp("[^"+RegExp.escape(this.env.delimiter)+"]*["+RegExp.escape(this.env.delimiter)+"]","g"); -var _15c=this.env.folder.replace(reg,""); -var _15d=this.env.dstfolder==this.env.delimiter?_15c:this.env.dstfolder+this.env.delimiter+_15c; -this.set_busy(true,"foldermoving"); -this.http_post("rename-folder","_folder_oldname="+urlencode(this.env.folder)+"&_folder_newname="+urlencode(_15d),true); -} -this.drag_active=false; -this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder)); -}; -this.create_folder=function(name){ -if(this.edit_folder){ -this.reset_folder_rename(); -} -var form; -if((form=this.gui_objects.editform)&&form.elements["_folder_name"]){ -name=form.elements["_folder_name"].value; -if(name.indexOf(this.env.delimiter)>=0){ -alert(this.get_label("forbiddencharacter")+" ("+this.env.delimiter+")"); -return false; -} -if(this.env.folder&&name!=""){ -name=this.env.folder+this.env.delimiter+name; -} -this.set_busy(true,"foldercreating"); -this.http_post("create-folder","_name="+urlencode(name),true); -}else{ -if(form.elements["_folder_name"]){ -form.elements["_folder_name"].focus(); -} -} -}; -this.rename_folder=function(id){ -var temp,row,form; -if(temp=this.edit_folder){ -this.reset_folder_rename(); -if(temp==id){ -return; -} -} -if(id&&this.env.subscriptionrows[id]&&(row=document.getElementById(id))){ -var reg=new RegExp(".*["+RegExp.escape(this.env.delimiter)+"]"); -this.name_input=document.createElement("INPUT"); -this.name_input.value=this.env.subscriptionrows[id][0].replace(reg,""); -this.name_input.style.width="100%"; -reg=new RegExp("["+RegExp.escape(this.env.delimiter)+"]?[^"+RegExp.escape(this.env.delimiter)+"]+$"); -this.name_input.__parent=this.env.subscriptionrows[id][0].replace(reg,""); -this.name_input.onkeypress=function(e){ -rcmail.name_input_keypress(e); -}; -row.cells[0].replaceChild(this.name_input,row.cells[0].firstChild); -this.edit_folder=id; -this.name_input.select(); -if(form=this.gui_objects.editform){ -form.onsubmit=function(){ -return false; -}; -} -} -}; -this.reset_folder_rename=function(){ -var cell=this.name_input?this.name_input.parentNode:null; -if(cell&&this.edit_folder&&this.env.subscriptionrows[this.edit_folder]){ -cell.innerHTML=this.env.subscriptionrows[this.edit_folder][1]; -} -this.edit_folder=null; -}; -this.name_input_keypress=function(e){ -var key=rcube_event.get_keycode(e); -if(key==13){ -var _169=this.name_input?this.name_input.value:null; -if(this.edit_folder&&_169){ -if(_169.indexOf(this.env.delimiter)>=0){ -alert(this.get_label("forbiddencharacter")+" ("+this.env.delimiter+")"); -return false; -} -if(this.name_input.__parent){ -_169=this.name_input.__parent+this.env.delimiter+_169; -} -this.set_busy(true,"folderrenaming"); -this.http_post("rename-folder","_folder_oldname="+urlencode(this.env.subscriptionrows[this.edit_folder][0])+"&_folder_newname="+urlencode(_169),true); -} -}else{ -if(key==27){ -this.reset_folder_rename(); -} -} -}; -this.delete_folder=function(id){ -var _16b=this.env.subscriptionrows[id][0]; -if(this.edit_folder){ -this.reset_folder_rename(); -} -if(_16b&&confirm(this.get_label("deletefolderconfirm"))){ -this.set_busy(true,"folderdeleting"); -this.http_post("delete-folder","_mboxes="+urlencode(_16b),true); -this.set_env("folder",null); -if(this.gui_objects.createfolderhint){ -this.gui_objects.createfolderhint.innerHTML=""; -} -} -}; -this.add_folder_row=function(name,_16d,_16e,_16f){ -if(!this.gui_objects.subscriptionlist){ -return false; -} -for(var _170 in this.env.subscriptionrows){ -if(this.env.subscriptionrows[_170]!=null&&!this.env.subscriptionrows[_170][2]){ -break; -} -} -var _171,form; -var _173=this.gui_objects.subscriptionlist.tBodies[0]; -var id="rcmrow"+(_173.childNodes.length+1); -var _175=this.subscription_list.get_single_selection(); -if(_16e&&_16e.id){ -id=_16e.id; -_170=_16e.id; -} -if(!id||!(_171=document.getElementById(_170))){ -this.goto_url("folders"); -}else{ -var row=this.clone_table_row(_171); -row.id=id; -if(_16f&&(_16f=this.get_folder_row_id(_16f))){ -_173.insertBefore(row,document.getElementById(_16f)); -}else{ -_173.appendChild(row); -} -if(_16e){ -_173.removeChild(_16e); -} -} -this.env.subscriptionrows[row.id]=[name,_16d,0]; -row.cells[0].innerHTML=_16d; -if(!_16e){ -row.cells[1].innerHTML="*"; -} -if(!_16e&&row.cells[2]&&row.cells[2].firstChild.tagName=="INPUT"){ -row.cells[2].firstChild.value=name; -row.cells[2].firstChild.checked=true; -} -if(!_16e&&(form=this.gui_objects.editform)){ -if(form.elements["_folder_oldname"]){ -form.elements["_folder_oldname"].options[form.elements["_folder_oldname"].options.length]=new Option(name,name); -} -if(form.elements["_folder_name"]){ -form.elements["_folder_name"].value=""; -} -} -this.init_subscription_list(); -if(_175&&document.getElementById("rcmrow"+_175)){ -this.subscription_list.select_row(_175); -} -if(document.getElementById(id).scrollIntoView){ -document.getElementById(id).scrollIntoView(); -} -}; -this.replace_folder_row=function(_177,_178,_179,_17a){ -var id=this.get_folder_row_id(_177); -var row=document.getElementById(id); -this.add_folder_row(_178,_179,row,_17a); -var form,elm; -if((form=this.gui_objects.editform)&&(elm=form.elements["_folder_oldname"])){ -for(var i=0;ithis.env.current_page)); -this.enable_command("lastpage",(this.env.pagecount>this.env.current_page)); -this.enable_command("previouspage",(this.env.current_page>1)); -this.enable_command("firstpage",(this.env.current_page>1)); -}; -this.set_button=function(_18e,_18f){ -var _190=this.buttons[_18e]; -var _191,obj; -if(!_190||!_190.length){ -return false; -} -for(var n=0;n<_190.length;n++){ -_191=_190[n]; -obj=document.getElementById(_191.id); -if(obj&&_191.type=="image"&&!_191.status){ -_191.pas=obj._original_src?obj._original_src:obj.src; -if(obj.runtimeStyle&&obj.runtimeStyle.filter&&obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/)){ -_191.pas=RegExp.$1; -} -}else{ -if(obj&&!_191.status){ -_191.pas=String(obj.className); -} -} -if(obj&&_191.type=="image"&&_191[_18f]){ -_191.status=_18f; -obj.src=_191[_18f]; -}else{ -if(obj&&typeof (_191[_18f])!="undefined"){ -_191.status=_18f; -obj.className=_191[_18f]; -} -} -if(obj&&_191.type=="input"){ -_191.status=_18f; -obj.disabled=!_18f; -} -} -}; -this.set_alttext=function(_194,_195){ -if(!this.buttons[_194]||!this.buttons[_194].length){ -return; -} -var _196,obj,link; -for(var n=0;n"+cont+"
"; -} -var _1b5=this; -this.gui_objects.message.innerHTML=cont; -this.gui_objects.message.style.display="block"; -if(type!="loading"){ -this.gui_objects.message.onmousedown=function(){ -_1b5.hide_message(); -return true; -}; -} -if(!hold){ -this.message_timer=window.setTimeout(function(){ -_1.hide_message(); -},this.message_time); -} -}; -this.hide_message=function(){ -if(this.gui_objects.message){ -this.gui_objects.message.style.display="none"; -this.gui_objects.message.onmousedown=null; -} -}; -this.select_folder=function(name,old){ -if(this.gui_objects.folderlist){ -var _1b8,_1b9; -if((_1b8=this.get_folder_li(old))){ -this.set_classname(_1b8,"selected",false); -this.set_classname(_1b8,"unfocused",false); -} -if((_1b9=this.get_folder_li(name))){ -this.set_classname(_1b9,"unfocused",false); -this.set_classname(_1b9,"selected",true); -} -} -}; -this.get_folder_li=function(name){ -if(this.gui_objects.folderlist){ -name=String(name).replace(this.identifier_expr,""); -return document.getElementById("rcmli"+name); -} -return null; -}; -this.set_message_coltypes=function(_1bb){ -this.coltypes=_1bb; -var cell,col; -var _1be=this.gui_objects.messagelist?this.gui_objects.messagelist.tHead:null; -for(var n=0;_1be&&n":""; -row.appendChild(col); -for(var n=0;n"; -}else{ -if(!_1c2.flagged&&this.env.unflaggedicon){ -col.innerHTML="\"\""; -} -} -}else{ -if(c=="attachment"){ -col.innerHTML=_1c3&&this.env.attachmenticon?"\"\"":" "; -}else{ -col.innerHTML=cols[c]; -} -} -row.appendChild(col); -} -this.message_list.insert_row(row,_1c4); -if(_1c4&&this.env.pagesize&&this.message_list.rowcount>this.env.pagesize){ -var uid=this.message_list.get_last_row(); -this.message_list.remove_row(uid); -this.message_list.clear_selection(uid); -} -}; -this.set_rowcount=function(text){ -if(this.gui_objects.countdisplay){ -this.gui_objects.countdisplay.innerHTML=text; -} -this.set_page_buttons(); -}; -this.set_mailboxname=function(_1ce){ -if(this.gui_objects.mailboxname&&_1ce){ -this.gui_objects.mailboxname.innerHTML=_1ce; -} -}; -this.set_quota=function(_1cf){ -if(this.gui_objects.quotadisplay&&_1cf){ -this.gui_objects.quotadisplay.innerHTML=_1cf; -} -}; -this.set_unread_count=function(mbox,_1d1,_1d2){ -if(!this.gui_objects.mailboxlist){ -return false; -} -this.env.unread_counts[mbox]=_1d1; -this.set_unread_count_display(mbox,_1d2); -}; -this.set_unread_count_display=function(mbox,_1d4){ -var reg,_1d6,item,_1d8,_1d9,div; -if(item=this.get_folder_li(mbox)){ -_1d8=this.env.unread_counts[mbox]?this.env.unread_counts[mbox]:0; -_1d6=item.getElementsByTagName("a")[0]; -reg=/\s+\([0-9]+\)$/i; -_1d9=0; -if((div=item.getElementsByTagName("div")[0])&&div.className.match(/collapsed/)){ -for(var k in this.env.unread_counts){ -if(k.indexOf(mbox+this.env.delimiter)==0){ -_1d9+=this.env.unread_counts[k]; -} -} -} -if(_1d8&&_1d6.innerHTML.match(reg)){ -_1d6.innerHTML=_1d6.innerHTML.replace(reg," ("+_1d8+")"); -}else{ -if(_1d8){ -_1d6.innerHTML+=" ("+_1d8+")"; -}else{ -_1d6.innerHTML=_1d6.innerHTML.replace(reg,""); -} -} -reg=new RegExp(RegExp.escape(this.env.delimiter)+"[^"+RegExp.escape(this.env.delimiter)+"]+$"); -if(mbox.match(reg)){ -this.set_unread_count_display(mbox.replace(reg,""),false); -} -this.set_classname(item,"unread",(_1d8+_1d9)>0?true:false); -} -reg=/^\([0-9]+\)\s+/i; -if(_1d4&&document.title){ -var _1dc=String(document.title); -var _1dd=""; -if(_1d8&&_1dc.match(reg)){ -_1dd=_1dc.replace(reg,"("+_1d8+") "); -}else{ -if(_1d8){ -_1dd="("+_1d8+") "+_1dc; -}else{ -_1dd=_1dc.replace(reg,""); -} -} -this.set_pagetitle(_1dd); -} -}; -this.new_message_focus=function(){ -if(this.env.framed&&window.parent){ -window.parent.focus(); -}else{ -window.focus(); -} -}; -this.add_contact_row=function(cid,cols,_1e0){ -if(!this.gui_objects.contactslist||!this.gui_objects.contactslist.tBodies[0]){ -return false; -} -var _1e1=this.gui_objects.contactslist.tBodies[0]; -var _1e2=_1e1.rows.length; -var even=_1e2%2; -var row=document.createElement("TR"); -row.id="rcmrow"+cid; -row.className="contact "+(even?"even":"odd"); -if(this.contact_list.in_selection(cid)){ -row.className+=" selected"; -} -for(var c in cols){ -col=document.createElement("TD"); -col.className=String(c).toLowerCase(); -col.innerHTML=cols[c]; -row.appendChild(col); -} -this.contact_list.insert_row(row); -this.enable_command("export",(this.contact_list.rowcount>0)); -}; -this.toggle_prefer_html=function(_1e6){ -var _1e7; -if(_1e7=document.getElementById("rcmfd_addrbook_show_images")){ -_1e7.disabled=!_1e6.checked; -} -}; -this.set_headers=function(_1e8){ -if(this.gui_objects.all_headers_row&&this.gui_objects.all_headers_box&&_1e8){ -var box=this.gui_objects.all_headers_box; -box.innerHTML=_1e8; -box.style.display="block"; -if(this.env.framed&&parent.rcmail){ -parent.rcmail.set_busy(false); -}else{ -this.set_busy(false); -} -} -}; -this.load_headers=function(elem){ -if(!this.gui_objects.all_headers_row||!this.gui_objects.all_headers_box||!this.env.uid){ -return; -} -this.set_classname(elem,"show-headers",false); -this.set_classname(elem,"hide-headers",true); -this.gui_objects.all_headers_row.style.display=bw.ie?"block":"table-row"; -elem.onclick=function(){ -rcmail.hide_headers(elem); -}; -if(!this.gui_objects.all_headers_box.innerHTML){ -this.display_message(this.get_label("loading"),"loading",true); -this.http_post("headers","_uid="+this.env.uid); -} -}; -this.hide_headers=function(elem){ -if(!this.gui_objects.all_headers_row||!this.gui_objects.all_headers_box){ -return; -} -this.set_classname(elem,"hide-headers",false); -this.set_classname(elem,"show-headers",true); -this.gui_objects.all_headers_row.style.display="none"; -elem.onclick=function(){ -rcmail.load_headers(elem); -}; -}; -this.html2plain=function(_1ec,id){ -var _1ee=new rcube_http_request(); -var url=this.env.bin_path+"html2text.php"; -var _1f0=this; -this.set_busy(true,"converting"); -console.log("HTTP POST: "+url); -_1ee.onerror=function(o){ -_1f0.http_error(o); -}; -_1ee.oncomplete=function(o){ -_1f0.set_text_value(o,id); -}; -_1ee.POST(url,_1ec,"application/octet-stream"); -}; -this.set_text_value=function(_1f3,id){ -this.set_busy(false); -document.getElementById(id).value=_1f3.get_text(); -console.log(_1f3.get_text()); -}; -this.redirect=function(url,lock){ -if(lock||lock===null){ -this.set_busy(true); -} -if(this.env.framed&&window.parent){ -parent.location.href=url; -}else{ -location.href=url; -} -}; -this.goto_url=function(_1f7,_1f8,lock){ -var _1fa=_1f8?"&"+_1f8:""; -this.redirect(this.env.comm_path+"&_action="+_1f7+_1fa,lock); -}; -this.http_sockets=new Array(); -this.get_request_obj=function(){ -for(var n=0;n0)); -} -case "moveto": -if(this.env.action=="show"){ -this.command("list"); -}else{ -if(this.message_list){ -this.message_list.init(); -} -} -break; -case "purge": -case "expunge": -if(!this.env.messagecount&&this.task=="mail"){ -if(this.env.contentframe){ -this.show_contentframe(false); -} -this.enable_command("show","reply","reply-all","forward","moveto","delete","mark","viewsource","print","load-attachment","purge","expunge","select-all","select-none","sort",false); -} -break; -case "check-recent": -case "getunread": -case "list": -if(this.task=="mail"){ -if(this.message_list&&_20b.__action=="list"){ -this.msglist_select(this.message_list); -} -this.enable_command("show","expunge","select-all","select-none","sort",(this.env.messagecount>0)); -this.enable_command("purge",this.purge_mailbox_test()); -}else{ -if(this.task=="addressbook"){ -this.enable_command("export",(this.contact_list&&this.contact_list.rowcount>0)); -} -} -break; -} -_20b.reset(); -}; -this.http_error=function(_20f){ -if(_20f.__lock){ -this.set_busy(false); -} -_20f.reset(); -_20f.__lock=false; -this.display_message("Unknown Server Error!","error"); -}; -this.send_keep_alive=function(){ -var d=new Date(); -this.http_request("keep-alive","_t="+d.getTime()); -}; -this.check_for_recent=function(_211){ -if(this.busy){ -return; -} -if(_211){ -this.set_busy(true,"checkingmail"); -} -var _212="_t="+(new Date().getTime()); -if(this.gui_objects.messagelist){ -_212+="&_list=1"; -} -if(this.gui_objects.quotadisplay){ -_212+="&_quota=1"; -} -if(this.env.search_request){ -_212+="&_search="+this.env.search_request; -} -this.http_request("check-recent",_212,true); -}; -this.get_single_uid=function(){ -return this.env.uid?this.env.uid:(this.message_list?this.message_list.get_single_selection():null); -}; -this.get_single_cid=function(){ -return this.env.cid?this.env.cid:(this.contact_list?this.contact_list.get_single_selection():null); -}; -this.get_caret_pos=function(obj){ -if(typeof (obj.selectionEnd)!="undefined"){ -return obj.selectionEnd; -}else{ -if(document.selection&&document.selection.createRange){ -var _214=document.selection.createRange(); -if(_214.parentElement()!=obj){ -return 0; -} -var gm=_214.duplicate(); -if(obj.tagName=="TEXTAREA"){ -gm.moveToElementText(obj); -}else{ -gm.expand("textedit"); -} -gm.setEndPoint("EndToStart",_214); -var p=gm.text.length; -return p<=obj.value.length?p:-1; -}else{ -return obj.value.length; -} -} -}; -this.set_caret2start=function(obj){ -if(obj.createTextRange){ -var _218=obj.createTextRange(); -_218.collapse(true); -_218.select(); -}else{ -if(obj.setSelectionRange){ -obj.setSelectionRange(0,0); -} -} -obj.focus(); -}; -this.lock_form=function(form,lock){ -if(!form||!form.elements){ -return; -} -var type; -for(var n=0;n | + | Charles McNulty | + +-----------------------------------------------------------------------+ + | Requires: jquery.js, common.js, list.js | + +-----------------------------------------------------------------------+ + + $Id: app.js 2889 2009-08-29 18:41:17Z alec $ +*/ + + +function rcube_webmail() +{ + this.env = new Object(); + this.labels = new Object(); + this.buttons = new Object(); + this.buttons_sel = new Object(); + this.gui_objects = new Object(); + this.gui_containers = new Object(); + this.commands = new Object(); + this.command_handlers = new Object(); + this.onloads = new Array(); + + // create protected reference to myself + this.ref = 'rcmail'; + var ref = this; + + // webmail client settings + this.dblclick_time = 500; + this.message_time = 3000; + + this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); + + // mimetypes supported by the browser (default settings) + this.mimetypes = new Array('text/plain', 'text/html', 'text/xml', + 'image/jpeg', 'image/gif', 'image/png', + 'application/x-javascript', 'application/pdf', + 'application/x-shockwave-flash'); + + // default environment vars + this.env.keep_alive = 60; // seconds + this.env.request_timeout = 180; // seconds + this.env.draft_autosave = 0; // seconds + this.env.comm_path = './'; + this.env.bin_path = './bin/'; + this.env.blankpage = 'program/blank.gif'; + + // set jQuery ajax options + jQuery.ajaxSetup({ cache:false, + error:function(request, status, err){ ref.http_error(request, status, err); }, + beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-RoundCube-Request', ref.env.request_token); } + }); + + // set environment variable(s) + this.set_env = function(p, value) + { + if (p != null && typeof(p) == 'object' && !value) + for (var n in p) + this.env[n] = p[n]; + else + this.env[p] = value; + }; + + // add a localized label to the client environment + this.add_label = function(key, value) + { + this.labels[key] = value; + }; + + // add a button to the button list + this.register_button = function(command, id, type, act, sel, over) + { + if (!this.buttons[command]) + this.buttons[command] = new Array(); + + var button_prop = {id:id, type:type}; + if (act) button_prop.act = act; + if (sel) button_prop.sel = sel; + if (over) button_prop.over = over; + + this.buttons[command][this.buttons[command].length] = button_prop; + }; + + // register a specific gui object + this.gui_object = function(name, id) + { + this.gui_objects[name] = id; + }; + + // register a container object + this.gui_container = function(name, id) + { + this.gui_containers[name] = id; + }; + + // add a GUI element (html node) to a specified container + this.add_element = function(elm, container) + { + if (this.gui_containers[container] && this.gui_containers[container].jquery) + this.gui_containers[container].append(elm); + }; + + // register an external handler for a certain command + this.register_command = function(command, callback, enable) + { + this.command_handlers[command] = callback; + + if (enable) + this.enable_command(command, true); + }; + + // execute the given script on load + this.add_onload = function(f) + { + this.onloads[this.onloads.length] = f; + }; + + // initialize webmail client + this.init = function() + { + var p = this; + this.task = this.env.task; + + // check browser + if (!bw.dom || !bw.xmlhttp_test()) + { + this.goto_url('error', '_code=0x199'); + return; + } + + // find all registered gui containers + for (var n in this.gui_containers) + this.gui_containers[n] = $('#'+this.gui_containers[n]); + + // find all registered gui objects + for (var n in this.gui_objects) + this.gui_objects[n] = rcube_find_object(this.gui_objects[n]); + + // init registered buttons + this.init_buttons(); + + // tell parent window that this frame is loaded + if (this.env.framed && parent.rcmail && parent.rcmail.set_busy) + parent.rcmail.set_busy(false); + + // enable general commands + this.enable_command('logout', 'mail', 'addressbook', 'settings', true); + + if (this.env.permaurl) + this.enable_command('permaurl', true); + + switch (this.task) + { + case 'mail': + if (this.gui_objects.messagelist) + { + this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); + this.message_list.row_init = function(o){ p.init_message_row(o); }; + this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); }); + this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); }); + this.message_list.addEventListener('select', function(o){ p.msglist_select(o); }); + this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); }); + this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); }); + this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); }); + document.onmouseup = function(e){ return p.doc_mouse_up(e); }; + + this.message_list.init(); + this.enable_command('toggle_status', 'toggle_flag', true); + + if (this.gui_objects.mailcontframe) + this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); }; + else + this.message_list.focus(); + } + + if (this.env.coltypes) + this.set_message_coltypes(this.env.coltypes); + + // enable mail commands + this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true); + + if (this.env.search_text != null && document.getElementById('quicksearchbox') != null) + document.getElementById('quicksearchbox').value = this.env.search_text; + + if (this.env.action=='show' || this.env.action=='preview') + { + this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', + 'open', 'mark', 'edit', 'viewsource', 'download', 'print', 'load-attachment', 'load-headers', true); + + if (this.env.next_uid) + { + this.enable_command('nextmessage', true); + this.enable_command('lastmessage', true); + } + if (this.env.prev_uid) + { + this.enable_command('previousmessage', true); + this.enable_command('firstmessage', true); + } + + if (this.env.blockedobjects) + { + if (this.gui_objects.remoteobjectsmsg) + this.gui_objects.remoteobjectsmsg.style.display = 'block'; + this.enable_command('load-images', 'always-load', true); + } + } + + if (this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox) + this.set_alttext('delete', 'movemessagetotrash'); + + // make preview/message frame visible + if (this.env.action == 'preview' && this.env.framed && parent.rcmail) + { + this.enable_command('compose', 'add-contact', false); + parent.rcmail.show_contentframe(true); + } + + if (this.env.action=='compose') + { + this.enable_command('add-attachment', 'send-attachment', 'remove-attachment', 'send', true); + if (this.env.spellcheck) + { + this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); }; + this.set_spellcheck_state('ready'); + if ($("input[name='_is_html']").val() == '1') + this.display_spellcheck_controls(false); + } + if (this.env.drafts_mailbox) + this.enable_command('savedraft', true); + + document.onmouseup = function(e){ return p.doc_mouse_up(e); }; + + // init message compose form + this.init_messageform(); + } + + if (this.env.messagecount) + this.enable_command('select-all', 'select-none', 'expunge', true); + + if (this.purge_mailbox_test()) + this.enable_command('purge', true); + + this.set_page_buttons(); + + // show printing dialog + if (this.env.action=='print') + window.print(); + + // get unread count for each mailbox + if (this.gui_objects.mailboxlist) + { + this.env.unread_counts = {}; + this.gui_objects.folderlist = this.gui_objects.mailboxlist; + this.http_request('getunread', ''); + } + + // ask user to send MDN + if (this.env.mdn_request && this.env.uid) + { + var mdnurl = '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox); + if (confirm(this.get_label('mdnrequest'))) + this.http_post('sendmdn', mdnurl); + else + this.http_post('mark', mdnurl+'&_flag=mdnsent'); + } + + break; + + + case 'addressbook': + if (this.gui_objects.contactslist) + { + this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:true, keyboard:true}); + this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); }; + this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); }); + this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); }); + this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); }); + this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); }); + this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); }); + this.contact_list.init(); + + if (this.env.cid) + this.contact_list.highlight_row(this.env.cid); + + if (this.gui_objects.contactslist.parentNode) + { + this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); }; + document.onmouseup = function(e){ return p.doc_mouse_up(e); }; + } + else + this.contact_list.focus(); + + this.gui_objects.folderlist = this.gui_objects.contactslist; + } + + this.set_page_buttons(); + + if (this.env.address_sources && this.env.address_sources[this.env.source] && !this.env.address_sources[this.env.source].readonly) + this.enable_command('add', true); + + if (this.env.cid) + this.enable_command('show', 'edit', true); + + if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) + this.enable_command('save', true); + else + this.enable_command('search', 'reset-search', 'moveto', 'import', true); + + if (this.contact_list && this.contact_list.rowcount > 0) + this.enable_command('export', true); + + this.enable_command('list', true); + break; + + + case 'settings': + this.enable_command('preferences', 'identities', 'save', 'folders', true); + + if (this.env.action=='identities') { + this.enable_command('add', this.env.identities_level < 2); + } + else if (this.env.action=='edit-identity' || this.env.action=='add-identity') { + this.enable_command('add', this.env.identities_level < 2); + this.enable_command('save', 'delete', 'edit', true); + } + else if (this.env.action=='folders') + this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true); + + if (this.gui_objects.identitieslist) + { + this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false}); + this.identity_list.addEventListener('select', function(o){ p.identity_select(o); }); + this.identity_list.init(); + this.identity_list.focus(); + + if (this.env.iid) + this.identity_list.highlight_row(this.env.iid); + } + else if (this.gui_objects.sectionslist) + { + this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false}); + this.sections_list.addEventListener('select', function(o){ p.section_select(o); }); + this.sections_list.init(); + this.sections_list.focus(); + this.sections_list.select('general'); // open first section by default + } + else if (this.gui_objects.subscriptionlist) + this.init_subscription_list(); + + break; + + case 'login': + var input_user = $('#rcmloginuser'); + input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); }); + + if (input_user.val() == '') + input_user.focus(); + else + $('#rcmloginpwd').focus(); + + // detect client timezone + $('#rcmlogintz').val(new Date().getTimezoneOffset() / -60); + + this.enable_command('login', true); + break; + + default: + break; + } + + // flag object as complete + this.loaded = true; + + // show message + if (this.pending_message) + this.display_message(this.pending_message[0], this.pending_message[1]); + + // map implicit containers + if (this.gui_objects.folderlist) + this.gui_containers.foldertray = $(this.gui_objects.folderlist); + + // trigger init event hook + this.triggerEvent('init', { task:this.task, action:this.env.action }); + + // execute all foreign onload scripts + // @deprecated + for (var i=0; i= 0) + this.set_env('flagged_col', found+1); + } + + // set eventhandler to flag icon, if icon found + if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('td')[this.env.flagged_col].getElementsByTagName('img')[0])) + { + var p = this; + row.flagged_icon.id = 'flaggedicn_'+row.uid; + row.flagged_icon._row = row.obj; + row.flagged_icon.onmousedown = function(e) { p.command('toggle_flag', this); }; + } + + this.triggerEvent('insertrow', { uid:uid, row:row }); + }; + + // init message compose form: set focus and eventhandlers + this.init_messageform = function() + { + if (!this.gui_objects.messageform) + return false; + + //this.messageform = this.gui_objects.messageform; + var input_from = $("[name='_from']"); + var input_to = $("[name='_to']"); + var input_subject = $("input[name='_subject']"); + var input_message = $("[name='_message']").get(0); + + // init live search events + this.init_address_input_events(input_to); + this.init_address_input_events($("[name='_cc']")); + this.init_address_input_events($("[name='_bcc']")); + + // add signature according to selected identity + if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '' + && $("input[name='_is_html']").val() != '1') { // if we have HTML editor, signature is added in callback + this.change_identity(input_from[0]); + } + + if (input_to.val() == '') + input_to.focus(); + else if (input_subject.val() == '') + input_subject.focus(); + else if (input_message) + input_message.focus(); + + // get summary of all field values + this.compose_field_hash(true); + + // start the auto-save timer + this.auto_save_start(); + }; + + this.init_address_input_events = function(obj) + { + var handler = function(e){ return ref.ksearch_keypress(e,this); }; + obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler); + obj.attr('autocomplete', 'off'); + }; + + + /*********************************************************/ + /********* client command interface *********/ + /*********************************************************/ + + // execute a specific command on the web client + this.command = function(command, props, obj) + { + if (obj && obj.blur) + obj.blur(); + + if (this.busy) + return false; + + // command not supported or allowed + if (!this.commands[command]) + { + // pass command to parent window + if (this.env.framed && parent.rcmail && parent.rcmail.command) + parent.rcmail.command(command, props); + + return false; + } + + // check input before leaving compose step + if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings')) + { + if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) + return false; + } + + // process external commands + if (typeof this.command_handlers[command] == 'function') + { + var ret = this.command_handlers[command](props, obj); + return ret !== null ? ret : (obj ? false : true); + } + else if (typeof this.command_handlers[command] == 'string') + { + var ret = window[this.command_handlers[command]](props, obj); + return ret !== null ? ret : (obj ? false : true); + } + + // trigger plugin hook + var event_ret = this.triggerEvent('before'+command, props); + if (typeof event_ret != 'undefined') { + // abort if one the handlers returned false + if (event_ret === false) + return false; + else + props = event_ret; + } + + // process internal command + switch (command) + { + case 'login': + if (this.gui_objects.loginform) + this.gui_objects.loginform.submit(); + break; + + // commands to switch task + case 'mail': + case 'addressbook': + case 'settings': + case 'logout': + this.switch_task(command); + break; + + case 'permaurl': + if (obj && obj.href && obj.target) + return true; + else if (this.env.permaurl) + parent.location.href = this.env.permaurl; + break; + + case 'open': + var uid; + if (uid = this.get_single_uid()) + { + obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid; + return true; + } + break; + + // misc list commands + case 'list': + if (this.task=='mail') + { + if (this.env.search_request<0 || (props != '' && (this.env.search_request && props != this.env.mailbox))) + this.reset_qsearch(); + + this.list_mailbox(props); + + if (this.env.trash_mailbox) + this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage'); + } + else if (this.task=='addressbook') + { + if (this.env.search_request<0 || (this.env.search_request && props != this.env.source)) + this.reset_qsearch(); + + this.list_contacts(props); + this.enable_command('add', (this.env.address_sources && !this.env.address_sources[props].readonly)); + } + break; + + + case 'load-headers': + this.load_headers(obj); + break; + + + case 'sort': + var sort_order, sort_col = props; + + if (this.env.sort_col==sort_col) + sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC'; + else + sort_order = 'ASC'; + + // set table header class + $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase())); + $('#rcm'+sort_col).addClass('sorted'+sort_order); + + // save new sort properties + this.env.sort_col = sort_col; + this.env.sort_order = sort_order; + + // reload message list + this.list_mailbox('', '', sort_col+'_'+sort_order); + break; + + case 'nextpage': + this.list_page('next'); + break; + + case 'lastpage': + this.list_page('last'); + break; + + case 'previouspage': + this.list_page('prev'); + break; + + case 'firstpage': + this.list_page('first'); + break; + + case 'expunge': + if (this.env.messagecount) + this.expunge_mailbox(this.env.mailbox); + break; + + case 'purge': + case 'empty-mailbox': + if (this.env.messagecount) + this.purge_mailbox(this.env.mailbox); + break; + + + // common commands used in multiple tasks + case 'show': + if (this.task=='mail') + { + var uid = this.get_single_uid(); + if (uid && (!this.env.uid || uid != this.env.uid)) + { + if (this.env.mailbox == this.env.drafts_mailbox) + this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true); + else + this.show_message(uid); + } + } + else if (this.task=='addressbook') + { + var cid = props ? props : this.get_single_cid(); + if (cid && !(this.env.action=='show' && cid==this.env.cid)) + this.load_contact(cid, 'show'); + } + break; + + case 'add': + if (this.task=='addressbook') + this.load_contact(0, 'add'); + else if (this.task=='settings') + { + this.identity_list.clear_selection(); + this.load_identity(0, 'add-identity'); + } + break; + + case 'edit': + var cid; + if (this.task=='addressbook' && (cid = this.get_single_cid())) + this.load_contact(cid, 'edit'); + else if (this.task=='settings' && props) + this.load_identity(props, 'edit-identity'); + else if (this.task=='mail' && (cid = this.get_single_uid())) { + var url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid='; + this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true); + } + break; + + case 'save-identity': + case 'save': + if (this.gui_objects.editform) + { + var input_pagesize = $("input[name='_pagesize']"); + var input_name = $("input[name='_name']"); + var input_email = $("input[name='_email']"); + + // user prefs + if (input_pagesize.length && isNaN(parseInt(input_pagesize.val()))) + { + alert(this.get_label('nopagesizewarning')); + input_pagesize.focus(); + break; + } + // contacts/identities + else + { + if (input_name.length && input_name.val() == '') + { + alert(this.get_label('nonamewarning')); + input_name.focus(); + break; + } + else if (input_email.length && !rcube_check_email(input_email.val())) + { + alert(this.get_label('noemailwarning')); + input_email.focus(); + break; + } + } + + this.gui_objects.editform.submit(); + } + break; + + case 'delete': + // mail task + if (this.task=='mail') + this.delete_messages(); + // addressbook task + else if (this.task=='addressbook') + this.delete_contacts(); + // user settings task + else if (this.task=='settings') + this.delete_identity(); + break; + + + // mail task commands + case 'move': + case 'moveto': + if (this.task == 'mail') + this.move_messages(props); + else if (this.task == 'addressbook' && this.drag_active) + this.copy_contact(null, props); + break; + + case 'mark': + if (props) + this.mark_message(props); + break; + + case 'toggle_status': + if (props && !props._row) + break; + + var uid; + var flag = 'read'; + + if (props._row.uid) + { + uid = props._row.uid; + + // toggle read/unread + if (this.message_list.rows[uid].deleted) { + flag = 'undelete'; + } else if (!this.message_list.rows[uid].unread) + flag = 'unread'; + } + + this.mark_message(flag, uid); + break; + + case 'toggle_flag': + if (props && !props._row) + break; + + var uid; + var flag = 'flagged'; + + if (props._row.uid) + { + uid = props._row.uid; + // toggle flagged/unflagged + if (this.message_list.rows[uid].flagged) + flag = 'unflagged'; + } + this.mark_message(flag, uid); + break; + + case 'always-load': + if (this.env.uid && this.env.sender) { + this.add_contact(urlencode(this.env.sender)); + window.setTimeout(function(){ ref.command('load-images'); }, 300); + break; + } + + case 'load-images': + if (this.env.uid) + this.show_message(this.env.uid, true, this.env.action=='preview'); + break; + + case 'load-attachment': + var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part; + + // open attachment in frame if it's of a supported mimetype + if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0) + { + if (props.mimetype == 'text/html') + qstring += '&_safe=1'; + this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'); + if (this.attachment_win) + { + window.setTimeout(function(){ ref.attachment_win.focus(); }, 10); + break; + } + } + + this.goto_url('get', qstring+'&_download=1', false); + break; + + case 'select-all': + if (props == 'invert') + this.message_list.invert_selection(); + else + this.message_list.select_all(props); + break; + + case 'select-none': + this.message_list.clear_selection(); + break; + + case 'nextmessage': + if (this.env.next_uid) + this.show_message(this.env.next_uid, false, this.env.action=='preview'); + break; + + case 'lastmessage': + if (this.env.last_uid) + this.show_message(this.env.last_uid); + break; + + case 'previousmessage': + if (this.env.prev_uid) + this.show_message(this.env.prev_uid, false, this.env.action=='preview'); + break; + + case 'firstmessage': + if (this.env.first_uid) + this.show_message(this.env.first_uid); + break; + + case 'checkmail': + this.check_for_recent(true); + break; + + case 'compose': + var url = this.env.comm_path+'&_action=compose'; + + if (this.task=='mail') + { + url += '&_mbox='+urlencode(this.env.mailbox); + + if (this.env.mailbox==this.env.drafts_mailbox) + { + var uid; + if (uid = this.get_single_uid()) + url += '&_draft_uid='+uid; + } + else if (props) + url += '&_to='+urlencode(props); + } + // modify url if we're in addressbook + else if (this.task=='addressbook') + { + // switch to mail compose step directly + if (props && props.indexOf('@') > 0) + { + url = this.get_task_url('mail', url); + this.redirect(url + '&_to='+urlencode(props)); + break; + } + + // use contact_id passed as command parameter + var a_cids = new Array(); + if (props) + a_cids[a_cids.length] = props; + // get selected contacts + else if (this.contact_list) + { + var selection = this.contact_list.get_selection(); + for (var n=0; n 0) { + var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : ''); + if (this.env.search_request) + add_url += '_search='+this.env.search_request; + + this.goto_url('export', add_url); + } + break; + + // collapse/expand folder + case 'collapse-folder': + if (props) + this.collapse_folder(props); + break; + + // user settings commands + case 'preferences': + this.goto_url(''); + break; + + case 'identities': + this.goto_url('identities'); + break; + + case 'delete-identity': + this.delete_identity(); + + case 'folders': + this.goto_url('folders'); + break; + + case 'subscribe': + this.subscribe_folder(props); + break; + + case 'unsubscribe': + this.unsubscribe_folder(props); + break; + + case 'create-folder': + this.create_folder(props); + break; + + case 'rename-folder': + this.rename_folder(props); + break; + + case 'delete-folder': + this.delete_folder(props); + break; + + } + + this.triggerEvent('after'+command, props); + + return obj ? false : true; + }; + + // set command enabled or disabled + this.enable_command = function() + { + var args = arguments; + if(!args.length) return -1; + + var command; + var enable = args[args.length-1]; + + for(var n=0; n= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) { + if (this.env.last_folder_target) { + $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget'); + this.env.folder_coords[this.env.last_folder_target].on = 0; + this.env.last_folder_target = null; + } + return; + } + + // over the folders + for (var k in this.env.folder_coords) { + pos = this.env.folder_coords[k]; + if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2 + && this.check_droptarget(k)) { + + li = this.get_folder_li(k); + div = $(li.getElementsByTagName("div")[0]); + + // if the folder is collapsed, expand it after 1sec and restart the drag & drop process. + if (div.hasClass('collapsed')) { + if (this.folder_auto_timer) + window.clearTimeout(this.folder_auto_timer); + + this.folder_auto_expand = k; + this.folder_auto_timer = window.setTimeout(function() { + rcmail.command("collapse-folder", rcmail.folder_auto_expand); + rcmail.drag_start(null); + }, 1000); + } else if (this.folder_auto_timer) { + window.clearTimeout(this.folder_auto_timer); + this.folder_auto_timer = null; + this.folder_auto_expand = null; + } + + $(li).addClass('droptarget'); + this.env.last_folder_target = k; + this.env.folder_coords[k].on = 1; + } + else if (pos.on) { + $(this.get_folder_li(k)).removeClass('droptarget'); + this.env.folder_coords[k].on = 0; + } + } + } + }; + + this.collapse_folder = function(id) + { + var div; + if ((li = this.get_folder_li(id)) && + (div = $(li.getElementsByTagName("div")[0])) && + (div.hasClass('collapsed') || div.hasClass('expanded'))) + { + var ul = $(li.getElementsByTagName("ul")[0]); + if (div.hasClass('collapsed')) + { + ul.show(); + div.removeClass('collapsed').addClass('expanded'); + var reg = new RegExp('&'+urlencode(id)+'&'); + this.set_env('collapsed_folders', this.env.collapsed_folders.replace(reg, '')); + } + else + { + ul.hide(); + div.removeClass('expanded').addClass('collapsed'); + this.set_env('collapsed_folders', this.env.collapsed_folders+'&'+urlencode(id)+'&'); + + // select parent folder if one of its childs is currently selected + if (this.env.mailbox.indexOf(id + this.env.delimiter) == 0) + this.command('list', id); + } + + // Work around a bug in IE6 and IE7, see #1485309 + if ((bw.ie6 || bw.ie7) && + li.nextSibling && + (li.nextSibling.getElementsByTagName("ul").length>0) && + li.nextSibling.getElementsByTagName("ul")[0].style && + (li.nextSibling.getElementsByTagName("ul")[0].style.display!='none')) + { + li.nextSibling.getElementsByTagName("ul")[0].style.display = 'none'; + li.nextSibling.getElementsByTagName("ul")[0].style.display = ''; + } + + this.http_post('save-pref', '_name=collapsed_folders&_value='+urlencode(this.env.collapsed_folders)); + this.set_unread_count_display(id, false); + } + } + + this.click_on_list = function(e) + { + if (this.gui_objects.qsearchbox) + this.gui_objects.qsearchbox.blur(); + + if (this.message_list) + this.message_list.focus(); + else if (this.contact_list) + this.contact_list.focus(); + + return rcube_event.get_button(e) == 2 ? true : rcube_event.cancel(e); + }; + + this.msglist_select = function(list) + { + if (this.preview_timer) + clearTimeout(this.preview_timer); + + var selected = list.selection.length==1; + + // Hide certain command buttons when Drafts folder is selected + if (this.env.mailbox == this.env.drafts_mailbox) + { + this.enable_command('reply', 'reply-all', 'forward', false); + this.enable_command('show', 'print', 'open', 'edit', 'download', 'viewsource', selected); + this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false)); + } + else + { + this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', 'edit', 'open', 'download', 'viewsource', selected); + this.enable_command('delete', 'moveto', 'mark', (list.selection.length > 0 ? true : false)); + } + + // start timer for message preview (wait for double click) + if (selected && this.env.contentframe && !list.multi_selecting) + this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200); + else if (this.env.contentframe) + this.show_contentframe(false); + }; + + this.msglist_dbl_click = function(list) + { + if (this.preview_timer) + clearTimeout(this.preview_timer); + + var uid = list.get_single_selection(); + if (uid && this.env.mailbox == this.env.drafts_mailbox) + this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true); + else if (uid) + this.show_message(uid, false, false); + }; + + this.msglist_keypress = function(list) + { + if (list.key_pressed == list.ENTER_KEY) + this.command('show'); + else if (list.key_pressed == list.DELETE_KEY) + this.command('delete'); + else if (list.key_pressed == list.BACKSPACE_KEY) + this.command('delete'); + else + list.shiftkey = false; + }; + + this.msglist_get_preview = function() + { + var uid = this.get_single_uid(); + if (uid && this.env.contentframe && !this.drag_active) + this.show_message(uid, false, true); + else if (this.env.contentframe) + this.show_contentframe(false); + }; + + this.check_droptarget = function(id) + { + if (this.task == 'mail') + return (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual); + else if (this.task == 'addressbook') + return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly); + else if (this.task == 'settings') + return (id != this.env.folder); + }; + + + /*********************************************************/ + /********* (message) list functionality *********/ + /*********************************************************/ + + // when user doble-clicks on a row + this.show_message = function(id, safe, preview) + { + if (!id) return; + + var add_url = ''; + var action = preview ? 'preview': 'show'; + var target = window; + + if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) + { + target = window.frames[this.env.contentframe]; + add_url = '&_framed=1'; + } + + if (safe) + add_url = '&_safe=1'; + + // also send search request to get the right messages + if (this.env.search_request) + add_url += '&_search='+this.env.search_request; + + var url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox)+add_url; + if (action == 'preview' && String(target.location.href).indexOf(url) >= 0) + this.show_contentframe(true); + else + { + this.set_busy(true, 'loading'); + target.location.href = this.env.comm_path+url; + + // mark as read and change mbox unread counter + if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread) + { + this.set_message(id, 'unread', false); + if (this.env.unread_counts[this.env.mailbox]) + { + this.env.unread_counts[this.env.mailbox] -= 1; + this.set_unread_count(this.env.mailbox, this.env.unread_counts[this.env.mailbox], this.env.mailbox == 'INBOX'); + } + } + } + }; + + this.show_contentframe = function(show) + { + var frm; + if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) + { + if (!show && window.frames[this.env.contentframe]) + { + if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)<0) + window.frames[this.env.contentframe].location.href = this.env.blankpage; + } + else if (!bw.safari && !bw.konq) + frm[show ? 'show' : 'hide'](); + } + + if (!show && this.busy) + this.set_busy(false); + }; + + // list a specific page + this.list_page = function(page) + { + if (page=='next') + page = this.env.current_page+1; + if (page=='last') + page = this.env.pagecount; + if (page=='prev' && this.env.current_page>1) + page = this.env.current_page-1; + if (page=='first' && this.env.current_page>1) + page = 1; + + if (page > 0 && page <= this.env.pagecount) + { + this.env.current_page = page; + + if (this.task=='mail') + this.list_mailbox(this.env.mailbox, page); + else if (this.task=='addressbook') + this.list_contacts(this.env.source, page); + } + }; + + // list messages of a specific mailbox using filter + this.filter_mailbox = function(filter) + { + var search; + if (this.gui_objects.qsearchbox) + search = this.gui_objects.qsearchbox.value; + + this.message_list.clear(); + + // reset vars + this.env.current_page = 1; + this.set_busy(true, 'searching'); + this.http_request('search', '_filter='+filter + + (search ? '&_q='+urlencode(search) : '') + + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), true); + } + + + // list messages of a specific mailbox + this.list_mailbox = function(mbox, page, sort) + { + var add_url = ''; + var target = window; + + if (!mbox) + mbox = this.env.mailbox; + + // add sort to url if set + if (sort) + add_url += '&_sort=' + sort; + + // also send search request to get the right messages + if (this.env.search_request) + add_url += '&_search='+this.env.search_request; + + // set page=1 if changeing to another mailbox + if (!page) + { + page = 1; + this.env.current_page = page; + this.show_contentframe(false); + } + + if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort)) + add_url += '&_refresh=1'; + + // unselect selected messages + this.last_selected = 0; + if (this.message_list) + this.message_list.clear_selection(); + + this.select_folder(mbox, this.env.mailbox); + this.env.mailbox = mbox; + + // load message list remotely + if (this.gui_objects.messagelist) + { + this.list_mailbox_remote(mbox, page, add_url); + return; + } + + if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) + { + target = window.frames[this.env.contentframe]; + add_url += '&_framed=1'; + } + + // load message list to target frame/window + if (mbox) + { + this.set_busy(true, 'loading'); + target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+add_url; + } + }; + + // send remote request to load message list + this.list_mailbox_remote = function(mbox, page, add_url) + { + // clear message list first + this.message_list.clear(); + + // send request to server + var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : ''); + this.set_busy(true, 'loading'); + this.http_request('list', url+add_url, true); + }; + + this.expunge_mailbox = function(mbox) + { + var lock = false; + var add_url = ''; + + // lock interface if it's the active mailbox + if (mbox == this.env.mailbox) + { + lock = true; + this.set_busy(true, 'loading'); + add_url = '&_reload=1'; + } + + // send request to server + var url = '_mbox='+urlencode(mbox); + this.http_post('expunge', url+add_url, lock); + }; + + this.purge_mailbox = function(mbox) + { + var lock = false; + var add_url = ''; + + if (!confirm(this.get_label('purgefolderconfirm'))) + return false; + + // lock interface if it's the active mailbox + if (mbox == this.env.mailbox) + { + lock = true; + this.set_busy(true, 'loading'); + add_url = '&_reload=1'; + } + + // send request to server + var url = '_mbox='+urlencode(mbox); + this.http_post('purge', url+add_url, lock); + return true; + }; + + // test if purge command is allowed + this.purge_mailbox_test = function() + { + return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox + || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter)) + || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter)))); + }; + + // set message icon + this.set_message_icon = function(uid) + { + var icn_src; + var rows = this.message_list.rows; + + if (!rows[uid]) + return false; + + if (rows[uid].deleted && this.env.deletedicon) + icn_src = this.env.deletedicon; + else if (rows[uid].replied && this.env.repliedicon) + { + if (rows[uid].forwarded && this.env.forwardedrepliedicon) + icn_src = this.env.forwardedrepliedicon; + else + icn_src = this.env.repliedicon; + } + else if (rows[uid].forwarded && this.env.forwardedicon) + icn_src = this.env.forwardedicon; + else if (rows[uid].unread && this.env.unreadicon) + icn_src = this.env.unreadicon; + else if (this.env.messageicon) + icn_src = this.env.messageicon; + + if (icn_src && rows[uid].icon) + rows[uid].icon.src = icn_src; + + icn_src = ''; + + if (rows[uid].flagged && this.env.flaggedicon) + icn_src = this.env.flaggedicon; + else if (!rows[uid].flagged && this.env.unflaggedicon) + icn_src = this.env.unflaggedicon; + + if (rows[uid].flagged_icon && icn_src) + rows[uid].flagged_icon.src = icn_src; + } + + // set message status + this.set_message_status = function(uid, flag, status) + { + var rows = this.message_list.rows; + + if (!rows[uid]) return false; + + if (flag == 'unread') + rows[uid].unread = status; + else if(flag == 'deleted') + rows[uid].deleted = status; + else if (flag == 'replied') + rows[uid].replied = status; + else if (flag == 'forwarded') + rows[uid].forwarded = status; + else if (flag == 'flagged') + rows[uid].flagged = status; + + this.env.messages[uid] = rows[uid]; + } + + // set message row status, class and icon + this.set_message = function(uid, flag, status) + { + var rows = this.message_list.rows; + + if (!rows[uid]) return false; + + if (flag) + this.set_message_status(uid, flag, status); + + var rowobj = $(rows[uid].obj); + if (rows[uid].unread && rows[uid].classname.indexOf('unread')<0) + { + rows[uid].classname += ' unread'; + rowobj.addClass('unread'); + } + else if (!rows[uid].unread && rows[uid].classname.indexOf('unread')>=0) + { + rows[uid].classname = rows[uid].classname.replace(/\s*unread/, ''); + rowobj.removeClass('unread'); + } + + if (rows[uid].deleted && rows[uid].classname.indexOf('deleted')<0) + { + rows[uid].classname += ' deleted'; + rowobj.addClass('deleted'); + } + else if (!rows[uid].deleted && rows[uid].classname.indexOf('deleted')>=0) + { + rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, ''); + rowobj.removeClass('deleted'); + } + + if (rows[uid].flagged && rows[uid].classname.indexOf('flagged')<0) + { + rows[uid].classname += ' flagged'; + rowobj.addClass('flagged'); + } + else if (!rows[uid].flagged && rows[uid].classname.indexOf('flagged')>=0) + { + rows[uid].classname = rows[uid].classname.replace(/\s*flagged/, ''); + rowobj.removeClass('flagged'); + } + + this.set_message_icon(uid); + } + + // move selected messages to the specified mailbox + this.move_messages = function(mbox) + { + // exit if current or no mailbox specified or if selection is empty + if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))) + return; + + var lock = false; + var add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : ''); + + // show wait message + if (this.env.action=='show') + { + lock = true; + this.set_busy(true, 'movingmessage'); + } + else + this.show_contentframe(false); + + // Hide message command buttons until a message is selected + this.enable_command('reply', 'reply-all', 'forward', 'delete', 'mark', 'print', 'open', 'edit', 'viewsource', 'download', false); + + this._with_selected_messages('moveto', lock, add_url); + }; + + // delete selected messages from the current mailbox + this.delete_messages = function() + { + var selection = this.message_list ? this.message_list.get_selection() : new Array(); + + // exit if no mailbox specified or if selection is empty + if (!this.env.uid && !selection.length) + return; + + // if config is set to flag for deletion + if (this.env.flag_for_deletion) + this.mark_message('delete'); + // if there isn't a defined trash mailbox or we are in it + else if (!this.env.trash_mailbox || String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase()) + this.permanently_remove_messages(); + // if there is a trash mailbox defined and we're not currently in it + else { + // if shift was pressed delete it immediately + if (this.message_list && this.message_list.shiftkey) + { + if (confirm(this.get_label('deletemessagesconfirm'))) + this.permanently_remove_messages(); + } + else + this.move_messages(this.env.trash_mailbox); + } + }; + + // delete the selected messages permanently + this.permanently_remove_messages = function() + { + // exit if no mailbox specified or if selection is empty + if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)) + return; + + this.show_contentframe(false); + this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : '')); + }; + + // Send a specifc request with UIDs of all selected messages + // @private + this._with_selected_messages = function(action, lock, add_url, remove) + { + var a_uids = new Array(); + + if (this.env.uid) + a_uids[0] = this.env.uid; + else + { + var selection = this.message_list.get_selection(); + var rows = this.message_list.rows; + var id; + for (var n=0; n=0) + message = message.substring(0, p-1) + message.substring(p+sig.length, message.length); + } + + message = message.replace(/[\r\n]+$/, ''); + len = message.length; + + // add the new signature string + if (this.env.signatures && this.env.signatures[id]) + { + sig = this.env.signatures[id]['text']; + if (this.env.signatures[id]['is_html']) + { + sig = this.env.signatures[id]['plain_text']; + } + if (sig.indexOf('-- ')!=0) + sig = '-- \n'+sig; + message += '\n\n'+sig; + if (len) len += 1; + } + } + else + { + var editor = tinyMCE.get(this.env.composebody); + + if (this.env.signatures) + { + // Append the signature as a div within the body + var sigElem = editor.dom.get('_rc_sig'); + var newsig = ''; + var htmlsig = true; + + if (!sigElem) + { + // add empty line before signature on IE + if (bw.ie) + editor.getBody().appendChild(editor.getDoc().createElement('br')); + + sigElem = editor.getDoc().createElement('div'); + sigElem.setAttribute('id', '_rc_sig'); + editor.getBody().appendChild(sigElem); + } + + if (this.env.signatures[id]) + { + newsig = this.env.signatures[id]['text']; + htmlsig = this.env.signatures[id]['is_html']; + + if (newsig) { + if (htmlsig && this.env.signatures[id]['plain_text'].indexOf('-- ')!=0) + newsig = '

--

' + newsig; + else if (!htmlsig && newsig.indexOf('-- ')!=0) + newsig = '-- \n' + newsig; + } + } + + if (htmlsig) + sigElem.innerHTML = newsig; + else + sigElem.innerHTML = '
' + newsig + '
'; + } + } + + input_message.val(message); + + // move cursor before the signature + if (!is_html) + this.set_caret_pos(input_message.get(0), len); + + this.env.identity = id; + return true; + }; + + this.show_attachment_form = function(a) + { + if (!this.gui_objects.uploadbox) + return false; + + var elm, list; + if (elm = this.gui_objects.uploadbox) + { + if (a && (list = this.gui_objects.attachmentlist)) + { + var pos = $(list).offset(); + elm.style.top = (pos.top + list.offsetHeight + 10) + 'px'; + elm.style.left = pos.left + 'px'; + } + + elm.style.visibility = a ? 'visible' : 'hidden'; + } + + // clear upload form + try { + if (!a && this.gui_objects.attachmentform != this.gui_objects.messageform) + this.gui_objects.attachmentform.reset(); + } + catch(e){} // ignore errors + + return true; + }; + + // upload attachment file + this.upload_file = function(form) + { + if (!form) + return false; + + // get file input fields + var send = false; + for (var n=0; n