thomascube
2010-11-27 5fec6de4216883e625d7e166f862985d00c99d4a
Copy plugins to release branch

265 files added
25060 ■■■■■ changed files
plugins/additional_message_headers/additional_message_headers.php 43 ●●●●● patch | view | raw | blame | history
plugins/additional_message_headers/config.inc.php.dist 14 ●●●●● patch | view | raw | blame | history
plugins/additional_message_headers/package.xml 47 ●●●●● patch | view | raw | blame | history
plugins/archive/archive.js 36 ●●●●● patch | view | raw | blame | history
plugins/archive/archive.php 144 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/cs_CZ.inc 25 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/de_CH.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/de_DE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/en_US.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/es_AR.inc 10 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/es_ES.inc 10 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/et_EE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/fr_FR.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/ja_JP.inc 10 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/nl_NL.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/pl_PL.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/ru_RU.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/sv_SE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/localization/zh_TW.inc 8 ●●●●● patch | view | raw | blame | history
plugins/archive/package.xml 64 ●●●●● patch | view | raw | blame | history
plugins/archive/skins/default/archive_act.png patch | view | raw | blame | history
plugins/archive/skins/default/archive_pas.png patch | view | raw | blame | history
plugins/archive/skins/default/foldericon.png patch | view | raw | blame | history
plugins/autologon/autologon.php 45 ●●●●● patch | view | raw | blame | history
plugins/database_attachments/database_attachments.php 156 ●●●●● patch | view | raw | blame | history
plugins/debug_logger/debug_logger.php 146 ●●●●● patch | view | raw | blame | history
plugins/debug_logger/runlog/runlog.php 227 ●●●●● patch | view | raw | blame | history
plugins/emoticons/emoticons.php 80 ●●●●● patch | view | raw | blame | history
plugins/enigma/README 35 ●●●●● patch | view | raw | blame | history
plugins/enigma/config.inc.php 14 ●●●●● patch | view | raw | blame | history
plugins/enigma/enigma.js 206 ●●●●● patch | view | raw | blame | history
plugins/enigma/enigma.php 475 ●●●●● patch | view | raw | blame | history
plugins/enigma/home/.htaccess 2 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG.php 2542 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php 336 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/Engine.php 1758 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/Exceptions.php 473 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/Key.php 223 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/Signature.php 428 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/SubKey.php 649 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/UserId.php 373 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php 216 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver.php 106 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver_gnupg.php 305 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_engine.php 547 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_error.php 62 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_key.php 129 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_signature.php 34 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_subkey.php 57 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_ui.php 459 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_userid.php 31 ●●●●● patch | view | raw | blame | history
plugins/enigma/localization/en_US.inc 53 ●●●●● patch | view | raw | blame | history
plugins/enigma/localization/ja_JP.inc 55 ●●●●● patch | view | raw | blame | history
plugins/enigma/skins/default/enigma.css 182 ●●●●● patch | view | raw | blame | history
plugins/enigma/skins/default/enigma.png patch | view | raw | blame | history
plugins/enigma/skins/default/enigma_error.png patch | view | raw | blame | history
plugins/enigma/skins/default/key.png patch | view | raw | blame | history
plugins/enigma/skins/default/key_add.png patch | view | raw | blame | history
plugins/enigma/skins/default/keys_toolbar.png patch | view | raw | blame | history
plugins/enigma/skins/default/templates/keyimport.html 20 ●●●●● patch | view | raw | blame | history
plugins/enigma/skins/default/templates/keyinfo.html 17 ●●●●● patch | view | raw | blame | history
plugins/enigma/skins/default/templates/keys.html 76 ●●●●● patch | view | raw | blame | history
plugins/example_addressbook/example_addressbook.php 49 ●●●●● patch | view | raw | blame | history
plugins/example_addressbook/example_addressbook_backend.php 109 ●●●●● patch | view | raw | blame | history
plugins/filesystem_attachments/filesystem_attachments.php 155 ●●●●● patch | view | raw | blame | history
plugins/help/config.inc.php.dist 5 ●●●●● patch | view | raw | blame | history
plugins/help/content/about.html 39 ●●●●● patch | view | raw | blame | history
plugins/help/content/license.html 387 ●●●●● patch | view | raw | blame | history
plugins/help/help.php 107 ●●●●● patch | view | raw | blame | history
plugins/help/localization/cs_CZ.inc 25 ●●●●● patch | view | raw | blame | history
plugins/help/localization/da_DK.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/de_DE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/en_GB.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/en_US.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/es_ES.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/et_EE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/hu_HU.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/ja_JP.inc 10 ●●●●● patch | view | raw | blame | history
plugins/help/localization/pl_PL.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/ru_RU.inc 23 ●●●●● patch | view | raw | blame | history
plugins/help/localization/sv_SE.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/localization/zh_TW.inc 8 ●●●●● patch | view | raw | blame | history
plugins/help/skins/default/help.css 29 ●●●●● patch | view | raw | blame | history
plugins/help/skins/default/help.gif patch | view | raw | blame | history
plugins/help/skins/default/templates/help.html 37 ●●●●● patch | view | raw | blame | history
plugins/http_authentication/http_authentication.php 44 ●●●●● patch | view | raw | blame | history
plugins/kolab_addressbook/kolab_addressbook.php 140 ●●●●● patch | view | raw | blame | history
plugins/kolab_addressbook/localization/en_US.inc 7 ●●●●● patch | view | raw | blame | history
plugins/kolab_addressbook/rcube_kolab_contacts.php 829 ●●●●● patch | view | raw | blame | history
plugins/kolab_core/README.txt 32 ●●●●● patch | view | raw | blame | history
plugins/kolab_core/config.inc.php.dist 8 ●●●●● patch | view | raw | blame | history
plugins/kolab_core/kolab_core.php 30 ●●●●● patch | view | raw | blame | history
plugins/kolab_core/rcube_kolab.php 109 ●●●●● patch | view | raw | blame | history
plugins/managesieve/Changelog 160 ●●●●● patch | view | raw | blame | history
plugins/managesieve/config.inc.php.dist 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/Net/Sieve.php 1211 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/rcube_sieve.php 990 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/bg_BG.inc 50 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/cs_CZ.inc 61 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/de_CH.inc 52 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/de_DE.inc 55 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/el_GR.inc 56 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/en_GB.inc 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/en_US.inc 92 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/es_AR.inc 81 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/es_ES.inc 81 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/et_EE.inc 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/fi_FI.inc 68 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/fr_FR.inc 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/hu_HU.inc 54 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/it_IT.inc 79 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/ja_JP.inc 82 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/nb_NO.inc 54 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/nl_NL.inc 49 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/pl_PL.inc 93 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/pt_BR.inc 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/pt_PT.inc 80 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/ru_RU.inc 74 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/sk_SK.inc 85 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/sl_SI.inc 53 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/sv_SE.inc 54 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/uk_UA.inc 76 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/zh_CN.inc 49 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/zh_TW.inc 78 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.js 511 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.php 1140 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/managesieve.css 296 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/managesieve_toolbar.png patch | view | raw | blame | history
plugins/managesieve/skins/default/templates/filteredit.html 117 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/templates/managesieve.html 54 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/templates/setedit.html 24 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/cs_CZ.inc 24 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/da_DK.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/de_DE.inc 6 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/en_US.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/es_AR.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/es_ES.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/et_EE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/ja_JP.inc 9 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/pl_PL.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/ru_RU.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/sk_SK.inc 15 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/sv_SE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/localization/zh_TW.inc 7 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/markasjunk.js 28 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/markasjunk.php 56 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/package.xml 66 ●●●●● patch | view | raw | blame | history
plugins/markasjunk/skins/default/junk_act.png patch | view | raw | blame | history
plugins/markasjunk/skins/default/junk_pas.png patch | view | raw | blame | history
plugins/new_user_dialog/localization/cs_CZ.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/de_CH.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/de_DE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/en_US.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/es_ES.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/et_EE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/it_IT.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/ja_JP.inc 9 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/nl_NL.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/pl_PL.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/pt_BR.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/pt_PT.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/ru_RU.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/sl_SI.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/sv_SE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/localization/zh_TW.inc 7 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/new_user_dialog.php 128 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/newuserdialog.css 59 ●●●●● patch | view | raw | blame | history
plugins/new_user_dialog/package.xml 105 ●●●●● patch | view | raw | blame | history
plugins/new_user_identity/new_user_identity.php 50 ●●●●● patch | view | raw | blame | history
plugins/password/README 262 ●●●●● patch | view | raw | blame | history
plugins/password/config.inc.php.dist 272 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/chgsaslpasswd.c 29 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/chgvirtualminpasswd.c 28 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/chpass-wrapper.py 32 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/chpasswd.php 36 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/cpanel.php 121 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/directadmin.php 483 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/hmail.php 61 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/ldap.php 280 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/ldap_simple.php 233 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/pam.php 41 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/poppassd.php 64 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/sasl.php 44 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/sql.php 145 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/virtualmin.php 40 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/vpopmaild.php 51 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/ximss.php 81 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/xmail.php 101 ●●●●● patch | view | raw | blame | history
plugins/password/localization/az_AZ.inc 24 ●●●●● patch | view | raw | blame | history
plugins/password/localization/bg_BG.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/ca_ES.inc 20 ●●●●● patch | view | raw | blame | history
plugins/password/localization/cs_CZ.inc 30 ●●●●● patch | view | raw | blame | history
plugins/password/localization/da_DK.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/de_CH.inc 19 ●●●●● patch | view | raw | blame | history
plugins/password/localization/de_DE.inc 19 ●●●●● patch | view | raw | blame | history
plugins/password/localization/en_US.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/es_AR.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/es_ES.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/et_EE.inc 17 ●●●●● patch | view | raw | blame | history
plugins/password/localization/fi_FI.inc 22 ●●●●● patch | view | raw | blame | history
plugins/password/localization/fr_FR.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/hu_HU.inc 17 ●●●●● patch | view | raw | blame | history
plugins/password/localization/it_IT.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/ja_JP.inc 23 ●●●●● patch | view | raw | blame | history
plugins/password/localization/lt_LT.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/lv_LV.inc 20 ●●●●● patch | view | raw | blame | history
plugins/password/localization/nl_NL.inc 17 ●●●●● patch | view | raw | blame | history
plugins/password/localization/pl_PL.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/pt_BR.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/pt_PT.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/ru_RU.inc 35 ●●●●● patch | view | raw | blame | history
plugins/password/localization/sl_SI.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/sv_SE.inc 18 ●●●●● patch | view | raw | blame | history
plugins/password/localization/tr_TR.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/localization/zh_TW.inc 21 ●●●●● patch | view | raw | blame | history
plugins/password/package.xml 238 ●●●●● patch | view | raw | blame | history
plugins/password/password.js 39 ●●●●● patch | view | raw | blame | history
plugins/password/password.php 257 ●●●●● patch | view | raw | blame | history
plugins/show_additional_headers/show_additional_headers.php 52 ●●●●● patch | view | raw | blame | history
plugins/squirrelmail_usercopy/config.inc.php.dist 25 ●●●●● patch | view | raw | blame | history
plugins/squirrelmail_usercopy/squirrelmail_usercopy.php 190 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/cs_CZ.inc 23 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/de_CH.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/de_DE.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/en_US.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/es_ES.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/et_EE.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/ja_JP.inc 8 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/pl_PL.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/ru_RU.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/sv_SE.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/localization/zh_TW.inc 6 ●●●●● patch | view | raw | blame | history
plugins/subscriptions_option/subscriptions_option.php 92 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/cs_CZ.inc 27 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/da_DK.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/de_CH.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/en_US.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/es_ES.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/et_EE.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/ja_JP.inc 11 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/pl_PL.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/pt_PT.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/ru_RU.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/sv_SE.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/localization/zh_TW.inc 9 ●●●●● patch | view | raw | blame | history
plugins/userinfo/userinfo.js 16 ●●●●● patch | view | raw | blame | history
plugins/userinfo/userinfo.php 55 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/cs_CZ.inc 21 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/de_CH.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/de_DE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/en_US.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/es_ES.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/et_EE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/ja_JP.inc 9 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/pl_PL.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/ru_RU.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/sv_SE.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/localization/zh_TW.inc 7 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/package.xml 98 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/skins/default/vcard.png patch | view | raw | blame | history
plugins/vcard_attachments/skins/default/vcard_add_contact.png patch | view | raw | blame | history
plugins/vcard_attachments/vcard_attachments.php 184 ●●●●● patch | view | raw | blame | history
plugins/vcard_attachments/vcardattach.js 23 ●●●●● patch | view | raw | blame | history
plugins/virtuser_file/virtuser_file.php 106 ●●●●● patch | view | raw | blame | history
plugins/virtuser_query/virtuser_query.php 117 ●●●●● patch | view | raw | blame | history
plugins/additional_message_headers/additional_message_headers.php
New file
@@ -0,0 +1,43 @@
<?php
/**
 * Additional Message Headers
 *
 * Very simple plugin which will add additional headers
 * to or remove them from outgoing messages.
 *
 * Enable the plugin in config/main.inc.php and add your desired headers:
 * $rcmail_config['additional_message_headers'] = array('User-Agent');
 *
 * @version @package_version@
 * @author Ziba Scott
 * @website http://roundcube.net
 */
class additional_message_headers extends rcube_plugin
{
    public $task = 'mail';
    function init()
    {
        $this->add_hook('message_outgoing_headers', array($this, 'message_headers'));
    }
    function message_headers($args)
    {
    $this->load_config();
        // additional email headers
        $additional_headers = rcmail::get_instance()->config->get('additional_message_headers',array());
        foreach($additional_headers as $header=>$value){
            if (null === $value) {
                unset($args['headers'][$header]);
            } else {
                $args['headers'][$header] = $value;
            }
        }
        return $args;
    }
}
?>
plugins/additional_message_headers/config.inc.php.dist
New file
@@ -0,0 +1,14 @@
<?php
// $rcmail_config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT'];
// $rcmail_config['additional_message_headers']['X-Originating-IP'] = $_SERVER['REMOTE_ADDR'];
// $rcmail_config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR'];
// if( isset( $_SERVER['MACHINE_NAME'] )) {
//     $rcmail_config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')';
// }
// To remove (e.g. X-Sender) message header use null value
// $rcmail_config['additional_message_headers']['X-Sender'] = null;
?>
plugins/additional_message_headers/package.xml
New file
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<package packagerversion="1.9.0" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
    http://pear.php.net/dtd/tasks-1.0.xsd
    http://pear.php.net/dtd/package-2.0
    http://pear.php.net/dtd/package-2.0.xsd">
 <name>additional_message_headers</name>
 <channel>pear.roundcube.net</channel>
 <summary>Additional message headers for Roundcube</summary>
 <description>Very simple plugin which will add additional headers to or remove them from outgoing messages.</description>
 <lead>
  <name>Ziba Scott</name>
  <user>ziba</user>
  <email>email@example.org</email>
  <active>yes</active>
 </lead>
 <date>2010-01-16</date>
 <time>18:19:33</time>
 <version>
  <release>1.1.0</release>
  <api>1.1.0</api>
 </version>
 <stability>
  <release>stable</release>
  <api>stable</api>
 </stability>
 <license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPL v2</license>
 <notes>-</notes>
 <contents>
  <dir baseinstalldir="/" name="/">
   <file name="additional_message_headers.php" role="php">
    <tasks:replace from="@name@" to="name" type="package-info" />
    <tasks:replace from="@package_version@" to="version" type="package-info" />
   </file>
  </dir> <!-- / -->
 </contents>
 <dependencies>
  <required>
   <php>
    <min>5.2.1</min>
   </php>
   <pearinstaller>
    <min>1.7.0</min>
   </pearinstaller>
  </required>
 </dependencies>
 <phprelease />
</package>
plugins/archive/archive.js
New file
@@ -0,0 +1,36 @@
/*
 * Archive plugin script
 * @version @package_version@
 */
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(','),
    lock = rcmail.set_busy(true, 'loading');
  rcmail.http_post('plugin.archive', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), lock);
}
// 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 && rcmail.env.archive_folder_icon && (li = rcmail.get_folder_li(rcmail.env.archive_folder)))
      $(li).css('background-image', 'url(' + rcmail.env.archive_folder_icon + ')');
  })
}
plugins/archive/archive.php
New file
@@ -0,0 +1,144 @@
<?php
/**
 * Archive
 *
 * Plugin that adds a new button to the mailbox toolbar
 * to move messages to a (user selectable) archive folder.
 *
 * @version @package_version@
 * @author Andre Rodier, Thomas Bruederli
 */
class archive extends rcube_plugin
{
  public $task = 'mail|settings';
  function init()
  {
    $rcmail = rcmail::get_instance();
    $this->register_action('plugin.archive', array($this, 'request_action'));
    // There is no "Archived flags"
    // $GLOBALS['IMAP_FLAGS']['ARCHIVED'] = 'Archive';
    if ($rcmail->task == 'mail' && ($rcmail->action == '' || $rcmail->action == 'show')
      && ($archive_folder = $rcmail->config->get('archive_mbox'))) {
      $skin_path = $this->local_skin_path();
      $this->include_script('archive.js');
      $this->add_texts('localization', true);
      $this->add_button(
        array(
            'command' => 'plugin.archive',
            'imagepas' => $skin_path.'/archive_pas.png',
            'imageact' => $skin_path.'/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);
      $rcmail->output->set_env('archive_folder_icon', $this->url($skin_path.'/foldericon.png'));
      // 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('preferences_list', array($this, 'prefs_table'));
        $this->add_hook('preferences_save', 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)
  {
    global $CURR_SECTION;
    if ($args['section'] == 'folders') {
      $this->add_texts('localization');
      $rcmail = rcmail::get_instance();
      // load folders list when needed
      if ($CURR_SECTION)
        $select = rcmail_mailbox_select(array('noselection' => '---', 'realnames' => true,
          'maxlength' => 30, 'exceptions' => array('INBOX')));
      else
        $select = new html_select();
      $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;
    }
  }
}
plugins/archive/localization/cs_CZ.inc
New file
@@ -0,0 +1,25 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube archive plugin                         |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
| Author: Milan Kozak <hodza@hodza.net>                                 |
+-----------------------------------------------------------------------+
@version $Id: labels.inc 2993 2009-09-26 18:32:07Z alec $
*/
$labels = array();
$labels['buttontitle'] = 'Archivovat zprávu';
$labels['archived'] = 'Úspěšně vloženo do archivu';
$labels['archivefolder'] = 'Archiv';
?>
plugins/archive/localization/de_CH.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Nachricht archivieren';
$labels['archived'] = 'Nachricht erfolgreich archiviert';
$labels['archivefolder'] = 'Archiv';
?>
plugins/archive/localization/de_DE.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Nachricht archivieren';
$labels['archived'] = 'Nachricht erfolgreich archiviert';
$labels['archivefolder'] = 'Archiv';
?>
plugins/archive/localization/en_US.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archive this message';
$labels['archived'] = 'Successfully archived';
$labels['archivefolder'] = 'Archive';
?>
plugins/archive/localization/es_AR.inc
New file
@@ -0,0 +1,10 @@
<?php
// MPBAUPGRADE
$labels = array();
$labels['buttontitle'] = 'Archivar este mensaje';
$labels['archived'] = 'Mensaje Archivado';
$labels['archivefolder'] = 'Archivo';
?>
plugins/archive/localization/es_ES.inc
New file
@@ -0,0 +1,10 @@
<?php
// MPBAUPGRADE
$labels = array();
$labels['buttontitle'] = 'Archivar este mensaje';
$labels['archived'] = 'Mensaje Archivado';
$labels['archivefolder'] = 'Archivo';
?>
plugins/archive/localization/et_EE.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Arhiveeri see kiri';
$labels['archived'] = 'Edukalt arhiveeritud';
$labels['archivefolder'] = 'Arhiveeri';
?>
plugins/archive/localization/fr_FR.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archiver ce message';
$labels['archived'] = 'Message archivé avec success';
$labels['archivefolder'] = 'Archive';
?>
plugins/archive/localization/ja_JP.inc
New file
@@ -0,0 +1,10 @@
<?php
//  EN-Revision: 3891
$labels = array();
$labels['buttontitle'] = 'このメッセージのアーカイブ';
$labels['archived'] = 'アーカイブに成功しました。';
$labels['archivefolder'] = 'アーカイブ';
?>
plugins/archive/localization/nl_NL.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Archiveer dit bericht';
$labels['archived'] = 'Succesvol gearchiveerd';
$labels['archivefolder'] = 'Archief';
?>
plugins/archive/localization/pl_PL.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Przenieś do archiwum';
$labels['archived'] = 'Pomyślnie zarchiwizowano';
$labels['archivefolder'] = 'Archiwum';
?>
plugins/archive/localization/ru_RU.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Переместить выбранное в архив';
$labels['archived'] = 'Перенесено в Архив';
$labels['archivefolder'] = 'Архив';
?>
plugins/archive/localization/sv_SE.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Arkivera meddelande';
$labels['archived'] = 'Meddelandet är arkiverat';
$labels['archivefolder'] = 'Arkiv';
?>
plugins/archive/localization/zh_TW.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['buttontitle'] = '封存此信件';
$labels['archived'] = '已成功封存';
$labels['archivefolder'] = '封存';
?>
plugins/archive/package.xml
New file
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
    http://pear.php.net/dtd/tasks-1.0.xsd
    http://pear.php.net/dtd/package-2.0
    http://pear.php.net/dtd/package-2.0.xsd">
    <name>archive</name>
    <channel>pear.roundcube.net</channel>
    <summary>Archive feature for Roundcube</summary>
    <description>This adds a button to move the selected messages to an archive folder. The folder can be selected in the settings panel.</description>
    <lead>
        <name>Thomas Bruederli</name>
        <user>thomasb</user>
        <email>roundcube@gmail.com</email>
        <active>yes</active>
    </lead>
    <date>2010-02-06</date>
    <time>12:12:00</time>
    <version>
        <release>1.4</release>
        <api>1.4</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license>
    <notes>-</notes>
    <contents>
        <dir baseinstalldir="/" name="/">
            <file name="archive.php" role="php">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="archive.js" role="data">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="localization/en_US.inc" role="data"></file>
            <file name="localization/cs_CZ.inc" role="data"></file>
            <file name="localization/de_CH.inc" role="data"></file>
            <file name="localization/de_DE.inc" role="data"></file>
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/fr_FR.inc" role="data"></file>
            <file name="localization/pl_PL.inc" role="data"></file>
            <file name="localization/ru_RU.inc" role="data"></file>
            <file name="localization/zh_TW.inc" role="data"></file>
            <file name="skins/default/archive_act.png" role="data"></file>
            <file name="skins/default/archive_pas.png" role="data"></file>
            <file name="skins/default/foldericon.png" role="data"></file>
        </dir>
        <!-- / -->
    </contents>
    <dependencies>
        <required>
            <php>
                <min>5.2.1</min>
            </php>
            <pearinstaller>
                <min>1.7.0</min>
            </pearinstaller>
        </required>
    </dependencies>
    <phprelease/>
</package>
plugins/archive/skins/default/archive_act.png
plugins/archive/skins/default/archive_pas.png
plugins/archive/skins/default/foldericon.png
plugins/autologon/autologon.php
New file
@@ -0,0 +1,45 @@
<?php
/**
 * Sample plugin to try out some hooks.
 * This performs an automatic login if accessed from localhost
 */
class autologon extends rcube_plugin
{
  public $task = 'login';
  function init()
  {
    $this->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 (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';
  }
}
plugins/database_attachments/database_attachments.php
New file
@@ -0,0 +1,156 @@
<?php
/**
 * Filesystem Attachments
 *
 * This plugin which provides database backed storage for temporary
 * attachment file handling.  The primary advantage of this plugin
 * is its compatibility with round-robin dns multi-server roundcube
 * installations.
 *
 * This plugin relies on the core filesystem_attachments plugin
 *
 * @author Ziba Scott <ziba@umich.edu>
 *
 */
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() will check the file and
     * return it's contents
     */
    function display($args)
    {
        return $this->get($args);
    }
    /**
     * When displaying or sending the attachment the file contents are fetched
     * using this method. This is also called by the attachment_display hook.
     */
    function get($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']);
    }
}
plugins/debug_logger/debug_logger.php
New file
@@ -0,0 +1,146 @@
<?php
/**
 * Debug Logger
 *
 * Enhanced logging for debugging purposes.  It is not recommened
 * to be enabled on production systems without testing because of
 * the somewhat increased memory, cpu and disk i/o overhead.
 *
 * Debug Logger listens for existing console("message") calls and
 * introduces start and end tags as well as free form tagging
 * which can redirect messages to files.  The resulting log files
 * provide timing and tag quantity results.
 *
 * Enable the plugin in config/main.inc.php and add your desired
 * log types and files.
 *
 * @version 1.0
 * @author Ziba Scott
 * @website http://roundcube.net
 *
 * Example:
 *
 * config/main.inc.php:
 *
 *   // $rcmail_config['debug_logger'][type of logging] = name of file in log_dir
 *   // The 'master' log includes timing information
 *   $rcmail_config['debug_logger']['master'] = 'master';
 *   // If you want sql messages to also go into a separate file
 *   $rcmail_config['debug_logger']['sql'] = 'sql';
 *
 * index.php (just after $RCMAIL->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();
    }
}
?>
plugins/debug_logger/runlog/runlog.php
New file
@@ -0,0 +1,227 @@
<?php
/**
 * runlog
 *
 * @author Ziba Scott <ziba@umich.edu>
 */
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 = '<pre>' . print_r( $msg, TRUE ) . '</pre>';
        }
        $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);
        }
    }
}
?>
plugins/emoticons/emoticons.php
New file
@@ -0,0 +1,80 @@
<?php
/**
 * Display Emoticons
 *
 * Sample plugin to replace emoticons in plain text message body with real icons
 *
 * @version 1.2.0
 * @author Thomas Bruederli
 * @author Aleksander Machniak
 * @website http://roundcube.net
 */
class emoticons extends rcube_plugin
{
    public $task = 'mail';
    function init()
    {
        $this->add_hook('message_part_after', array($this, 'replace'));
    }
    function replace($args)
    {
        // This is a lookbehind assertion which will exclude html entities
        // E.g. situation when ";)" in "&quot;)" shouldn't be replaced by the icon
        // It's so long because of assertion format restrictions
        $entity = '(?<!&'
            . '[a-zA-Z0-9]{2}' . '|' . '#[0-9]{2}' . '|'
            . '[a-zA-Z0-9]{3}' . '|' . '#[0-9]{3}' . '|'
            . '[a-zA-Z0-9]{4}' . '|' . '#[0-9]{4}' . '|'
            . '[a-zA-Z0-9]{5}' . '|'
            . '[a-zA-Z0-9]{6}' . '|'
            . '[a-zA-Z0-9]{7}'
            . ')';
        // map of emoticon replacements
        $map = array(
            '/:\)/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif',
                'title' => ':)'
            )),
            '/:-\)/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif',
                'title' => ':-)'
            )),
            '/(?<!mailto):D/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif',
                'title' => ':D'
            )),
            '/:-D/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif',
                'title' => ':-D'
            )),
            '/:\(/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-frown.gif',
                'title' => ':('
            )),
            '/:-\(/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-frown.gif',
                'title' => ':-('
            )),
            '/'.$entity.';\)/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-wink.gif',
                'title' => ';)'
            )),
            '/'.$entity.';-\)/' => html::img(array(
                'src'   => './program/js/tiny_mce/plugins/emotions/img/smiley-wink.gif',
                'title' => ';-)'
            )),
        );
        if ($args['type'] == 'plain') {
            $args['body'] = preg_replace(
                array_keys($map), array_values($map), $args['body']);
        }
        return $args;
    }
}
plugins/enigma/README
New file
@@ -0,0 +1,35 @@
------------------------------------------------------------------
THIS IS NOT EVEN AN "ALPHA" STATE. USE ONLY FOR DEVELOPMENT!!!!!!!
------------------------------------------------------------------
WARNING: Don't use with gnupg-2.x!
Enigma Plugin Status:
* DONE:
- PGP signed messages verification
- Handling of PGP keys files attached to incoming messages
- PGP encrypted messages decryption (started)
- PGP keys management UI (started)
* TODO (must have):
- Parsing of decrypted messages into array (see rcube_mime_struct) and then into rcube_message_part structure
  (create core class rcube_mime_parser or take over PEAR::Mail_mimeDecode package and improve it)
- Sending encrypted/signed messages (probably some changes in core will be needed)
- Per-Identity settings (including keys/certs) (+ split Identities details page into tabs)
- Handling big messages with temp files (including changes in Roundcube core)
- Performance improvements (some caching, code review)
- better (and more) icons
* TODO (later):
- Keys generation
- Certs generation
- Keys/Certs info in Contacts details page (+ split Contact details page into tabs)
- Key server support
- S/MIME signed messages verification
- S/MIME encrypted messages decryption
- Handling of S/MIME certs files attached to incoming messages
- SSL (S/MIME) Certs management
plugins/enigma/config.inc.php
New file
@@ -0,0 +1,14 @@
<?php
// Enigma Plugin options
// --------------------
// A driver to use for PGP. Default: "gnupg".
$rcmail_config['enigma_pgp_driver'] = 'gnupg';
// A driver to use for S/MIME. Default: "phpssl".
$rcmail_config['enigma_smime_driver'] = 'phpssl';
// Keys directory for all users. Default 'enigma/home'.
// Must be writeable by PHP process
$rcmail_config['enigma_pgp_homedir'] = null;
plugins/enigma/enigma.js
New file
@@ -0,0 +1,206 @@
/* Enigma Plugin */
if (window.rcmail)
{
    rcmail.addEventListener('init', function(evt)
    {
        if (rcmail.env.task == 'settings') {
            rcmail.register_command('plugin.enigma', function() { rcmail.goto_url('plugin.enigma') }, true);
            rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import() }, true);
            rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_key_export() }, true);
            if (rcmail.gui_objects.keyslist)
            {
                var p = rcmail;
                rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist,
                    {multiselect:false, draggable:false, keyboard:false});
                rcmail.keys_list.addEventListener('select', function(o){ p.enigma_key_select(o); });
                rcmail.keys_list.init();
                rcmail.keys_list.focus();
                rcmail.enigma_list();
                rcmail.register_command('firstpage', function(props) {return rcmail.enigma_list_page('first'); });
                rcmail.register_command('previouspage', function(props) {return rcmail.enigma_list_page('previous'); });
                rcmail.register_command('nextpage', function(props) {return rcmail.enigma_list_page('next'); });
                rcmail.register_command('lastpage', function(props) {return rcmail.enigma_list_page('last'); });
            }
            if (rcmail.env.action == 'edit-prefs') {
                rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true);
                rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true);
            }
            else if (rcmail.env.action == 'plugin.enigma') {
                rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import() }, true);
                rcmail.register_command('plugin.enigma-export', function() { rcmail.enigma_export() }, true);
            }
        }
    });
}
/*********************************************************/
/*********    Enigma Settings/Keys/Certs UI      *********/
/*********************************************************/
// Display key(s) import form
rcube_webmail.prototype.enigma_key_import = function()
{
    this.enigma_loadframe(null, '&_a=keyimport');
};
// Submit key(s) form
rcube_webmail.prototype.enigma_import = function()
{
    var form, file;
    if (form = this.gui_objects.importform) {
        file = document.getElementById('rcmimportfile');
        if (file && !file.value) {
            alert(this.get_label('selectimportfile'));
            return;
        }
        form.submit();
        this.set_busy(true, 'importwait');
        this.lock_form(form, true);
   }
};
// list row selection handler
rcube_webmail.prototype.enigma_key_select = function(list)
{
    var id;
    if (id = list.get_single_selection())
        this.enigma_loadframe(id);
};
// load key frame
rcube_webmail.prototype.enigma_loadframe = function(id, url)
{
    var frm, win;
    if (this.env.contentframe && window.frames && (frm = window.frames[this.env.contentframe])) {
        if (!id && !url && (win = window.frames[this.env.contentframe])) {
            if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
                win.location.href = this.env.blankpage;
            return;
        }
        this.set_busy(true);
        if (!url)
            url = '&_a=keyinfo&_id='+id;
        frm.location.href = this.env.comm_path+'&_action=plugin.enigma&_framed=1' + url;
    }
};
// Search keys/certs
rcube_webmail.prototype.enigma_search = function(props)
{
    if (!props && this.gui_objects.qsearchbox)
        props = this.gui_objects.qsearchbox.value;
    if (props || this.env.search_request) {
        var params = {'_a': 'keysearch', '_q': urlencode(props)},
          lock = this.set_busy(true, 'searching');
//        if (this.gui_objects.search_filter)
  //          addurl += '&_filter=' + this.gui_objects.search_filter.value;
        this.env.current_page = 1;
        this.enigma_loadframe();
        this.enigma_clear_list();
        this.http_post('plugin.enigma', params, lock);
    }
    return false;
}
// Reset search filter and the list
rcube_webmail.prototype.enigma_search_reset = function(props)
{
    var s = this.env.search_request;
    this.reset_qsearch();
    if (s) {
        this.enigma_loadframe();
        this.enigma_clear_list();
        // refresh the list
        this.enigma_list();
    }
    return false;
}
// Keys/certs listing
rcube_webmail.prototype.enigma_list = function(page)
{
    var params = {'_a': 'keylist'},
      lock = this.set_busy(true, 'loading');
    this.env.current_page = page ? page : 1;
    if (this.env.search_request)
        params._q = this.env.search_request;
    if (page)
        params._p = page;
    this.enigma_clear_list();
    this.http_post('plugin.enigma', params, lock);
}
// Change list page
rcube_webmail.prototype.enigma_list_page = function(page)
{
    if (page == 'next')
        page = this.env.current_page + 1;
    else if (page == 'last')
        page = this.env.pagecount;
    else if (page == 'prev' && this.env.current_page > 1)
        page = this.env.current_page - 1;
    else if (page == 'first' && this.env.current_page > 1)
        page = 1;
    this.enigma_list(page);
}
// Remove list rows
rcube_webmail.prototype.enigma_clear_list = function()
{
    this.enigma_loadframe();
    if (this.keys_list)
        this.keys_list.clear(true);
}
// Adds a row to the list
rcube_webmail.prototype.enigma_add_list_row = function(r)
{
    if (!this.gui_objects.keyslist || !this.keys_list)
        return false;
    var list = this.keys_list,
        tbody = this.gui_objects.keyslist.tBodies[0],
        rowcount = tbody.rows.length,
        even = rowcount%2,
        css_class = 'message'
            + (even ? ' even' : ' odd'),
        // for performance use DOM instead of jQuery here
        row = document.createElement('tr'),
        col = document.createElement('td');
    row.id = 'rcmrow' + r.id;
    row.className = css_class;
    col.innerHTML = r.name;
    row.appendChild(col);
    list.insert_row(row);
}
/*********************************************************/
/*********        Enigma Message methods         *********/
/*********************************************************/
// Import attached keys/certs file
rcube_webmail.prototype.enigma_import_attachment = function(mime_id)
{
    var lock = this.set_busy(true, 'loading');
    this.http_post('plugin.enigmaimport', '_uid='+this.env.uid+'&_mbox='
        +urlencode(this.env.mailbox)+'&_part='+urlencode(mime_id), lock);
    return false;
};
plugins/enigma/enigma.php
New file
@@ -0,0 +1,475 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Enigma Plugin for Roundcube                                             |
 | Version 0.1                                                             |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
/*
    This class contains only hooks and action handlers.
    Most plugin logic is placed in enigma_engine and enigma_ui classes.
*/
class enigma extends rcube_plugin
{
    public $task = 'mail|settings';
    public $rc;
    public $engine;
    private $env_loaded;
    private $message;
    private $keys_parts = array();
    private $keys_bodies = array();
    /**
     * Plugin initialization.
     */
    function init()
    {
        $rcmail = rcmail::get_instance();
        $this->rc = $rcmail;
        if ($this->rc->task == 'mail') {
            // message parse/display hooks
            $this->add_hook('message_part_structure', array($this, 'parse_structure'));
            $this->add_hook('message_body_prefix', array($this, 'status_message'));
            // message displaying
            if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
                $this->add_hook('message_load', array($this, 'message_load'));
                $this->add_hook('template_object_messagebody', array($this, 'message_output'));
                $this->register_action('plugin.enigmaimport', array($this, 'import_file'));
            }
            // message composing
            else if ($rcmail->action == 'compose') {
                $this->load_ui();
                $this->ui->init($section);
            }
            // message sending (and draft storing)
            else if ($rcmail->action == 'sendmail') {
                //$this->add_hook('outgoing_message_body', array($this, 'msg_encode'));
                //$this->add_hook('outgoing_message_body', array($this, 'msg_sign'));
            }
        }
        else if ($this->rc->task == 'settings') {
            // add hooks for Enigma settings
            $this->add_hook('preferences_sections_list', array($this, 'preferences_section'));
            $this->add_hook('preferences_list', array($this, 'preferences_list'));
            $this->add_hook('preferences_save', array($this, 'preferences_save'));
            // register handler for keys/certs management
            $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
            // grab keys/certs management iframe requests
            $section = get_input_value('_section', RCUBE_INPUT_GET);
            if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
                $this->load_ui();
                $this->ui->init($section);
            }
        }
    }
    /**
     * Plugin environment initialization.
     */
    function load_env()
    {
        if ($this->env_loaded)
            return;
        $this->env_loaded = true;
        // Add include path for Enigma classes and drivers
        $include_path = $this->home . '/lib' . PATH_SEPARATOR;
        $include_path .= ini_get('include_path');
        set_include_path($include_path);
        // load the Enigma plugin configuration
        $this->load_config();
        // include localization (if wasn't included before)
        $this->add_texts('localization/');
    }
    /**
     * Plugin UI initialization.
     */
    function load_ui()
    {
        if ($this->ui)
            return;
        // load config/localization
        $this->load_env();
        // Load UI
        $this->ui = new enigma_ui($this, $this->home);
    }
    /**
     * Plugin engine initialization.
     */
    function load_engine()
    {
        if ($this->engine)
            return;
        // load config/localization
        $this->load_env();
        $this->engine = new enigma_engine($this);
    }
    /**
     * Handler for message_part_structure hook.
     * Called for every part of the message.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function parse_structure($p)
    {
        $struct = $p['structure'];
        if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
            $this->parse_plain($p);
        }
        else if ($p['mimetype'] == 'multipart/signed') {
            $this->parse_signed($p);
        }
        else if ($p['mimetype'] == 'multipart/encrypted') {
            $this->parse_encrypted($p);
        }
        else if ($p['mimetype'] == 'application/pkcs7-mime') {
            $this->parse_encrypted($p);
        }
        return $p;
    }
    /**
     * Handler for preferences_sections_list hook.
     * Adds Enigma settings sections into preferences sections list.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function preferences_section($p)
    {
        // add labels
        $this->add_texts('localization/');
        $p['list']['enigmasettings'] = array(
            'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
        );
        $p['list']['enigmacerts'] = array(
            'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
        );
        $p['list']['enigmakeys'] = array(
            'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
        );
        return $p;
    }
    /**
     * Handler for preferences_list hook.
     * Adds options blocks into Enigma settings sections in Preferences.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function preferences_list($p)
    {
        if ($p['section'] == 'enigmasettings') {
            // This makes that section is not removed from the list
            $p['blocks']['dummy']['options']['dummy'] = array();
        }
        else if ($p['section'] == 'enigmacerts') {
            // This makes that section is not removed from the list
            $p['blocks']['dummy']['options']['dummy'] = array();
        }
        else if ($p['section'] == 'enigmakeys') {
            // This makes that section is not removed from the list
            $p['blocks']['dummy']['options']['dummy'] = array();
        }
        return $p;
    }
    /**
     * Handler for preferences_save hook.
     * Executed on Enigma settings form submit.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function preferences_save($p)
    {
        if ($p['section'] == 'enigmasettings') {
            $a['prefs'] = array(
//                'dummy' => get_input_value('_dummy', RCUBE_INPUT_POST),
            );
        }
        return $p;
    }
    /**
     * Handler for keys/certs management UI template.
     */
    function preferences_ui()
    {
        $this->load_ui();
        $this->ui->init();
    }
    /**
     * Handler for message_body_prefix hook.
     * Called for every displayed (content) part of the message.
     * Adds infobox about signature verification and/or decryption
     * status above the body.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function status_message($p)
    {
        $part_id = $p['part']->mime_id;
        // skip: not a message part
        if ($p['part'] instanceof rcube_message)
            return $p;
        // skip: message has no signed/encoded content
        if (!$this->engine)
            return $p;
        // Decryption status
        if (isset($this->engine->decryptions[$part_id])) {
            // get decryption status
            $status = $this->engine->decryptions[$part_id];
            // Load UI and add css script
            $this->load_ui();
            $this->ui->add_css();
            // display status info
            $attrib['id'] = 'enigma-message';
            if ($status instanceof enigma_error) {
                $attrib['class'] = 'enigmaerror';
                $code = $status->getCode();
                if ($code == enigma_error::E_KEYNOTFOUND)
                    $msg = Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
                        $this->gettext('decryptnokey')));
                else if ($code == enigma_error::E_BADPASS)
                    $msg = Q($this->gettext('decryptbadpass'));
                else
                    $msg = Q($this->gettext('decrypterror'));
            }
            else {
                $attrib['class'] = 'enigmanotice';
                $msg = Q($this->gettext('decryptok'));
            }
            $p['prefix'] .= html::div($attrib, $msg);
        }
        // Signature verification status
        if (isset($this->engine->signed_parts[$part_id])
            && ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
        ) {
            // add css script
            $this->load_ui();
            $this->ui->add_css();
            // display status info
            $attrib['id'] = 'enigma-message';
            if ($sig instanceof enigma_signature) {
                if ($sig->valid) {
                    $attrib['class'] = 'enigmanotice';
                    $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
                    $msg = Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
                }
                else {
                    $attrib['class'] = 'enigmawarning';
                    $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
                    $msg = Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
                }
            }
            else if ($sig->getCode() == enigma_error::E_KEYNOTFOUND) {
                $attrib['class'] = 'enigmawarning';
                $msg = Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
                    $this->gettext('signokey')));
            }
            else {
                $attrib['class'] = 'enigmaerror';
                $msg = Q($this->gettext('sigerror'));
            }
/*
            $msg .= '&nbsp;' . html::a(array('href' => "#sigdetails",
                'onclick' => JS_OBJECT_NAME.".command('enigma-sig-details')"),
                Q($this->gettext('showdetails')));
*/
            // test
//            $msg .= '<br /><pre>'.$sig->body.'</pre>';
            $p['prefix'] .= html::div($attrib, $msg);
            // Display each signature message only once
            unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
        }
        return $p;
    }
    /**
     * Handler for plain/text message.
     *
     * @param array Reference to hook's parameters (see enigma::parse_structure())
     */
    private function parse_plain(&$p)
    {
        $this->load_engine();
        $this->engine->parse_plain($p);
    }
    /**
     * Handler for multipart/signed message.
     * Verifies signature.
     *
     * @param array Reference to hook's parameters (see enigma::parse_structure())
     */
    private function parse_signed(&$p)
    {
        $this->load_engine();
        $this->engine->parse_signed($p);
    }
    /**
     * Handler for multipart/encrypted and application/pkcs7-mime message.
     *
     * @param array Reference to hook's parameters (see enigma::parse_structure())
     */
    private function parse_encrypted(&$p)
    {
        $this->load_engine();
        $this->engine->parse_encrypted($p);
    }
    /**
     * Handler for message_load hook.
     * Check message bodies and attachments for keys/certs.
     */
    function message_load($p)
    {
        $this->message = $p['object'];
        // handle attachments vcard attachments
        foreach ((array)$this->message->attachments as $attachment) {
            if ($this->is_keys_part($attachment)) {
                $this->keys_parts[] = $attachment->mime_id;
            }
        }
        // the same with message bodies
        foreach ((array)$this->message->parts as $idx => $part) {
            if ($this->is_keys_part($part)) {
                $this->keys_parts[] = $part->mime_id;
                $this->keys_bodies[] = $part->mime_id;
            }
        }
        // @TODO: inline PGP keys
        if ($this->keys_parts) {
            $this->add_texts('localization');
        }
    }
    /**
     * Handler for template_object_messagebody hook.
     * This callback function adds a box below the message content
     * if there is a key/cert attachment available
     */
    function message_output($p)
    {
        $attach_script = false;
        foreach ($this->keys_parts as $part) {
            // remove part's body
            if (in_array($part, $this->keys_bodies))
                $p['content'] = '';
            $style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
                ." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
            // add box below messsage body
            $p['content'] .= html::p(array('style' => $style),
                html::a(array(
                    'href' => "#",
                    'onclick' => "return ".JS_OBJECT_NAME.".enigma_import_attachment('".JQ($part)."')",
                    'title' => $this->gettext('keyattimport')),
                    html::img(array('src' => $this->url('skins/default/key_add.png'), 'style' => "vertical-align:middle")))
                . ' ' . html::span(null, $this->gettext('keyattfound')));
            $attach_script = true;
        }
        if ($attach_script) {
            $this->include_script('enigma.js');
        }
        return $p;
    }
    /**
     * Handler for attached keys/certs import
     */
    function import_file()
    {
        $this->load_engine();
        $this->engine->import_file();
    }
    /**
     * Checks if specified message part is a PGP-key or S/MIME cert data
     *
     * @param rcube_message_part Part object
     *
     * @return boolean True if part is a key/cert
     */
    private function is_keys_part($part)
    {
        // @TODO: S/MIME
        return (
            // Content-Type: application/pgp-keys
            $part->mimetype == 'application/pgp-keys'
        );
    }
}
plugins/enigma/home/.htaccess
New file
@@ -0,0 +1,2 @@
Order allow,deny
Deny from all
plugins/enigma/lib/Crypt/GPG.php
New file
@@ -0,0 +1,2542 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This package provides an object oriented interface to GNU Privacy
 * Guard (GPG). It requires the GPG executable to be on the system.
 *
 * Though GPG can support symmetric-key cryptography, this package is intended
 * only to facilitate public-key cryptography.
 *
 * This file contains the main GPG class. The class in this file lets you
 * encrypt, decrypt, sign and verify data; import and delete keys; and perform
 * other useful GPG tasks.
 *
 * Example usage:
 * <code>
 * <?php
 * // encrypt some data
 * $gpg = new Crypt_GPG();
 * $gpg->addEncryptKey($mySecretKeyId);
 * $encryptedData = $gpg->encrypt($data);
 * ?>
 * </code>
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: GPG.php 302814 2010-08-26 15:43:07Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://pear.php.net/manual/en/package.encryption.crypt-gpg.php
 * @link      http://www.gnupg.org/
 */
/**
 * Signature handler class
 */
require_once 'Crypt/GPG/VerifyStatusHandler.php';
/**
 * Decryption handler class
 */
require_once 'Crypt/GPG/DecryptStatusHandler.php';
/**
 * GPG key class
 */
require_once 'Crypt/GPG/Key.php';
/**
 * GPG sub-key class
 */
require_once 'Crypt/GPG/SubKey.php';
/**
 * GPG user id class
 */
require_once 'Crypt/GPG/UserId.php';
/**
 * GPG process and I/O engine class
 */
require_once 'Crypt/GPG/Engine.php';
/**
 * GPG exception classes
 */
require_once 'Crypt/GPG/Exceptions.php';
// {{{ class Crypt_GPG
/**
 * A class to use GPG from PHP
 *
 * This class provides an object oriented interface to GNU Privacy Guard (GPG).
 *
 * Though GPG can support symmetric-key cryptography, this class is intended
 * only to facilitate public-key cryptography.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG
{
    // {{{ class error constants
    /**
     * Error code returned when there is no error.
     */
    const ERROR_NONE = 0;
    /**
     * Error code returned when an unknown or unhandled error occurs.
     */
    const ERROR_UNKNOWN = 1;
    /**
     * Error code returned when a bad passphrase is used.
     */
    const ERROR_BAD_PASSPHRASE = 2;
    /**
     * Error code returned when a required passphrase is missing.
     */
    const ERROR_MISSING_PASSPHRASE = 3;
    /**
     * Error code returned when a key that is already in the keyring is
     * imported.
     */
    const ERROR_DUPLICATE_KEY = 4;
    /**
     * Error code returned the required data is missing for an operation.
     *
     * This could be missing key data, missing encrypted data or missing
     * signature data.
     */
    const ERROR_NO_DATA = 5;
    /**
     * Error code returned when an unsigned key is used.
     */
    const ERROR_UNSIGNED_KEY = 6;
    /**
     * Error code returned when a key that is not self-signed is used.
     */
    const ERROR_NOT_SELF_SIGNED = 7;
    /**
     * Error code returned when a public or private key that is not in the
     * keyring is used.
     */
    const ERROR_KEY_NOT_FOUND = 8;
    /**
     * Error code returned when an attempt to delete public key having a
     * private key is made.
     */
    const ERROR_DELETE_PRIVATE_KEY = 9;
    /**
     * Error code returned when one or more bad signatures are detected.
     */
    const ERROR_BAD_SIGNATURE = 10;
    /**
     * Error code returned when there is a problem reading GnuPG data files.
     */
    const ERROR_FILE_PERMISSIONS = 11;
    // }}}
    // {{{ class constants for data signing modes
    /**
     * Signing mode for normal signing of data. The signed message will not
     * be readable without special software.
     *
     * This is the default signing mode.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_NORMAL = 1;
    /**
     * Signing mode for clearsigning data. Clearsigned signatures are ASCII
     * armored data and are readable without special software. If the signed
     * message is unencrypted, the message will still be readable. The message
     * text will be in the original encoding.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_CLEAR = 2;
    /**
     * Signing mode for creating a detached signature. When using detached
     * signatures, only the signature data is returned. The original message
     * text may be distributed separately from the signature data. This is
     * useful for miltipart/signed email messages as per
     * {@link http://www.ietf.org/rfc/rfc3156.txt RFC 3156}.
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     */
    const SIGN_MODE_DETACHED = 3;
    // }}}
    // {{{ class constants for fingerprint formats
    /**
     * No formatting is performed.
     *
     * Example: C3BC615AD9C766E5A85C1F2716D27458B1BBA1C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_NONE = 1;
    /**
     * Fingerprint is formatted in the format used by the GnuPG gpg command's
     * default output.
     *
     * Example: C3BC 615A D9C7 66E5 A85C  1F27 16D2 7458 B1BB A1C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_CANONICAL = 2;
    /**
     * Fingerprint is formatted in the format used when displaying X.509
     * certificates
     *
     * Example: C3:BC:61:5A:D9:C7:66:E5:A8:5C:1F:27:16:D2:74:58:B1:BB:A1:C4
     *
     * @see Crypt_GPG::getFingerprint()
     */
    const FORMAT_X509 = 3;
    // }}}
    // {{{ other class constants
    /**
     * URI at which package bugs may be reported.
     */
    const BUG_URI = 'http://pear.php.net/bugs/report.php?package=Crypt_GPG';
    // }}}
    // {{{ protected class properties
    /**
     * Engine used to control the GPG subprocess
     *
     * @var Crypt_GPG_Engine
     *
     * @see Crypt_GPG::setEngine()
     */
    protected $engine = null;
    /**
     * Keys used to encrypt
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => null
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addEncryptKey()
     * @see Crypt_GPG::clearEncryptKeys()
     */
    protected $encryptKeys = array();
    /**
     * Keys used to decrypt
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => $passphrase
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addSignKey()
     * @see Crypt_GPG::clearSignKeys()
     */
    protected $signKeys = array();
    /**
     * Keys used to sign
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => $passphrase
     *   )
     * );
     * </code>
     *
     * @var array
     * @see Crypt_GPG::addDecryptKey()
     * @see Crypt_GPG::clearDecryptKeys()
     */
    protected $decryptKeys = array();
    // }}}
    // {{{ __construct()
    /**
     * Creates a new GPG object
     *
     * Available options are:
     *
     * - <kbd>string  homedir</kbd>        - the directory where the GPG
     *                                       keyring files are stored. If not
     *                                       specified, Crypt_GPG uses the
     *                                       default of <kbd>~/.gnupg</kbd>.
     * - <kbd>string  publicKeyring</kbd>  - the file path of the public
     *                                       keyring. Use this if the public
     *                                       keyring is not in the homedir, or
     *                                       if the keyring is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       keyring with this option
     *                                       (/foo/bar/pubring.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  privateKeyring</kbd> - the file path of the private
     *                                       keyring. Use this if the private
     *                                       keyring is not in the homedir, or
     *                                       if the keyring is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       keyring with this option
     *                                       (/foo/bar/secring.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  trustDb</kbd>        - the file path of the web-of-trust
     *                                       database. Use this if the trust
     *                                       database is not in the homedir, or
     *                                       if the database is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       trust database with this option
     *                                       (/foo/bar/trustdb.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  binary</kbd>         - the location of the GPG binary. If
     *                                       not specified, the driver attempts
     *                                       to auto-detect the GPG binary
     *                                       location using a list of known
     *                                       default locations for the current
     *                                       operating system. The option
     *                                       <kbd>gpgBinary</kbd> is a
     *                                       deprecated alias for this option.
     * - <kbd>boolean debug</kbd>          - whether or not to use debug mode.
     *                                       When debug mode is on, all
     *                                       communication to and from the GPG
     *                                       subprocess is logged. This can be
     *
     * @param array $options optional. An array of options used to create the
     *                       GPG object. All options are optional and are
     *                       represented as key-value pairs.
     *
     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
     *         not specified, Crypt_GPG is run as the web user, and the web
     *         user has no home directory. This exception is also thrown if any
     *         of the options <kbd>publicKeyring</kbd>,
     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
     *         specified but the files do not exist or are are not readable.
     *         This can happen if the user running the Crypt_GPG process (for
     *         example, the Apache user) does not have permission to read the
     *         files.
     *
     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
     *         if no <kbd>binary</kbd> is provided and no suitable binary could
     *         be found.
     */
    public function __construct(array $options = array())
    {
        $this->setEngine(new Crypt_GPG_Engine($options));
    }
    // }}}
    // {{{ importKey()
    /**
     * Imports a public or private key into the keyring
     *
     * Keys may be removed from the keyring using
     * {@link Crypt_GPG::deletePublicKey()} or
     * {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $data the key data to be imported.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function importKey($data)
    {
        return $this->_importKey($data, false);
    }
    // }}}
    // {{{ importKeyFile()
    /**
     * Imports a public or private key file into the keyring
     *
     * Keys may be removed from the keyring using
     * {@link Crypt_GPG::deletePublicKey()} or
     * {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $filename the key file to be imported.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *                                                  private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_FileException if the key file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function importKeyFile($filename)
    {
        return $this->_importKey($filename, true);
    }
    // }}}
    // {{{ exportPublicKey()
    /**
     * Exports a public key from the keyring
     *
     * The exported key remains on the keyring. To delete the public key, use
     * {@link Crypt_GPG::deletePublicKey()}.
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first public key is exported.
     *
     * @param string  $keyId either the full uid of the public key, the email
     *                       part of the uid of the public key or the key id of
     *                       the public key. For example,
     *                       "Test User (example) <test@example.com>",
     *                       "test@example.com" or a hexadecimal string.
     * @param boolean $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the public key data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function exportPublicKey($keyId, $armor = true)
    {
        $fingerprint = $this->getFingerprint($keyId);
        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Public key not found: ' . $keyId,
                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId);
        }
        $keyData   = '';
        $operation = '--export ' . escapeshellarg($fingerprint);
        $arguments = ($armor) ? array('--armor') : array();
        $this->engine->reset();
        $this->engine->setOutput($keyData);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        if ($code !== Crypt_GPG::ERROR_NONE) {
            throw new Crypt_GPG_Exception(
                'Unknown error exporting public key. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
        return $keyData;
    }
    // }}}
    // {{{ deletePublicKey()
    /**
     * Deletes a public key from the keyring
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first public key is deleted.
     *
     * The private key must be deleted first or an exception will be thrown.
     * See {@link Crypt_GPG::deletePrivateKey()}.
     *
     * @param string $keyId either the full uid of the public key, the email
     *                      part of the uid of the public key or the key id of
     *                      the public key. For example,
     *                      "Test User (example) <test@example.com>",
     *                      "test@example.com" or a hexadecimal string.
     *
     * @return void
     *
     * @throws Crypt_GPG_KeyNotFoundException if a public key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_DeletePrivateKeyException if the specified public key
     *         has an associated private key on the keyring. The private key
     *         must be deleted first.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function deletePublicKey($keyId)
    {
        $fingerprint = $this->getFingerprint($keyId);
        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Public key not found: ' . $keyId,
                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId);
        }
        $operation = '--delete-key ' . escapeshellarg($fingerprint);
        $arguments = array(
            '--batch',
            '--yes'
        );
        $this->engine->reset();
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
            break;
        case Crypt_GPG::ERROR_DELETE_PRIVATE_KEY:
            throw new Crypt_GPG_DeletePrivateKeyException(
                'Private key must be deleted before public key can be ' .
                'deleted.', $code, $keyId);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error deleting public key. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
    }
    // }}}
    // {{{ deletePrivateKey()
    /**
     * Deletes a private key from the keyring
     *
     * If more than one key fingerprint is available for the specified
     * <kbd>$keyId</kbd> (for example, if you use a non-unique uid) only the
     * first private key is deleted.
     *
     * Calls GPG with the <kbd>--delete-secret-key</kbd> command.
     *
     * @param string $keyId either the full uid of the private key, the email
     *                      part of the uid of the private key or the key id of
     *                      the private key. For example,
     *                      "Test User (example) <test@example.com>",
     *                      "test@example.com" or a hexadecimal string.
     *
     * @return void
     *
     * @throws Crypt_GPG_KeyNotFoundException if a private key with the given
     *         <kbd>$keyId</kbd> is not found.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function deletePrivateKey($keyId)
    {
        $fingerprint = $this->getFingerprint($keyId);
        if ($fingerprint === null) {
            throw new Crypt_GPG_KeyNotFoundException(
                'Private key not found: ' . $keyId,
                Crypt_GPG::ERROR_KEY_NOT_FOUND, $keyId);
        }
        $operation = '--delete-secret-key ' . escapeshellarg($fingerprint);
        $arguments = array(
            '--batch',
            '--yes'
        );
        $this->engine->reset();
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
            break;
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            throw new Crypt_GPG_KeyNotFoundException(
                'Private key not found: ' . $keyId,
                $code, $keyId);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error deleting private key. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
    }
    // }}}
    // {{{ getKeys()
    /**
     * Gets the available keys in the keyring
     *
     * Calls GPG with the <kbd>--list-keys</kbd> command and grabs keys. See
     * the first section of <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG package} for a detailed
     * description of how the GPG command output is parsed.
     *
     * @param string $keyId optional. Only keys with that match the specified
     *                      pattern are returned. The pattern may be part of
     *                      a user id, a key id or a key fingerprint. If not
     *                      specified, all keys are returned.
     *
     * @return array an array of {@link Crypt_GPG_Key} objects. If no keys
     *               match the specified <kbd>$keyId</kbd> an empty array is
     *               returned.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Key
     */
    public function getKeys($keyId = '')
    {
        // get private key fingerprints
        if ($keyId == '') {
            $operation = '--list-secret-keys';
        } else {
            $operation = '--list-secret-keys ' . escapeshellarg($keyId);
        }
        // According to The file 'doc/DETAILS' in the GnuPG distribution, using
        // double '--with-fingerprint' also prints the fingerprint for subkeys.
        $arguments = array(
            '--with-colons',
            '--with-fingerprint',
            '--with-fingerprint',
            '--fixed-list-mode'
        );
        $output = '';
        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            // ignore not found key errors
            break;
        case Crypt_GPG::ERROR_FILE_PERMISSIONS:
            $filename = $this->engine->getErrorFilename();
            if ($filename) {
                throw new Crypt_GPG_FileException(sprintf(
                    'Error reading GnuPG data file \'%s\'. Check to make ' .
                    'sure it is readable by the current user.', $filename),
                    $code, $filename);
            }
            throw new Crypt_GPG_FileException(
                'Error reading GnuPG data file. Check to make GnuPG data ' .
                'files are readable by the current user.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error getting keys. Please use the \'debug\' option ' .
                'when creating the Crypt_GPG object, and file a bug report ' .
                'at ' . self::BUG_URI, $code);
        }
        $privateKeyFingerprints = array();
        $lines = explode(PHP_EOL, $output);
        foreach ($lines as $line) {
            $lineExp = explode(':', $line);
            if ($lineExp[0] == 'fpr') {
                $privateKeyFingerprints[] = $lineExp[9];
            }
        }
        // get public keys
        if ($keyId == '') {
            $operation = '--list-public-keys';
        } else {
            $operation = '--list-public-keys ' . escapeshellarg($keyId);
        }
        $output = '';
        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            // ignore not found key errors
            break;
        case Crypt_GPG::ERROR_FILE_PERMISSIONS:
            $filename = $this->engine->getErrorFilename();
            if ($filename) {
                throw new Crypt_GPG_FileException(sprintf(
                    'Error reading GnuPG data file \'%s\'. Check to make ' .
                    'sure it is readable by the current user.', $filename),
                    $code, $filename);
            }
            throw new Crypt_GPG_FileException(
                'Error reading GnuPG data file. Check to make GnuPG data ' .
                'files are readable by the current user.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error getting keys. Please use the \'debug\' option ' .
                'when creating the Crypt_GPG object, and file a bug report ' .
                'at ' . self::BUG_URI, $code);
        }
        $keys = array();
        $key    = null; // current key
        $subKey = null; // current sub-key
        $lines = explode(PHP_EOL, $output);
        foreach ($lines as $line) {
            $lineExp = explode(':', $line);
            if ($lineExp[0] == 'pub') {
                // new primary key means last key should be added to the array
                if ($key !== null) {
                    $keys[] = $key;
                }
                $key = new Crypt_GPG_Key();
                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);
            } elseif ($lineExp[0] == 'sub') {
                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);
            } elseif ($lineExp[0] == 'fpr') {
                $fingerprint = $lineExp[9];
                // set current sub-key fingerprint
                $subKey->setFingerprint($fingerprint);
                // if private key exists, set has private to true
                if (in_array($fingerprint, $privateKeyFingerprints)) {
                    $subKey->setHasPrivate(true);
                }
            } elseif ($lineExp[0] == 'uid') {
                $string = stripcslashes($lineExp[9]); // as per documentation
                $userId = new Crypt_GPG_UserId($string);
                if ($lineExp[1] == 'r') {
                    $userId->setRevoked(true);
                }
                $key->addUserId($userId);
            }
        }
        // add last key
        if ($key !== null) {
            $keys[] = $key;
        }
        return $keys;
    }
    // }}}
    // {{{ getFingerprint()
    /**
     * Gets a key fingerprint from the keyring
     *
     * If more than one key fingerprint is available (for example, if you use
     * a non-unique user id) only the first key fingerprint is returned.
     *
     * Calls the GPG <kbd>--list-keys</kbd> command with the
     * <kbd>--with-fingerprint</kbd> option to retrieve a public key
     * fingerprint.
     *
     * @param string  $keyId  either the full user id of the key, the email
     *                        part of the user id of the key, or the key id of
     *                        the key. For example,
     *                        "Test User (example) <test@example.com>",
     *                        "test@example.com" or a hexadecimal string.
     * @param integer $format optional. How the fingerprint should be formatted.
     *                        Use {@link Crypt_GPG::FORMAT_X509} for X.509
     *                        certificate format,
     *                        {@link Crypt_GPG::FORMAT_CANONICAL} for the format
     *                        used by GnuPG output and
     *                        {@link Crypt_GPG::FORMAT_NONE} for no formatting.
     *                        Defaults to <code>Crypt_GPG::FORMAT_NONE</code>.
     *
     * @return string the fingerprint of the key, or null if no fingerprint
     *                is found for the given <kbd>$keyId</kbd>.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function getFingerprint($keyId, $format = Crypt_GPG::FORMAT_NONE)
    {
        $output    = '';
        $operation = '--list-keys ' . escapeshellarg($keyId);
        $arguments = array(
            '--with-colons',
            '--with-fingerprint'
        );
        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            // ignore not found key errors
            break;
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error getting key fingerprint. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
        $fingerprint = null;
        $lines = explode(PHP_EOL, $output);
        foreach ($lines as $line) {
            if (substr($line, 0, 3) == 'fpr') {
                $lineExp     = explode(':', $line);
                $fingerprint = $lineExp[9];
                switch ($format) {
                case Crypt_GPG::FORMAT_CANONICAL:
                    $fingerprintExp = str_split($fingerprint, 4);
                    $format         = '%s %s %s %s %s  %s %s %s %s %s';
                    $fingerprint    = vsprintf($format, $fingerprintExp);
                    break;
                case Crypt_GPG::FORMAT_X509:
                    $fingerprintExp = str_split($fingerprint, 2);
                    $fingerprint    = implode(':', $fingerprintExp);
                    break;
                }
                break;
            }
        }
        return $fingerprint;
    }
    // }}}
    // {{{ encrypt()
    /**
     * Encrypts string data
     *
     * Data is ASCII armored by default but may optionally be returned as
     * binary.
     *
     * @param string  $data  the data to be encrypted.
     * @param boolean $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the encrypted data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @sensitive $data
     */
    public function encrypt($data, $armor = true)
    {
        return $this->_encrypt($data, false, null, $armor);
    }
    // }}}
    // {{{ encryptFile()
    /**
     * Encrypts a file
     *
     * Encrypted data is ASCII armored by default but may optionally be saved
     * as binary.
     *
     * @param string  $filename      the filename of the file to encrypt.
     * @param string  $encryptedFile optional. The filename of the file in
     *                               which to store the encrypted data. If null
     *                               or unspecified, the encrypted data is
     *                               returned as a string.
     * @param boolean $armor         optional. If true, ASCII armored data is
     *                               returned; otherwise, binary data is
     *                               returned. Defaults to true.
     *
     * @return void|string if the <kbd>$encryptedFile</kbd> parameter is null,
     *                     a string containing the encrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function encryptFile($filename, $encryptedFile = null, $armor = true)
    {
        return $this->_encrypt($filename, true, $encryptedFile, $armor);
    }
    // }}}
    // {{{ encryptAndSign()
    /**
     * Encrypts and signs data
     *
     * Data is encrypted and signed in a single pass.
     *
     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify
     * encrypted-signed data without decrypting it at the same time. If you try
     * to use {@link Crypt_GPG::verify()} method on encrypted-signed data with
     * earlier GnuPG versions, you will get an error. Please use
     * {@link Crypt_GPG::decryptAndVerify()} to verify encrypted-signed data.
     *
     * @param string  $data  the data to be encrypted and signed.
     * @param boolean $armor optional. If true, ASCII armored data is returned;
     *                       otherwise, binary data is returned. Defaults to
     *                       true.
     *
     * @return string the encrypted signed data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG::decryptAndVerify()
     */
    public function encryptAndSign($data, $armor = true)
    {
        return $this->_encryptAndSign($data, false, null, $armor);
    }
    // }}}
    // {{{ encryptAndSignFile()
    /**
     * Encrypts and signs a file
     *
     * The file is encrypted and signed in a single pass.
     *
     * NOTE: Until GnuPG version 1.4.10, it was not possible to verify
     * encrypted-signed files without decrypting them at the same time. If you
     * try to use {@link Crypt_GPG::verify()} method on encrypted-signed files
     * with earlier GnuPG versions, you will get an error. Please use
     * {@link Crypt_GPG::decryptAndVerifyFile()} to verify encrypted-signed
     * files.
     *
     * @param string  $filename   the name of the file containing the data to
     *                            be encrypted and signed.
     * @param string  $signedFile optional. The name of the file in which the
     *                            encrypted, signed data should be stored. If
     *                            null or unspecified, the encrypted, signed
     *                            data is returned as a string.
     * @param boolean $armor      optional. If true, ASCII armored data is
     *                            returned; otherwise, binary data is returned.
     *                            Defaults to true.
     *
     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a
     *                     string containing the encrypted, signed data is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG::decryptAndVerifyFile()
     */
    public function encryptAndSignFile($filename, $signedFile = null,
        $armor = true
    ) {
        return $this->_encryptAndSign($filename, true, $signedFile, $armor);
    }
    // }}}
    // {{{ decrypt()
    /**
     * Decrypts string data
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedData the data to be decrypted.
     *
     * @return string the decrypted data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decrypt($encryptedData)
    {
        return $this->_decrypt($encryptedData, false, null);
    }
    // }}}
    // {{{ decryptFile()
    /**
     * Decrypts a file
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedFile the name of the encrypted file data to
     *                              decrypt.
     * @param string $decryptedFile optional. The name of the file to which the
     *                              decrypted data should be written. If null
     *                              or unspecified, the decrypted data is
     *                              returned as a string.
     *
     * @return void|string if the <kbd>$decryptedFile</kbd> parameter is null,
     *                     a string containing the decrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptFile($encryptedFile, $decryptedFile = null)
    {
        return $this->_decrypt($encryptedFile, true, $decryptedFile);
    }
    // }}}
    // {{{ decryptAndVerify()
    /**
     * Decrypts and verifies string data
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedData the encrypted, signed data to be decrypted
     *                              and verified.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptAndVerify($encryptedData)
    {
        return $this->_decryptAndVerify($encryptedData, false, null);
    }
    // }}}
    // {{{ decryptAndVerifyFile()
    /**
     * Decrypts and verifies a signed, encrypted file
     *
     * This method assumes the required private key is available in the keyring
     * and throws an exception if the private key is not available. To add a
     * private key to the keyring, use the {@link Crypt_GPG::importKey()} or
     * {@link Crypt_GPG::importKeyFile()} methods.
     *
     * @param string $encryptedFile the name of the signed, encrypted file to
     *                              to decrypt and verify.
     * @param string $decryptedFile optional. The name of the file to which the
     *                              decrypted data should be written. If null
     *                              or unspecified, the decrypted data is
     *                              returned in the results array.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *               If the decrypted data is written to a file, the 'data'
     *               element is null.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function decryptAndVerifyFile($encryptedFile, $decryptedFile = null)
    {
        return $this->_decryptAndVerify($encryptedFile, true, $decryptedFile);
    }
    // }}}
    // {{{ sign()
    /**
     * Signs data
     *
     * Data may be signed using any one of the three available signing modes:
     * - {@link Crypt_GPG::SIGN_MODE_NORMAL}
     * - {@link Crypt_GPG::SIGN_MODE_CLEAR}
     * - {@link Crypt_GPG::SIGN_MODE_DETACHED}
     *
     * @param string  $data     the data to be signed.
     * @param boolean $mode     optional. The data signing mode to use. Should
     *                          be one of {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                          {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                          {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not
     *                          specified, defaults to
     *                          <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>.
     * @param boolean $armor    optional. If true, ASCII armored data is
     *                          returned; otherwise, binary data is returned.
     *                          Defaults to true. This has no effect if the
     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                          used.
     * @param boolean $textmode optional. If true, line-breaks in signed data
     *                          are normalized. Use this option when signing
     *                          e-mail, or for greater compatibility between
     *                          systems with different line-break formats.
     *                          Defaults to false. This has no effect if the
     *                          mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                          used as clear-signing always uses textmode.
     *
     * @return string the signed data, or the signature data if a detached
     *                signature is requested.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function sign($data, $mode = Crypt_GPG::SIGN_MODE_NORMAL,
        $armor = true, $textmode = false
    ) {
        return $this->_sign($data, false, null, $mode, $armor, $textmode);
    }
    // }}}
    // {{{ signFile()
    /**
     * Signs a file
     *
     * The file may be signed using any one of the three available signing
     * modes:
     * - {@link Crypt_GPG::SIGN_MODE_NORMAL}
     * - {@link Crypt_GPG::SIGN_MODE_CLEAR}
     * - {@link Crypt_GPG::SIGN_MODE_DETACHED}
     *
     * @param string  $filename   the name of the file containing the data to
     *                            be signed.
     * @param string  $signedFile optional. The name of the file in which the
     *                            signed data should be stored. If null or
     *                            unspecified, the signed data is returned as a
     *                            string.
     * @param boolean $mode       optional. The data signing mode to use. Should
     *                            be one of {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                            {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                            {@link Crypt_GPG::SIGN_MODE_DETACHED}. If not
     *                            specified, defaults to
     *                            <kbd>Crypt_GPG::SIGN_MODE_NORMAL</kbd>.
     * @param boolean $armor      optional. If true, ASCII armored data is
     *                            returned; otherwise, binary data is returned.
     *                            Defaults to true. This has no effect if the
     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used.
     * @param boolean $textmode   optional. If true, line-breaks in signed data
     *                            are normalized. Use this option when signing
     *                            e-mail, or for greater compatibility between
     *                            systems with different line-break formats.
     *                            Defaults to false. This has no effect if the
     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used as clear-signing always uses textmode.
     *
     * @return void|string if the <kbd>$signedFile</kbd> parameter is null, a
     *                     string containing the signed data (or the signature
     *                     data if a detached signature is requested) is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    public function signFile($filename, $signedFile = null,
        $mode = Crypt_GPG::SIGN_MODE_NORMAL, $armor = true, $textmode = false
    ) {
        return $this->_sign(
            $filename,
            true,
            $signedFile,
            $mode,
            $armor,
            $textmode
        );
    }
    // }}}
    // {{{ verify()
    /**
     * Verifies signed data
     *
     * The {@link Crypt_GPG::decrypt()} method may be used to get the original
     * message if the signed data is not clearsigned and does not use a
     * detached signature.
     *
     * @param string $signedData the signed data to be verified.
     * @param string $signature  optional. If verifying data signed using a
     *                           detached signature, this must be the detached
     *                           signature data. The data that was signed is
     *                           specified in <kbd>$signedData</kbd>.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data. For each signature that is valid, the
     *               {@link Crypt_GPG_Signature::isValid()} will return true.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    public function verify($signedData, $signature = '')
    {
        return $this->_verify($signedData, false, $signature);
    }
    // }}}
    // {{{ verifyFile()
    /**
     * Verifies a signed file
     *
     * The {@link Crypt_GPG::decryptFile()} method may be used to get the
     * original message if the signed data is not clearsigned and does not use
     * a detached signature.
     *
     * @param string $filename  the signed file to be verified.
     * @param string $signature optional. If verifying a file signed using a
     *                          detached signature, this must be the detached
     *                          signature data. The file that was signed is
     *                          specified in <kbd>$filename</kbd>.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data. For each signature that is valid, the
     *               {@link Crypt_GPG_Signature::isValid()} will return true.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_FileException if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    public function verifyFile($filename, $signature = '')
    {
        return $this->_verify($filename, true, $signature);
    }
    // }}}
    // {{{ addDecryptKey()
    /**
     * Adds a key to use for decryption
     *
     * @param mixed  $key        the key to use. This may be a key identifier,
     *                           user id, fingerprint, {@link Crypt_GPG_Key} or
     *                           {@link Crypt_GPG_SubKey}. The key must be able
     *                           to encrypt.
     * @param string $passphrase optional. The passphrase of the key required
     *                           for decryption.
     *
     * @return void
     *
     * @see Crypt_GPG::decrypt()
     * @see Crypt_GPG::decryptFile()
     * @see Crypt_GPG::clearDecryptKeys()
     * @see Crypt_GPG::_addKey()
     * @see Crypt_GPG_DecryptStatusHandler
     *
     * @sensitive $passphrase
     */
    public function addDecryptKey($key, $passphrase = null)
    {
        $this->_addKey($this->decryptKeys, true, false, $key, $passphrase);
    }
    // }}}
    // {{{ addEncryptKey()
    /**
     * Adds a key to use for encryption
     *
     * @param mixed $key the key to use. This may be a key identifier, user id
     *                   user id, fingerprint, {@link Crypt_GPG_Key} or
     *                   {@link Crypt_GPG_SubKey}. The key must be able to
     *                   encrypt.
     *
     * @return void
     *
     * @see Crypt_GPG::encrypt()
     * @see Crypt_GPG::encryptFile()
     * @see Crypt_GPG::clearEncryptKeys()
     * @see Crypt_GPG::_addKey()
     */
    public function addEncryptKey($key)
    {
        $this->_addKey($this->encryptKeys, true, false, $key);
    }
    // }}}
    // {{{ addSignKey()
    /**
     * Adds a key to use for signing
     *
     * @param mixed  $key        the key to use. This may be a key identifier,
     *                           user id, fingerprint, {@link Crypt_GPG_Key} or
     *                           {@link Crypt_GPG_SubKey}. The key must be able
     *                           to sign.
     * @param string $passphrase optional. The passphrase of the key required
     *                           for signing.
     *
     * @return void
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::signFile()
     * @see Crypt_GPG::clearSignKeys()
     * @see Crypt_GPG::handleSignStatus()
     * @see Crypt_GPG::_addKey()
     *
     * @sensitive $passphrase
     */
    public function addSignKey($key, $passphrase = null)
    {
        $this->_addKey($this->signKeys, false, true, $key, $passphrase);
    }
    // }}}
    // {{{ clearDecryptKeys()
    /**
     * Clears all decryption keys
     *
     * @return void
     *
     * @see Crypt_GPG::decrypt()
     * @see Crypt_GPG::addDecryptKey()
     */
    public function clearDecryptKeys()
    {
        $this->decryptKeys = array();
    }
    // }}}
    // {{{ clearEncryptKeys()
    /**
     * Clears all encryption keys
     *
     * @return void
     *
     * @see Crypt_GPG::encrypt()
     * @see Crypt_GPG::addEncryptKey()
     */
    public function clearEncryptKeys()
    {
        $this->encryptKeys = array();
    }
    // }}}
    // {{{ clearSignKeys()
    /**
     * Clears all signing keys
     *
     * @return void
     *
     * @see Crypt_GPG::sign()
     * @see Crypt_GPG::addSignKey()
     */
    public function clearSignKeys()
    {
        $this->signKeys = array();
    }
    // }}}
    // {{{ handleSignStatus()
    /**
     * Handles the status output from GPG for the sign operation
     *
     * This method is responsible for sending the passphrase commands when
     * required by the {@link Crypt_GPG::sign()} method. See <b>doc/DETAILS</b>
     * in the {@link http://www.gnupg.org/download/ GPG distribution} for
     * detailed information on GPG's status output.
     *
     * @param string $line the status line to handle.
     *
     * @return void
     *
     * @see Crypt_GPG::sign()
     */
    public function handleSignStatus($line)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'NEED_PASSPHRASE':
            $subKeyId = $tokens[1];
            if (array_key_exists($subKeyId, $this->signKeys)) {
                $passphrase = $this->signKeys[$subKeyId]['passphrase'];
                $this->engine->sendCommand($passphrase);
            } else {
                $this->engine->sendCommand('');
            }
            break;
        }
    }
    // }}}
    // {{{ handleImportKeyStatus()
    /**
     * Handles the status output from GPG for the import operation
     *
     * This method is responsible for building the result array that is
     * returned from the {@link Crypt_GPG::importKey()} method. See
     * <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
     * information on GPG's status output.
     *
     * @param string $line    the status line to handle.
     * @param array  &$result the current result array being processed.
     *
     * @return void
     *
     * @see Crypt_GPG::importKey()
     * @see Crypt_GPG::importKeyFile()
     * @see Crypt_GPG_Engine::addStatusHandler()
     */
    public function handleImportKeyStatus($line, array &$result)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'IMPORT_OK':
            $result['fingerprint'] = $tokens[2];
            break;
        case 'IMPORT_RES':
            $result['public_imported']   = intval($tokens[3]);
            $result['public_unchanged']  = intval($tokens[5]);
            $result['private_imported']  = intval($tokens[11]);
            $result['private_unchanged'] = intval($tokens[12]);
            break;
        }
    }
    // }}}
    // {{{ setEngine()
    /**
     * Sets the I/O engine to use for GnuPG operations
     *
     * Normally this method does not need to be used. It provides a means for
     * dependency injection.
     *
     * @param Crypt_GPG_Engine $engine the engine to use.
     *
     * @return void
     */
    public function setEngine(Crypt_GPG_Engine $engine)
    {
        $this->engine = $engine;
    }
    // }}}
    // {{{ _addKey()
    /**
     * Adds a key to one of the internal key arrays
     *
     * This handles resolving full key objects from the provided
     * <kbd>$key</kbd> value.
     *
     * @param array   &$array     the array to which the key should be added.
     * @param boolean $encrypt    whether or not the key must be able to
     *                            encrypt.
     * @param boolean $sign       whether or not the key must be able to sign.
     * @param mixed   $key        the key to add. This may be a key identifier,
     *                            user id, fingerprint, {@link Crypt_GPG_Key} or
     *                            {@link Crypt_GPG_SubKey}.
     * @param string  $passphrase optional. The passphrase associated with the
     *                            key.
     *
     * @return void
     *
     * @sensitive $passphrase
     */
    private function _addKey(array &$array, $encrypt, $sign, $key,
        $passphrase = null
    ) {
        $subKeys = array();
        if (is_scalar($key)) {
            $keys = $this->getKeys($key);
            if (count($keys) == 0) {
                throw new Crypt_GPG_KeyNotFoundException(
                    'Key "' . $key . '" not found.', 0, $key);
            }
            $key = $keys[0];
        }
        if ($key instanceof Crypt_GPG_Key) {
            if ($encrypt && !$key->canEncrypt()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot encrypt.');
            }
            if ($sign && !$key->canSign()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot sign.');
            }
            foreach ($key->getSubKeys() as $subKey) {
                $canEncrypt = $subKey->canEncrypt();
                $canSign    = $subKey->canSign();
                if (   ($encrypt && $sign && $canEncrypt && $canSign)
                    || ($encrypt && !$sign && $canEncrypt)
                    || (!$encrypt && $sign && $canSign)
                ) {
                    // We add all subkeys that meet the requirements because we
                    // were not told which subkey is required.
                    $subKeys[] = $subKey;
                }
            }
        } elseif ($key instanceof Crypt_GPG_SubKey) {
            $subKeys[] = $key;
        }
        if (count($subKeys) === 0) {
            throw new InvalidArgumentException(
                'Key "' . $key . '" is not in a recognized format.');
        }
        foreach ($subKeys as $subKey) {
            if ($encrypt && !$subKey->canEncrypt()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot encrypt.');
            }
            if ($sign && !$subKey->canSign()) {
                throw new InvalidArgumentException(
                    'Key "' . $key . '" cannot sign.');
            }
            $array[$subKey->getId()] = array(
                'fingerprint' => $subKey->getFingerprint(),
                'passphrase'  => $passphrase
            );
        }
    }
    // }}}
    // {{{ _importKey()
    /**
     * Imports a public or private key into the keyring
     *
     * @param string  $key    the key to be imported.
     * @param boolean $isFile whether or not the input is a filename.
     *
     * @return array an associative array containing the following elements:
     *               - <kbd>fingerprint</kbd>       - the fingerprint of the
     *                                                imported key,
     *               - <kbd>public_imported</kbd>   - the number of public
     *                                                keys imported,
     *               - <kbd>public_unchanged</kbd>  - the number of unchanged
     *                                                public keys,
     *               - <kbd>private_imported</kbd>  - the number of private
     *                                                keys imported,
     *               - <kbd>private_unchanged</kbd> - the number of unchanged
     *                                                private keys.
     *
     * @throws Crypt_GPG_NoDataException if the key data is missing or if the
     *         data is is not valid key data.
     *
     * @throws Crypt_GPG_FileException if the key file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    private function _importKey($key, $isFile)
    {
        $result = array();
        if ($isFile) {
            $input = @fopen($key, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open key file "' .
                    $key . '" for importing.', 0, $key);
            }
        } else {
            $input = strval($key);
            if ($input == '') {
                throw new Crypt_GPG_NoDataException(
                    'No valid GPG key data found.', Crypt_GPG::ERROR_NO_DATA);
            }
        }
        $arguments = array();
        $version   = $this->engine->getVersion();
        if (   version_compare($version, '1.0.5', 'ge')
            && version_compare($version, '1.0.7', 'lt')
        ) {
            $arguments[] = '--allow-secret-key-import';
        }
        $this->engine->reset();
        $this->engine->addStatusHandler(
            array($this, 'handleImportKeyStatus'),
            array(&$result)
        );
        $this->engine->setOperation('--import', $arguments);
        $this->engine->setInput($input);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_DUPLICATE_KEY:
        case Crypt_GPG::ERROR_NONE:
            // ignore duplicate key import errors
            break;
        case Crypt_GPG::ERROR_NO_DATA:
            throw new Crypt_GPG_NoDataException(
                'No valid GPG key data found.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error importing GPG key. Please use the \'debug\' ' .
                'option when creating the Crypt_GPG object, and file a bug ' .
                'report at ' . self::BUG_URI, $code);
        }
        return $result;
    }
    // }}}
    // {{{ _encrypt()
    /**
     * Encrypts data
     *
     * @param string  $data       the data to encrypt.
     * @param boolean $isFile     whether or not the data is a filename.
     * @param string  $outputFile the filename of the file in which to store
     *                            the encrypted data. If null, the encrypted
     *                            data is returned as a string.
     * @param boolean $armor      if true, ASCII armored data is returned;
     *                            otherwise, binary data is returned.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the encrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified.
     *         See {@link Crypt_GPG::addEncryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    private function _encrypt($data, $isFile, $outputFile, $armor)
    {
        if (count($this->encryptKeys) === 0) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No encryption keys specified.');
        }
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input file "' .
                    $data . '" for encryption.', 0, $data);
            }
        } else {
            $input = strval($data);
        }
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if ($isFile) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException('Could not open output ' .
                    'file "' . $outputFile . '" for storing encrypted data.',
                    0, $outputFile);
            }
        }
        $arguments = ($armor) ? array('--armor') : array();
        foreach ($this->encryptKeys as $key) {
            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']);
        }
        $this->engine->reset();
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--encrypt', $arguments);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        if ($outputFile !== null) {
            fclose($output);
        }
        $code = $this->engine->getErrorCode();
        if ($code !== Crypt_GPG::ERROR_NONE) {
            throw new Crypt_GPG_Exception(
                'Unknown error encrypting data. Please use the \'debug\' ' .
                'option when creating the Crypt_GPG object, and file a bug ' .
                'report at ' . self::BUG_URI, $code);
        }
        if ($outputFile === null) {
            return $output;
        }
    }
    // }}}
    // {{{ _decrypt()
    /**
     * Decrypts data
     *
     * @param string  $data       the data to be decrypted.
     * @param boolean $isFile     whether or not the data is a filename.
     * @param string  $outputFile the name of the file to which the decrypted
     *                            data should be written. If null, the decrypted
     *                            data is returned as a string.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the decrypted data is returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    private function _decrypt($data, $isFile, $outputFile)
    {
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input file "' .
                    $data . '" for decryption.', 0, $data);
            }
        } else {
            $input = strval($data);
            if ($input == '') {
                throw new Crypt_GPG_NoDataException(
                    'Cannot decrypt data. No PGP encrypted data was found in '.
                    'the provided data.', Crypt_GPG::ERROR_NO_DATA);
            }
        }
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if ($isFile) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException('Could not open output ' .
                    'file "' . $outputFile . '" for storing decrypted data.',
                    0, $outputFile);
            }
        }
        $handler = new Crypt_GPG_DecryptStatusHandler($this->engine,
            $this->decryptKeys);
        $this->engine->reset();
        $this->engine->addStatusHandler(array($handler, 'handle'));
        $this->engine->setOperation('--decrypt');
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        if ($outputFile !== null) {
            fclose($output);
        }
        // if there was any problem decrypting the data, the handler will
        // deal with it here.
        $handler->throwException();
        if ($outputFile === null) {
            return $output;
        }
    }
    // }}}
    // {{{ _sign()
    /**
     * Signs data
     *
     * @param string  $data       the data to be signed.
     * @param boolean $isFile     whether or not the data is a filename.
     * @param string  $outputFile the name of the file in which the signed data
     *                            should be stored. If null, the signed data is
     *                            returned as a string.
     * @param boolean $mode       the data signing mode to use. Should be one of
     *                            {@link Crypt_GPG::SIGN_MODE_NORMAL},
     *                            {@link Crypt_GPG::SIGN_MODE_CLEAR} or
     *                            {@link Crypt_GPG::SIGN_MODE_DETACHED}.
     * @param boolean $armor      if true, ASCII armored data is returned;
     *                            otherwise, binary data is returned. This has
     *                            no effect if the mode
     *                            <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used.
     * @param boolean $textmode   if true, line-breaks in signed data be
     *                            normalized. Use this option when signing
     *                            e-mail, or for greater compatibility between
     *                            systems with different line-break formats.
     *                            Defaults to false. This has no effect if the
     *                            mode <kbd>Crypt_GPG::SIGN_MODE_CLEAR</kbd> is
     *                            used as clear-signing always uses textmode.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the signed data (or the signature
     *                     data if a detached signature is requested) is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no signing key is specified.
     *         See {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    private function _sign($data, $isFile, $outputFile, $mode, $armor,
        $textmode
    ) {
        if (count($this->signKeys) === 0) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No signing keys specified.');
        }
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input ' .
                    'file "' . $data . '" for signing.', 0, $data);
            }
        } else {
            $input = strval($data);
        }
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if ($isFile) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException('Could not open output ' .
                    'file "' . $outputFile . '" for storing signed ' .
                    'data.', 0, $outputFile);
            }
        }
        switch ($mode) {
        case Crypt_GPG::SIGN_MODE_DETACHED:
            $operation = '--detach-sign';
            break;
        case Crypt_GPG::SIGN_MODE_CLEAR:
            $operation = '--clearsign';
            break;
        case Crypt_GPG::SIGN_MODE_NORMAL:
        default:
            $operation = '--sign';
            break;
        }
        $arguments  = array();
        if ($armor) {
            $arguments[] = '--armor';
        }
        if ($textmode) {
            $arguments[] = '--textmode';
        }
        foreach ($this->signKeys as $key) {
            $arguments[] = '--local-user ' .
                escapeshellarg($key['fingerprint']);
        }
        $this->engine->reset();
        $this->engine->addStatusHandler(array($this, 'handleSignStatus'));
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        if ($outputFile !== null) {
            fclose($output);
        }
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
            break;
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            throw new Crypt_GPG_KeyNotFoundException(
                'Cannot sign data. Private key not found. Import the '.
                'private key before trying to sign data.', $code,
                $this->engine->getErrorKeyId());
        case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            throw new Crypt_GPG_BadPassphraseException(
                'Cannot sign data. Incorrect passphrase provided.', $code);
        case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
            throw new Crypt_GPG_BadPassphraseException(
                'Cannot sign data. No passphrase provided.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error signing data. Please use the \'debug\' option ' .
                'when creating the Crypt_GPG object, and file a bug report ' .
                'at ' . self::BUG_URI, $code);
        }
        if ($outputFile === null) {
            return $output;
        }
    }
    // }}}
    // {{{ _encryptAndSign()
    /**
     * Encrypts and signs data
     *
     * @param string  $data       the data to be encrypted and signed.
     * @param boolean $isFile     whether or not the data is a filename.
     * @param string  $outputFile the name of the file in which the encrypted,
     *                            signed data should be stored. If null, the
     *                            encrypted, signed data is returned as a
     *                            string.
     * @param boolean $armor      if true, ASCII armored data is returned;
     *                            otherwise, binary data is returned.
     *
     * @return void|string if the <kbd>$outputFile</kbd> parameter is null, a
     *                     string containing the encrypted, signed data is
     *                     returned.
     *
     * @throws Crypt_GPG_KeyNotFoundException if no encryption key is specified
     *         or if no signing key is specified. See
     *         {@link Crypt_GPG::addEncryptKey()} and
     *         {@link Crypt_GPG::addSignKey()}.
     *
     * @throws Crypt_GPG_BadPassphraseException if a specified passphrase is
     *         incorrect or if a required passphrase is not specified.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     */
    private function _encryptAndSign($data, $isFile, $outputFile, $armor)
    {
        if (count($this->signKeys) === 0) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No signing keys specified.');
        }
        if (count($this->encryptKeys) === 0) {
            throw new Crypt_GPG_KeyNotFoundException(
                'No encryption keys specified.');
        }
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input ' .
                    'file "' . $data . '" for encrypting and signing.', 0,
                    $data);
            }
        } else {
            $input = strval($data);
        }
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if ($isFile) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException('Could not open output ' .
                    'file "' . $outputFile . '" for storing encrypted, ' .
                    'signed data.', 0, $outputFile);
            }
        }
        $arguments  = ($armor) ? array('--armor') : array();
        foreach ($this->signKeys as $key) {
            $arguments[] = '--local-user ' .
                escapeshellarg($key['fingerprint']);
        }
        foreach ($this->encryptKeys as $key) {
            $arguments[] = '--recipient ' . escapeshellarg($key['fingerprint']);
        }
        $this->engine->reset();
        $this->engine->addStatusHandler(array($this, 'handleSignStatus'));
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--encrypt --sign', $arguments);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        if ($outputFile !== null) {
            fclose($output);
        }
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
            break;
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            throw new Crypt_GPG_KeyNotFoundException(
                'Cannot sign encrypted data. Private key not found. Import '.
                'the private key before trying to sign the encrypted data.',
                $code, $this->engine->getErrorKeyId());
        case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            throw new Crypt_GPG_BadPassphraseException(
                'Cannot sign encrypted data. Incorrect passphrase provided.',
                $code);
        case Crypt_GPG::ERROR_MISSING_PASSPHRASE:
            throw new Crypt_GPG_BadPassphraseException(
                'Cannot sign encrypted data. No passphrase provided.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error encrypting and signing data. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
        if ($outputFile === null) {
            return $output;
        }
    }
    // }}}
    // {{{ _verify()
    /**
     * Verifies data
     *
     * @param string  $data      the signed data to be verified.
     * @param boolean $isFile    whether or not the data is a filename.
     * @param string  $signature if verifying a file signed using a detached
     *                           signature, this must be the detached signature
     *                           data. Otherwise, specify ''.
     *
     * @return array an array of {@link Crypt_GPG_Signature} objects for the
     *               signed data.
     *
     * @throws Crypt_GPG_NoDataException if the provided data is not signed
     *         data.
     *
     * @throws Crypt_GPG_FileException if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    private function _verify($data, $isFile, $signature)
    {
        if ($signature == '') {
            $operation = '--verify';
            $arguments = array();
        } else {
            // Signed data goes in FD_MESSAGE, detached signature data goes in
            // FD_INPUT.
            $operation = '--verify - "-&' . Crypt_GPG_Engine::FD_MESSAGE. '"';
            $arguments = array('--enable-special-filenames');
        }
        $handler = new Crypt_GPG_VerifyStatusHandler();
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input ' .
                    'file "' . $data . '" for verifying.', 0, $data);
            }
        } else {
            $input = strval($data);
            if ($input == '') {
                throw new Crypt_GPG_NoDataException(
                    'No valid signature data found.', Crypt_GPG::ERROR_NO_DATA);
            }
        }
        $this->engine->reset();
        $this->engine->addStatusHandler(array($handler, 'handle'));
        if ($signature == '') {
            // signed or clearsigned data
            $this->engine->setInput($input);
        } else {
            // detached signature
            $this->engine->setInput($signature);
            $this->engine->setMessage($input);
        }
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        $code = $this->engine->getErrorCode();
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
        case Crypt_GPG::ERROR_BAD_SIGNATURE:
            break;
        case Crypt_GPG::ERROR_NO_DATA:
            throw new Crypt_GPG_NoDataException(
                'No valid signature data found.', $code);
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            throw new Crypt_GPG_KeyNotFoundException(
                'Public key required for data verification not in keyring.',
                $code, $this->engine->getErrorKeyId());
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error validating signature details. Please use the ' .
                '\'debug\' option when creating the Crypt_GPG object, and ' .
                'file a bug report at ' . self::BUG_URI, $code);
        }
        return $handler->getSignatures();
    }
    // }}}
    // {{{ _decryptAndVerify()
    /**
     * Decrypts and verifies encrypted, signed data
     *
     * @param string  $data       the encrypted signed data to be decrypted and
     *                            verified.
     * @param boolean $isFile     whether or not the data is a filename.
     * @param string  $outputFile the name of the file to which the decrypted
     *                            data should be written. If null, the decrypted
     *                            data is returned in the results array.
     *
     * @return array two element array. The array has an element 'data'
     *               containing the decrypted data and an element
     *               'signatures' containing an array of
     *               {@link Crypt_GPG_Signature} objects for the signed data.
     *               If the decrypted data is written to a file, the 'data'
     *               element is null.
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring or it the public
     *         key needed for verification is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG signed, encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_FileException if the output file is not writeable or
     *         if the input file is not readable.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @see Crypt_GPG_Signature
     */
    private function _decryptAndVerify($data, $isFile, $outputFile)
    {
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException('Could not open input ' .
                    'file "' . $data . '" for decrypting and verifying.', 0,
                    $data);
            }
        } else {
            $input = strval($data);
            if ($input == '') {
                throw new Crypt_GPG_NoDataException(
                    'No valid encrypted signed data found.',
                    Crypt_GPG::ERROR_NO_DATA);
            }
        }
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if ($isFile) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException('Could not open output ' .
                    'file "' . $outputFile . '" for storing decrypted data.',
                    0, $outputFile);
            }
        }
        $verifyHandler = new Crypt_GPG_VerifyStatusHandler();
        $decryptHandler = new Crypt_GPG_DecryptStatusHandler($this->engine,
            $this->decryptKeys);
        $this->engine->reset();
        $this->engine->addStatusHandler(array($verifyHandler, 'handle'));
        $this->engine->addStatusHandler(array($decryptHandler, 'handle'));
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--decrypt');
        $this->engine->run();
        if ($isFile) {
            fclose($input);
        }
        if ($outputFile !== null) {
            fclose($output);
        }
        $return = array(
            'data'       => null,
            'signatures' => $verifyHandler->getSignatures()
        );
        // if there was any problem decrypting the data, the handler will
        // deal with it here.
        try {
            $decryptHandler->throwException();
        } catch (Exception $e) {
            if ($e instanceof Crypt_GPG_KeyNotFoundException) {
                throw new Crypt_GPG_KeyNotFoundException(
                    'Public key required for data verification not in ',
                    'the keyring. Either no suitable private decryption key ' .
                    'is in the keyring or the public key required for data ' .
                    'verification is not in the keyring. Import a suitable ' .
                    'key before trying to decrypt and verify this data.',
                    self::ERROR_KEY_NOT_FOUND, $this->engine->getErrorKeyId());
            }
            if ($e instanceof Crypt_GPG_NoDataException) {
                throw new Crypt_GPG_NoDataException(
                    'Cannot decrypt and verify data. No PGP encrypted data ' .
                    'was found in the provided data.', self::ERROR_NO_DATA);
            }
            throw $e;
        }
        if ($outputFile === null) {
            $return['data'] = $output;
        }
        return $return;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/DecryptStatusHandler.php
New file
@@ -0,0 +1,336 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains an object that handles GPG's status output for the
 * decrypt operation.
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2009 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: DecryptStatusHandler.php 302814 2010-08-26 15:43:07Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
/**
 * Crypt_GPG base class
 */
require_once 'Crypt/GPG.php';
/**
 * GPG exception classes
 */
require_once 'Crypt/GPG/Exceptions.php';
/**
 * Status line handler for the decrypt operation
 *
 * This class is used internally by Crypt_GPG and does not need be used
 * directly. See the {@link Crypt_GPG} class for end-user API.
 *
 * This class is responsible for sending the passphrase commands when required
 * by the {@link Crypt_GPG::decrypt()} method. See <b>doc/DETAILS</b> in the
 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
 * information on GPG's status output for the decrypt operation.
 *
 * This class is also responsible for parsing error status and throwing a
 * meaningful exception in the event that decryption fails.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_DecryptStatusHandler
{
    // {{{ protected properties
    /**
     * Keys used to decrypt
     *
     * The array is of the form:
     * <code>
     * array(
     *   $key_id => array(
     *     'fingerprint' => $fingerprint,
     *     'passphrase'  => $passphrase
     *   )
     * );
     * </code>
     *
     * @var array
     */
    protected $keys = array();
    /**
     * Engine used to which passphrases are passed
     *
     * @var Crypt_GPG_Engine
     */
    protected $engine = null;
    /**
     * The id of the current sub-key used for decryption
     *
     * @var string
     */
    protected $currentSubKey = '';
    /**
     * Whether or not decryption succeeded
     *
     * If the message is only signed (compressed) and not encrypted, this is
     * always true. If the message is encrypted, this flag is set to false
     * until we know the decryption succeeded.
     *
     * @var boolean
     */
    protected $decryptionOkay = true;
    /**
     * Whether or not there was no data for decryption
     *
     * @var boolean
     */
    protected $noData = false;
    /**
     * Keys for which the passhprase is missing
     *
     * This contains primary user ids indexed by sub-key id and is used to
     * create helpful exception messages.
     *
     * @var array
     */
    protected $missingPassphrases = array();
    /**
     * Keys for which the passhprase is incorrect
     *
     * This contains primary user ids indexed by sub-key id and is used to
     * create helpful exception messages.
     *
     * @var array
     */
    protected $badPassphrases = array();
    /**
     * Keys that can be used to decrypt the data but are missing from the
     * keychain
     *
     * This is an array with both the key and value being the sub-key id of
     * the missing keys.
     *
     * @var array
     */
    protected $missingKeys = array();
    // }}}
    // {{{ __construct()
    /**
     * Creates a new decryption status handler
     *
     * @param Crypt_GPG_Engine $engine the GPG engine to which passphrases are
     *                                 passed.
     * @param array            $keys   the decryption keys to use.
     */
    public function __construct(Crypt_GPG_Engine $engine, array $keys)
    {
        $this->engine = $engine;
        $this->keys   = $keys;
    }
    // }}}
    // {{{ handle()
    /**
     * Handles a status line
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    public function handle($line)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'ENC_TO':
            // Now we know the message is encrypted. Set flag to check if
            // decryption succeeded.
            $this->decryptionOkay = false;
            // this is the new key message
            $this->currentSubKeyId = $tokens[1];
            break;
        case 'NEED_PASSPHRASE':
            // send passphrase to the GPG engine
            $subKeyId = $tokens[1];
            if (array_key_exists($subKeyId, $this->keys)) {
                $passphrase = $this->keys[$subKeyId]['passphrase'];
                $this->engine->sendCommand($passphrase);
            } else {
                $this->engine->sendCommand('');
            }
            break;
        case 'USERID_HINT':
            // remember the user id for pretty exception messages
            $this->badPassphrases[$tokens[1]]
                = implode(' ', array_splice($tokens, 2));
            break;
        case 'GOOD_PASSPHRASE':
            // if we got a good passphrase, remove the key from the list of
            // bad passphrases.
            unset($this->badPassphrases[$this->currentSubKeyId]);
            break;
        case 'MISSING_PASSPHRASE':
            $this->missingPassphrases[$this->currentSubKeyId]
                = $this->currentSubKeyId;
            break;
        case 'NO_SECKEY':
            // note: this message is also received if there are multiple
            // recipients and a previous key had a correct passphrase.
            $this->missingKeys[$tokens[1]] = $tokens[1];
            break;
        case 'NODATA':
            $this->noData = true;
            break;
        case 'DECRYPTION_OKAY':
            // If the message is encrypted, this is the all-clear signal.
            $this->decryptionOkay = true;
            break;
        }
    }
    // }}}
    // {{{ throwException()
    /**
     * Takes the final status of the decrypt operation and throws an
     * appropriate exception
     *
     * If decryption was successful, no exception is thrown.
     *
     * @return void
     *
     * @throws Crypt_GPG_KeyNotFoundException if the private key needed to
     *         decrypt the data is not in the user's keyring.
     *
     * @throws Crypt_GPG_NoDataException if specified data does not contain
     *         GPG encrypted data.
     *
     * @throws Crypt_GPG_BadPassphraseException if a required passphrase is
     *         incorrect or if a required passphrase is not specified. See
     *         {@link Crypt_GPG::addDecryptKey()}.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <i>debug</i> option and file a bug report if these
     *         exceptions occur.
     */
    public function throwException()
    {
        $code = Crypt_GPG::ERROR_NONE;
        if (!$this->decryptionOkay) {
            if (count($this->badPassphrases) > 0) {
                $code = Crypt_GPG::ERROR_BAD_PASSPHRASE;
            } elseif (count($this->missingKeys) > 0) {
                $code = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            } else {
                $code = Crypt_GPG::ERROR_UNKNOWN;
            }
        } elseif ($this->noData) {
            $code = Crypt_GPG::ERROR_NO_DATA;
        }
        switch ($code) {
        case Crypt_GPG::ERROR_NONE:
            break;
        case Crypt_GPG::ERROR_KEY_NOT_FOUND:
            if (count($this->missingKeys) > 0) {
                $keyId = reset($this->missingKeys);
            } else {
                $keyId = '';
            }
            throw new Crypt_GPG_KeyNotFoundException(
                'Cannot decrypt data. No suitable private key is in the ' .
                'keyring. Import a suitable private key before trying to ' .
                'decrypt this data.', $code, $keyId);
        case Crypt_GPG::ERROR_BAD_PASSPHRASE:
            $badPassphrases = array_diff_key(
                $this->badPassphrases,
                $this->missingPassphrases
            );
            $missingPassphrases = array_intersect_key(
                $this->badPassphrases,
                $this->missingPassphrases
            );
            $message =  'Cannot decrypt data.';
            if (count($badPassphrases) > 0) {
                $message = ' Incorrect passphrase provided for keys: "' .
                    implode('", "', $badPassphrases) . '".';
            }
            if (count($missingPassphrases) > 0) {
                $message = ' No passphrase provided for keys: "' .
                    implode('", "', $badPassphrases) . '".';
            }
            throw new Crypt_GPG_BadPassphraseException($message, $code,
                $badPassphrases, $missingPassphrases);
        case Crypt_GPG::ERROR_NO_DATA:
            throw new Crypt_GPG_NoDataException(
                'Cannot decrypt data. No PGP encrypted data was found in '.
                'the provided data.', $code);
        default:
            throw new Crypt_GPG_Exception(
                'Unknown error decrypting data.', $code);
        }
    }
    // }}}
}
?>
plugins/enigma/lib/Crypt/GPG/Engine.php
New file
@@ -0,0 +1,1758 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains an engine that handles GPG subprocess control and I/O.
 * PHP's process manipulation functions are used to handle the GPG subprocess.
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: Engine.php 302822 2010-08-26 17:30:57Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
/**
 * Crypt_GPG base class.
 */
require_once 'Crypt/GPG.php';
/**
 * GPG exception classes.
 */
require_once 'Crypt/GPG/Exceptions.php';
/**
 * Standard PEAR exception is used if GPG binary is not found.
 */
require_once 'PEAR/Exception.php';
// {{{ class Crypt_GPG_Engine
/**
 * Native PHP Crypt_GPG I/O engine
 *
 * This class is used internally by Crypt_GPG and does not need be used
 * directly. See the {@link Crypt_GPG} class for end-user API.
 *
 * This engine uses PHP's native process control functions to directly control
 * the GPG process. The GPG executable is required to be on the system.
 *
 * All data is passed to the GPG subprocess using file descriptors. This is the
 * most secure method of passing data to the GPG subprocess.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_Engine
{
    // {{{ constants
    /**
     * Size of data chunks that are sent to and retrieved from the IPC pipes.
     *
     * PHP reads 8192 bytes. If this is set to less than 8192, PHP reads 8192
     * and buffers the rest so we might as well just read 8192.
     *
     * Using values other than 8192 also triggers PHP bugs.
     *
     * @see http://bugs.php.net/bug.php?id=35224
     */
    const CHUNK_SIZE = 8192;
    /**
     * Standard input file descriptor. This is used to pass data to the GPG
     * process.
     */
    const FD_INPUT = 0;
    /**
     * Standard output file descriptor. This is used to receive normal output
     * from the GPG process.
     */
    const FD_OUTPUT = 1;
    /**
     * Standard output file descriptor. This is used to receive error output
     * from the GPG process.
     */
    const FD_ERROR = 2;
    /**
     * GPG status output file descriptor. The status file descriptor outputs
     * detailed information for many GPG commands. See the second section of
     * the file <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG package} for a detailed
     * description of GPG's status output.
     */
    const FD_STATUS = 3;
    /**
     * Command input file descriptor. This is used for methods requiring
     * passphrases.
     */
    const FD_COMMAND = 4;
    /**
     * Extra message input file descriptor. This is used for passing signed
     * data when verifying a detached signature.
     */
    const FD_MESSAGE = 5;
    /**
     * Minimum version of GnuPG that is supported.
     */
    const MIN_VERSION = '1.0.2';
    // }}}
    // {{{ private class properties
    /**
     * Whether or not to use debugging mode
     *
     * When set to true, every GPG command is echoed before it is run. Sensitive
     * data is always handled using pipes and is not specified as part of the
     * command. As a result, sensitive data is never displayed when debug is
     * enabled. Sensitive data includes private key data and passphrases.
     *
     * Debugging is off by default.
     *
     * @var boolean
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_debug = false;
    /**
     * Location of GPG binary
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     * @see Crypt_GPG_Engine::_getBinary()
     */
    private $_binary = '';
    /**
     * Directory containing the GPG key files
     *
     * This property only contains the path when the <i>homedir</i> option
     * is specified in the constructor.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_homedir = '';
    /**
     * File path of the public keyring
     *
     * This property only contains the file path when the <i>public_keyring</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_publicKeyring = '';
    /**
     * File path of the private (secret) keyring
     *
     * This property only contains the file path when the <i>private_keyring</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_privateKeyring = '';
    /**
     * File path of the trust database
     *
     * This property only contains the file path when the <i>trust_db</i>
     * option is specified in the constructor.
     *
     * If the specified file path starts with <kbd>~/</kbd>, the path is
     * relative to the <i>homedir</i> if specified, otherwise to
     * <kbd>~/.gnupg</kbd>.
     *
     * @var string
     * @see Crypt_GPG_Engine::__construct()
     */
    private $_trustDb = '';
    /**
     * Array of pipes used for communication with the GPG binary
     *
     * This is an array of file descriptor resources.
     *
     * @var array
     */
    private $_pipes = array();
    /**
     * Array of currently opened pipes
     *
     * This array is used to keep track of remaining opened pipes so they can
     * be closed when the GPG subprocess is finished. This array is a subset of
     * the {@link Crypt_GPG_Engine::$_pipes} array and contains opened file
     * descriptor resources.
     *
     * @var array
     * @see Crypt_GPG_Engine::_closePipe()
     */
    private $_openPipes = array();
    /**
     * A handle for the GPG process
     *
     * @var resource
     */
    private $_process = null;
    /**
     * Whether or not the operating system is Darwin (OS X)
     *
     * @var boolean
     */
    private $_isDarwin = false;
    /**
     * Commands to be sent to GPG's command input stream
     *
     * @var string
     * @see Crypt_GPG_Engine::sendCommand()
     */
    private $_commandBuffer = '';
    /**
     * Array of status line handlers
     *
     * @var array
     * @see Crypt_GPG_Engine::addStatusHandler()
     */
    private $_statusHandlers = array();
    /**
     * Array of error line handlers
     *
     * @var array
     * @see Crypt_GPG_Engine::addErrorHandler()
     */
    private $_errorHandlers = array();
    /**
     * The error code of the current operation
     *
     * @var integer
     * @see Crypt_GPG_Engine::getErrorCode()
     */
    private $_errorCode = Crypt_GPG::ERROR_NONE;
    /**
     * File related to the error code of the current operation
     *
     * @var string
     * @see Crypt_GPG_Engine::getErrorFilename()
     */
    private $_errorFilename = '';
    /**
     * Key id related to the error code of the current operation
     *
     * @var string
     * @see Crypt_GPG_Engine::getErrorKeyId()
     */
    private $_errorkeyId = '';
    /**
     * The number of currently needed passphrases
     *
     * If this is not zero when the GPG command is completed, the error code is
     * set to {@link Crypt_GPG::ERROR_MISSING_PASSPHRASE}.
     *
     * @var integer
     */
    private $_needPassphrase = 0;
    /**
     * The input source
     *
     * This is data to send to GPG. Either a string or a stream resource.
     *
     * @var string|resource
     * @see Crypt_GPG_Engine::setInput()
     */
    private $_input = null;
    /**
     * The extra message input source
     *
     * Either a string or a stream resource.
     *
     * @var string|resource
     * @see Crypt_GPG_Engine::setMessage()
     */
    private $_message = null;
    /**
     * The output location
     *
     * This is where the output from GPG is sent. Either a string or a stream
     * resource.
     *
     * @var string|resource
     * @see Crypt_GPG_Engine::setOutput()
     */
    private $_output = '';
    /**
     * The GPG operation to execute
     *
     * @var string
     * @see Crypt_GPG_Engine::setOperation()
     */
    private $_operation;
    /**
     * Arguments for the current operation
     *
     * @var array
     * @see Crypt_GPG_Engine::setOperation()
     */
    private $_arguments = array();
    /**
     * The version number of the GPG binary
     *
     * @var string
     * @see Crypt_GPG_Engine::getVersion()
     */
    private $_version = '';
    /**
     * Cached value indicating whether or not mbstring function overloading is
     * on for strlen
     *
     * This is cached for optimal performance inside the I/O loop.
     *
     * @var boolean
     * @see Crypt_GPG_Engine::_byteLength()
     * @see Crypt_GPG_Engine::_byteSubstring()
     */
    private static $_mbStringOverload = null;
    // }}}
    // {{{ __construct()
    /**
     * Creates a new GPG engine
     *
     * Available options are:
     *
     * - <kbd>string  homedir</kbd>        - the directory where the GPG
     *                                       keyring files are stored. If not
     *                                       specified, Crypt_GPG uses the
     *                                       default of <kbd>~/.gnupg</kbd>.
     * - <kbd>string  publicKeyring</kbd>  - the file path of the public
     *                                       keyring. Use this if the public
     *                                       keyring is not in the homedir, or
     *                                       if the keyring is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       keyring with this option
     *                                       (/foo/bar/pubring.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  privateKeyring</kbd> - the file path of the private
     *                                       keyring. Use this if the private
     *                                       keyring is not in the homedir, or
     *                                       if the keyring is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       keyring with this option
     *                                       (/foo/bar/secring.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  trustDb</kbd>        - the file path of the web-of-trust
     *                                       database. Use this if the trust
     *                                       database is not in the homedir, or
     *                                       if the database is in a directory
     *                                       not writable by the process
     *                                       invoking GPG (like Apache). Then
     *                                       you can specify the path to the
     *                                       trust database with this option
     *                                       (/foo/bar/trustdb.gpg), and specify
     *                                       a writable directory (like /tmp)
     *                                       using the <i>homedir</i> option.
     * - <kbd>string  binary</kbd>         - the location of the GPG binary. If
     *                                       not specified, the driver attempts
     *                                       to auto-detect the GPG binary
     *                                       location using a list of known
     *                                       default locations for the current
     *                                       operating system. The option
     *                                       <kbd>gpgBinary</kbd> is a
     *                                       deprecated alias for this option.
     * - <kbd>boolean debug</kbd>          - whether or not to use debug mode.
     *                                       When debug mode is on, all
     *                                       communication to and from the GPG
     *                                       subprocess is logged. This can be
     *                                       useful to diagnose errors when
     *                                       using Crypt_GPG.
     *
     * @param array $options optional. An array of options used to create the
     *                       GPG object. All options are optional and are
     *                       represented as key-value pairs.
     *
     * @throws Crypt_GPG_FileException if the <kbd>homedir</kbd> does not exist
     *         and cannot be created. This can happen if <kbd>homedir</kbd> is
     *         not specified, Crypt_GPG is run as the web user, and the web
     *         user has no home directory. This exception is also thrown if any
     *         of the options <kbd>publicKeyring</kbd>,
     *         <kbd>privateKeyring</kbd> or <kbd>trustDb</kbd> options are
     *         specified but the files do not exist or are are not readable.
     *         This can happen if the user running the Crypt_GPG process (for
     *         example, the Apache user) does not have permission to read the
     *         files.
     *
     * @throws PEAR_Exception if the provided <kbd>binary</kbd> is invalid, or
     *         if no <kbd>binary</kbd> is provided and no suitable binary could
     *         be found.
     */
    public function __construct(array $options = array())
    {
        $this->_isDarwin = (strncmp(strtoupper(PHP_OS), 'DARWIN', 6) === 0);
        // populate mbstring overloading cache if not set
        if (self::$_mbStringOverload === null) {
            self::$_mbStringOverload = (extension_loaded('mbstring')
                && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
        }
        // get homedir
        if (array_key_exists('homedir', $options)) {
            $this->_homedir = (string)$options['homedir'];
        } else {
            // note: this requires the package OS dep exclude 'windows'
            $info = posix_getpwuid(posix_getuid());
            $this->_homedir = $info['dir'].'/.gnupg';
        }
        // attempt to create homedir if it does not exist
        if (!is_dir($this->_homedir)) {
            if (@mkdir($this->_homedir, 0777, true)) {
                // Set permissions on homedir. Parent directories are created
                // with 0777, homedir is set to 0700.
                chmod($this->_homedir, 0700);
            } else {
                throw new Crypt_GPG_FileException('The \'homedir\' "' .
                    $this->_homedir . '" is not readable or does not exist '.
                    'and cannot be created. This can happen if \'homedir\' '.
                    'is not specified in the Crypt_GPG options, Crypt_GPG is '.
                    'run as the web user, and the web user has no home '.
                    'directory.',
                    0, $this->_homedir);
            }
        }
        // get binary
        if (array_key_exists('binary', $options)) {
            $this->_binary = (string)$options['binary'];
        } elseif (array_key_exists('gpgBinary', $options)) {
            // deprecated alias
            $this->_binary = (string)$options['gpgBinary'];
        } else {
            $this->_binary = $this->_getBinary();
        }
        if ($this->_binary == '' || !is_executable($this->_binary)) {
            throw new PEAR_Exception('GPG binary not found. If you are sure '.
                'the GPG binary is installed, please specify the location of '.
                'the GPG binary using the \'binary\' driver option.');
        }
        /*
         * Note:
         *
         * Normally, GnuPG expects keyrings to be in the homedir and expects
         * to be able to write temporary files in the homedir. Sometimes,
         * keyrings are not in the homedir, or location of the keyrings does
         * not allow writing temporary files. In this case, the <i>homedir</i>
         * option by itself is not enough to specify the keyrings because GnuPG
         * can not write required temporary files. Additional options are
         * provided so you can specify the location of the keyrings separately
         * from the homedir.
         */
        // get public keyring
        if (array_key_exists('publicKeyring', $options)) {
            $this->_publicKeyring = (string)$options['publicKeyring'];
            if (!is_readable($this->_publicKeyring)) {
                 throw new Crypt_GPG_FileException('The \'publicKeyring\' "' .
                    $this->_publicKeyring . '" does not exist or is ' .
                    'not readable. Check the location and ensure the file ' .
                    'permissions are correct.', 0, $this->_publicKeyring);
            }
        }
        // get private keyring
        if (array_key_exists('privateKeyring', $options)) {
            $this->_privateKeyring = (string)$options['privateKeyring'];
            if (!is_readable($this->_privateKeyring)) {
                 throw new Crypt_GPG_FileException('The \'privateKeyring\' "' .
                    $this->_privateKeyring . '" does not exist or is ' .
                    'not readable. Check the location and ensure the file ' .
                    'permissions are correct.', 0, $this->_privateKeyring);
            }
        }
        // get trust database
        if (array_key_exists('trustDb', $options)) {
            $this->_trustDb = (string)$options['trustDb'];
            if (!is_readable($this->_trustDb)) {
                 throw new Crypt_GPG_FileException('The \'trustDb\' "' .
                    $this->_trustDb . '" does not exist or is not readable. ' .
                    'Check the location and ensure the file permissions are ' .
                    'correct.', 0, $this->_trustDb);
            }
        }
        if (array_key_exists('debug', $options)) {
            $this->_debug = (boolean)$options['debug'];
        }
    }
    // }}}
    // {{{ __destruct()
    /**
     * Closes open GPG subprocesses when this object is destroyed
     *
     * Subprocesses should never be left open by this class unless there is
     * an unknown error and unexpected script termination occurs.
     */
    public function __destruct()
    {
        $this->_closeSubprocess();
    }
    // }}}
    // {{{ addErrorHandler()
    /**
     * Adds an error handler method
     *
     * The method is run every time a new error line is received from the GPG
     * subprocess. The handler method must accept the error line to be handled
     * as its first parameter.
     *
     * @param callback $callback the callback method to use.
     * @param array    $args     optional. Additional arguments to pass as
     *                           parameters to the callback method.
     *
     * @return void
     */
    public function addErrorHandler($callback, array $args = array())
    {
        $this->_errorHandlers[] = array(
            'callback' => $callback,
            'args'     => $args
        );
    }
    // }}}
    // {{{ addStatusHandler()
    /**
     * Adds a status handler method
     *
     * The method is run every time a new status line is received from the
     * GPG subprocess. The handler method must accept the status line to be
     * handled as its first parameter.
     *
     * @param callback $callback the callback method to use.
     * @param array    $args     optional. Additional arguments to pass as
     *                           parameters to the callback method.
     *
     * @return void
     */
    public function addStatusHandler($callback, array $args = array())
    {
        $this->_statusHandlers[] = array(
            'callback' => $callback,
            'args'     => $args
        );
    }
    // }}}
    // {{{ sendCommand()
    /**
     * Sends a command to the GPG subprocess over the command file-descriptor
     * pipe
     *
     * @param string $command the command to send.
     *
     * @return void
     *
     * @sensitive $command
     */
    public function sendCommand($command)
    {
        if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) {
            $this->_commandBuffer .= $command . PHP_EOL;
        }
    }
    // }}}
    // {{{ reset()
    /**
     * Resets the GPG engine, preparing it for a new operation
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::run()
     * @see Crypt_GPG_Engine::setOperation()
     */
    public function reset()
    {
        $this->_operation      = '';
        $this->_arguments      = array();
        $this->_input          = null;
        $this->_message        = null;
        $this->_output         = '';
        $this->_errorCode      = Crypt_GPG::ERROR_NONE;
        $this->_needPassphrase = 0;
        $this->_commandBuffer  = '';
        $this->_statusHandlers = array();
        $this->_errorHandlers  = array();
        $this->addStatusHandler(array($this, '_handleErrorStatus'));
        $this->addErrorHandler(array($this, '_handleErrorError'));
        if ($this->_debug) {
            $this->addStatusHandler(array($this, '_handleDebugStatus'));
            $this->addErrorHandler(array($this, '_handleDebugError'));
        }
    }
    // }}}
    // {{{ run()
    /**
     * Runs the current GPG operation
     *
     * This creates and manages the GPG subprocess.
     *
     * The operation must be set with {@link Crypt_GPG_Engine::setOperation()}
     * before this method is called.
     *
     * @return void
     *
     * @throws Crypt_GPG_InvalidOperationException if no operation is specified.
     *
     * @see Crypt_GPG_Engine::reset()
     * @see Crypt_GPG_Engine::setOperation()
     */
    public function run()
    {
        if ($this->_operation === '') {
            throw new Crypt_GPG_InvalidOperationException('No GPG operation ' .
                'specified. Use Crypt_GPG_Engine::setOperation() before ' .
                'calling Crypt_GPG_Engine::run().');
        }
        $this->_openSubprocess();
        $this->_process();
        $this->_closeSubprocess();
    }
    // }}}
    // {{{ getErrorCode()
    /**
     * Gets the error code of the last executed operation
     *
     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
     * been executed.
     *
     * @return integer the error code of the last executed operation.
     */
    public function getErrorCode()
    {
        return $this->_errorCode;
    }
    // }}}
    // {{{ getErrorFilename()
    /**
     * Gets the file related to the error code of the last executed operation
     *
     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
     * been executed. If there is no file related to the error, an empty string
     * is returned.
     *
     * @return string the file related to the error code of the last executed
     *                operation.
     */
    public function getErrorFilename()
    {
        return $this->_errorFilename;
    }
    // }}}
    // {{{ getErrorKeyId()
    /**
     * Gets the key id related to the error code of the last executed operation
     *
     * This value is only meaningful after {@link Crypt_GPG_Engine::run()} has
     * been executed. If there is no key id related to the error, an empty
     * string is returned.
     *
     * @return string the key id related to the error code of the last executed
     *                operation.
     */
    public function getErrorKeyId()
    {
        return $this->_errorKeyId;
    }
    // }}}
    // {{{ setInput()
    /**
     * Sets the input source for the current GPG operation
     *
     * @param string|resource &$input either a reference to the string
     *                                containing the input data or an open
     *                                stream resource containing the input
     *                                data.
     *
     * @return void
     */
    public function setInput(&$input)
    {
        $this->_input =& $input;
    }
    // }}}
    // {{{ setMessage()
    /**
     * Sets the message source for the current GPG operation
     *
     * Detached signature data should be specified here.
     *
     * @param string|resource &$message either a reference to the string
     *                                  containing the message data or an open
     *                                  stream resource containing the message
     *                                  data.
     *
     * @return void
     */
    public function setMessage(&$message)
    {
        $this->_message =& $message;
    }
    // }}}
    // {{{ setOutput()
    /**
     * Sets the output destination for the current GPG operation
     *
     * @param string|resource &$output either a reference to the string in
     *                                 which to store GPG output or an open
     *                                 stream resource to which the output data
     *                                 should be written.
     *
     * @return void
     */
    public function setOutput(&$output)
    {
        $this->_output =& $output;
    }
    // }}}
    // {{{ setOperation()
    /**
     * Sets the operation to perform
     *
     * @param string $operation the operation to perform. This should be one
     *                          of GPG's operations. For example,
     *                          <kbd>--encrypt</kbd>, <kbd>--decrypt</kbd>,
     *                          <kbd>--sign</kbd>, etc.
     * @param array  $arguments optional. Additional arguments for the GPG
     *                          subprocess. See the GPG manual for specific
     *                          values.
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::reset()
     * @see Crypt_GPG_Engine::run()
     */
    public function setOperation($operation, array $arguments = array())
    {
        $this->_operation = $operation;
        $this->_arguments = $arguments;
    }
    // }}}
    // {{{ getVersion()
    /**
     * Gets the version of the GnuPG binary
     *
     * @return string a version number string containing the version of GnuPG
     *                being used. This value is suitable to use with PHP's
     *                version_compare() function.
     *
     * @throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
     *         Use the <kbd>debug</kbd> option and file a bug report if these
     *         exceptions occur.
     *
     * @throws Crypt_GPG_UnsupportedException if the provided binary is not
     *         GnuPG or if the GnuPG version is less than 1.0.2.
     */
    public function getVersion()
    {
        if ($this->_version == '') {
            $options = array(
                'homedir' => $this->_homedir,
                'binary'  => $this->_binary,
                'debug'   => $this->_debug
            );
            $engine = new self($options);
            $info   = '';
            // Set a garbage version so we do not end up looking up the version
            // recursively.
            $engine->_version = '1.0.0';
            $engine->reset();
            $engine->setOutput($info);
            $engine->setOperation('--version');
            $engine->run();
            $code = $this->getErrorCode();
            if ($code !== Crypt_GPG::ERROR_NONE) {
                throw new Crypt_GPG_Exception(
                    'Unknown error getting GnuPG version information. Please ' .
                    'use the \'debug\' option when creating the Crypt_GPG ' .
                    'object, and file a bug report at ' . Crypt_GPG::BUG_URI,
                    $code);
            }
            $matches    = array();
            $expression = '/gpg \(GnuPG\) (\S+)/';
            if (preg_match($expression, $info, $matches) === 1) {
                $this->_version = $matches[1];
            } else {
                throw new Crypt_GPG_Exception(
                    'No GnuPG version information provided by the binary "' .
                    $this->_binary . '". Are you sure it is GnuPG?');
            }
            if (version_compare($this->_version, self::MIN_VERSION, 'lt')) {
                throw new Crypt_GPG_Exception(
                    'The version of GnuPG being used (' . $this->_version .
                    ') is not supported by Crypt_GPG. The minimum version ' .
                    'required by Crypt_GPG is ' . self::MIN_VERSION);
            }
        }
        return $this->_version;
    }
    // }}}
    // {{{ _handleErrorStatus()
    /**
     * Handles error values in the status output from GPG
     *
     * This method is responsible for setting the
     * {@link Crypt_GPG_Engine::$_errorCode}. See <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
     * information on GPG's status output.
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    private function _handleErrorStatus($line)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'BAD_PASSPHRASE':
            $this->_errorCode = Crypt_GPG::ERROR_BAD_PASSPHRASE;
            break;
        case 'MISSING_PASSPHRASE':
            $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
            break;
        case 'NODATA':
            $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
            break;
        case 'DELETE_PROBLEM':
            if ($tokens[1] == '1') {
                $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
                break;
            } elseif ($tokens[1] == '2') {
                $this->_errorCode = Crypt_GPG::ERROR_DELETE_PRIVATE_KEY;
                break;
            }
            break;
        case 'IMPORT_RES':
            if ($tokens[12] > 0) {
                $this->_errorCode = Crypt_GPG::ERROR_DUPLICATE_KEY;
            }
            break;
        case 'NO_PUBKEY':
        case 'NO_SECKEY':
            $this->_errorKeyId = $tokens[1];
            $this->_errorCode  = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            break;
        case 'NEED_PASSPHRASE':
            $this->_needPassphrase++;
            break;
        case 'GOOD_PASSPHRASE':
            $this->_needPassphrase--;
            break;
        case 'EXPSIG':
        case 'EXPKEYSIG':
        case 'REVKEYSIG':
        case 'BADSIG':
            $this->_errorCode = Crypt_GPG::ERROR_BAD_SIGNATURE;
            break;
        }
    }
    // }}}
    // {{{ _handleErrorError()
    /**
     * Handles error values in the error output from GPG
     *
     * This method is responsible for setting the
     * {@link Crypt_GPG_Engine::$_errorCode}.
     *
     * @param string $line the error line to handle.
     *
     * @return void
     */
    private function _handleErrorError($line)
    {
        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
            $pattern = '/no valid OpenPGP data found/';
            if (preg_match($pattern, $line) === 1) {
                $this->_errorCode = Crypt_GPG::ERROR_NO_DATA;
            }
        }
        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
            $pattern = '/No secret key|secret key not available/';
            if (preg_match($pattern, $line) === 1) {
                $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            }
        }
        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
            $pattern = '/No public key|public key not found/';
            if (preg_match($pattern, $line) === 1) {
                $this->_errorCode = Crypt_GPG::ERROR_KEY_NOT_FOUND;
            }
        }
        if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
            $matches = array();
            $pattern = '/can\'t (?:access|open) `(.*?)\'/';
            if (preg_match($pattern, $line, $matches) === 1) {
                $this->_errorFilename = $matches[1];
                $this->_errorCode = Crypt_GPG::ERROR_FILE_PERMISSIONS;
            }
        }
    }
    // }}}
    // {{{ _handleDebugStatus()
    /**
     * Displays debug output for status lines
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    private function _handleDebugStatus($line)
    {
        $this->_debug('STATUS: ' . $line);
    }
    // }}}
    // {{{ _handleDebugError()
    /**
     * Displays debug output for error lines
     *
     * @param string $line the error line to handle.
     *
     * @return void
     */
    private function _handleDebugError($line)
    {
        $this->_debug('ERROR: ' . $line);
    }
    // }}}
    // {{{ _process()
    /**
     * Performs internal streaming operations for the subprocess using either
     * strings or streams as input / output points
     *
     * This is the main I/O loop for streaming to and from the GPG subprocess.
     *
     * The implementation of this method is verbose mainly for performance
     * reasons. Adding streams to a lookup array and looping the array inside
     * the main I/O loop would be siginficantly slower for large streams.
     *
     * @return void
     *
     * @throws Crypt_GPG_Exception if there is an error selecting streams for
     *         reading or writing. If this occurs, please file a bug report at
     *         http://pear.php.net/bugs/report.php?package=Crypt_GPG.
     */
    private function _process()
    {
        $this->_debug('BEGIN PROCESSING');
        $this->_commandBuffer = '';    // buffers input to GPG
        $messageBuffer        = '';    // buffers input to GPG
        $inputBuffer          = '';    // buffers input to GPG
        $outputBuffer         = '';    // buffers output from GPG
        $statusBuffer         = '';    // buffers output from GPG
        $errorBuffer          = '';    // buffers output from GPG
        $inputComplete        = false; // input stream is completely buffered
        $messageComplete      = false; // message stream is completely buffered
        if (is_string($this->_input)) {
            $inputBuffer   = $this->_input;
            $inputComplete = true;
        }
        if (is_string($this->_message)) {
            $messageBuffer   = $this->_message;
            $messageComplete = true;
        }
        if (is_string($this->_output)) {
            $outputBuffer =& $this->_output;
        }
        // convenience variables
        $fdInput   = $this->_pipes[self::FD_INPUT];
        $fdOutput  = $this->_pipes[self::FD_OUTPUT];
        $fdError   = $this->_pipes[self::FD_ERROR];
        $fdStatus  = $this->_pipes[self::FD_STATUS];
        $fdCommand = $this->_pipes[self::FD_COMMAND];
        $fdMessage = $this->_pipes[self::FD_MESSAGE];
        while (true) {
            $inputStreams     = array();
            $outputStreams    = array();
            $exceptionStreams = array();
            // set up input streams
            if (is_resource($this->_input) && !$inputComplete) {
                if (feof($this->_input)) {
                    $inputComplete = true;
                } else {
                    $inputStreams[] = $this->_input;
                }
            }
            // close GPG input pipe if there is no more data
            if ($inputBuffer == '' && $inputComplete) {
                $this->_debug('=> closing GPG input pipe');
                $this->_closePipe(self::FD_INPUT);
            }
            if (is_resource($this->_message) && !$messageComplete) {
                if (feof($this->_message)) {
                    $messageComplete = true;
                } else {
                    $inputStreams[] = $this->_message;
                }
            }
            // close GPG message pipe if there is no more data
            if ($messageBuffer == '' && $messageComplete) {
                $this->_debug('=> closing GPG message pipe');
                $this->_closePipe(self::FD_MESSAGE);
            }
            if (!feof($fdOutput)) {
                $inputStreams[] = $fdOutput;
            }
            if (!feof($fdStatus)) {
                $inputStreams[] = $fdStatus;
            }
            if (!feof($fdError)) {
                $inputStreams[] = $fdError;
            }
            // set up output streams
            if ($outputBuffer != '' && is_resource($this->_output)) {
                $outputStreams[] = $this->_output;
            }
            if ($this->_commandBuffer != '') {
                $outputStreams[] = $fdCommand;
            }
            if ($messageBuffer != '') {
                $outputStreams[] = $fdMessage;
            }
            if ($inputBuffer != '') {
                $outputStreams[] = $fdInput;
            }
            // no streams left to read or write, we're all done
            if (count($inputStreams) === 0 && count($outputStreams) === 0) {
                break;
            }
            $this->_debug('selecting streams');
            $ready = stream_select(
                $inputStreams,
                $outputStreams,
                $exceptionStreams,
                null
            );
            $this->_debug('=> got ' . $ready);
            if ($ready === false) {
                throw new Crypt_GPG_Exception(
                    'Error selecting stream for communication with GPG ' .
                    'subprocess. Please file a bug report at: ' .
                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
            }
            if ($ready === 0) {
                throw new Crypt_GPG_Exception(
                    'stream_select() returned 0. This can not happen! Please ' .
                    'file a bug report at: ' .
                    'http://pear.php.net/bugs/report.php?package=Crypt_GPG');
            }
            // write input (to GPG)
            if (in_array($fdInput, $outputStreams)) {
                $this->_debug('GPG is ready for input');
                $chunk = self::_byteSubstring(
                    $inputBuffer,
                    0,
                    self::CHUNK_SIZE
                );
                $length = self::_byteLength($chunk);
                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG input'
                );
                $length = fwrite($fdInput, $chunk, $length);
                $this->_debug('=> wrote ' . $length . ' bytes');
                $inputBuffer = self::_byteSubstring(
                    $inputBuffer,
                    $length
                );
            }
            // read input (from PHP stream)
            if (in_array($this->_input, $inputStreams)) {
                $this->_debug('input stream is ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from input stream'
                );
                $chunk        = fread($this->_input, self::CHUNK_SIZE);
                $length       = self::_byteLength($chunk);
                $inputBuffer .= $chunk;
                $this->_debug('=> read ' . $length . ' bytes');
            }
            // write message (to GPG)
            if (in_array($fdMessage, $outputStreams)) {
                $this->_debug('GPG is ready for message data');
                $chunk = self::_byteSubstring(
                    $messageBuffer,
                    0,
                    self::CHUNK_SIZE
                );
                $length = self::_byteLength($chunk);
                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG message'
                );
                $length = fwrite($fdMessage, $chunk, $length);
                $this->_debug('=> wrote ' . $length . ' bytes');
                $messageBuffer = self::_byteSubstring($messageBuffer, $length);
            }
            // read message (from PHP stream)
            if (in_array($this->_message, $inputStreams)) {
                $this->_debug('message stream is ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from message stream'
                );
                $chunk          = fread($this->_message, self::CHUNK_SIZE);
                $length         = self::_byteLength($chunk);
                $messageBuffer .= $chunk;
                $this->_debug('=> read ' . $length . ' bytes');
            }
            // read output (from GPG)
            if (in_array($fdOutput, $inputStreams)) {
                $this->_debug('GPG output stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG output'
                );
                $chunk         = fread($fdOutput, self::CHUNK_SIZE);
                $length        = self::_byteLength($chunk);
                $outputBuffer .= $chunk;
                $this->_debug('=> read ' . $length . ' bytes');
            }
            // write output (to PHP stream)
            if (in_array($this->_output, $outputStreams)) {
                $this->_debug('output stream is ready for data');
                $chunk = self::_byteSubstring(
                    $outputBuffer,
                    0,
                    self::CHUNK_SIZE
                );
                $length = self::_byteLength($chunk);
                $this->_debug(
                    '=> about to write ' . $length . ' bytes to output stream'
                );
                $length = fwrite($this->_output, $chunk, $length);
                $this->_debug('=> wrote ' . $length . ' bytes');
                $outputBuffer = self::_byteSubstring($outputBuffer, $length);
            }
            // read error (from GPG)
            if (in_array($fdError, $inputStreams)) {
                $this->_debug('GPG error stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG error'
                );
                $chunk        = fread($fdError, self::CHUNK_SIZE);
                $length       = self::_byteLength($chunk);
                $errorBuffer .= $chunk;
                $this->_debug('=> read ' . $length . ' bytes');
                // pass lines to error handlers
                while (($pos = strpos($errorBuffer, PHP_EOL)) !== false) {
                    $line = self::_byteSubstring($errorBuffer, 0, $pos);
                    foreach ($this->_errorHandlers as $handler) {
                        array_unshift($handler['args'], $line);
                        call_user_func_array(
                            $handler['callback'],
                            $handler['args']
                        );
                        array_shift($handler['args']);
                    }
                    $errorBuffer = self::_byteSubString(
                        $errorBuffer,
                        $pos + self::_byteLength(PHP_EOL)
                    );
                }
            }
            // read status (from GPG)
            if (in_array($fdStatus, $inputStreams)) {
                $this->_debug('GPG status stream ready for reading');
                $this->_debug(
                    '=> about to read ' . self::CHUNK_SIZE .
                    ' bytes from GPG status'
                );
                $chunk         = fread($fdStatus, self::CHUNK_SIZE);
                $length        = self::_byteLength($chunk);
                $statusBuffer .= $chunk;
                $this->_debug('=> read ' . $length . ' bytes');
                // pass lines to status handlers
                while (($pos = strpos($statusBuffer, PHP_EOL)) !== false) {
                    $line = self::_byteSubstring($statusBuffer, 0, $pos);
                    // only pass lines beginning with magic prefix
                    if (self::_byteSubstring($line, 0, 9) == '[GNUPG:] ') {
                        $line = self::_byteSubstring($line, 9);
                        foreach ($this->_statusHandlers as $handler) {
                            array_unshift($handler['args'], $line);
                            call_user_func_array(
                                $handler['callback'],
                                $handler['args']
                            );
                            array_shift($handler['args']);
                        }
                    }
                    $statusBuffer = self::_byteSubString(
                        $statusBuffer,
                        $pos + self::_byteLength(PHP_EOL)
                    );
                }
            }
            // write command (to GPG)
            if (in_array($fdCommand, $outputStreams)) {
                $this->_debug('GPG is ready for command data');
                // send commands
                $chunk = self::_byteSubstring(
                    $this->_commandBuffer,
                    0,
                    self::CHUNK_SIZE
                );
                $length = self::_byteLength($chunk);
                $this->_debug(
                    '=> about to write ' . $length . ' bytes to GPG command'
                );
                $length = fwrite($fdCommand, $chunk, $length);
                $this->_debug('=> wrote ' . $length);
                $this->_commandBuffer = self::_byteSubstring(
                    $this->_commandBuffer,
                    $length
                );
            }
        } // end loop while streams are open
        $this->_debug('END PROCESSING');
    }
    // }}}
    // {{{ _openSubprocess()
    /**
     * Opens an internal GPG subprocess for the current operation
     *
     * Opens a GPG subprocess, then connects the subprocess to some pipes. Sets
     * the private class property {@link Crypt_GPG_Engine::$_process} to
     * the new subprocess.
     *
     * @return void
     *
     * @throws Crypt_GPG_OpenSubprocessException if the subprocess could not be
     *         opened.
     *
     * @see Crypt_GPG_Engine::setOperation()
     * @see Crypt_GPG_Engine::_closeSubprocess()
     * @see Crypt_GPG_Engine::$_process
     */
    private function _openSubprocess()
    {
        $version = $this->getVersion();
        $env = $_ENV;
        // Newer versions of GnuPG return localized results. Crypt_GPG only
        // works with English, so set the locale to 'C' for the subprocess.
        $env['LC_ALL'] = 'C';
        $commandLine = $this->_binary;
        $defaultArguments = array(
            '--status-fd ' . escapeshellarg(self::FD_STATUS),
            '--command-fd ' . escapeshellarg(self::FD_COMMAND),
            '--no-secmem-warning',
            '--no-tty',
            '--no-default-keyring', // ignored if keying files are not specified
            '--no-options'          // prevent creation of ~/.gnupg directory
        );
        if (version_compare($version, '1.0.7', 'ge')) {
            if (version_compare($version, '2.0.0', 'lt')) {
                $defaultArguments[] = '--no-use-agent';
            }
            $defaultArguments[] = '--no-permission-warning';
        }
        if (version_compare($version, '1.4.2', 'ge')) {
            $defaultArguments[] = '--exit-on-status-write-error';
        }
        if (version_compare($version, '1.3.2', 'ge')) {
            $defaultArguments[] = '--trust-model always';
        } else {
            $defaultArguments[] = '--always-trust';
        }
        $arguments = array_merge($defaultArguments, $this->_arguments);
        if ($this->_homedir) {
            $arguments[] = '--homedir ' . escapeshellarg($this->_homedir);
            // the random seed file makes subsequent actions faster so only
            // disable it if we have to.
            if (!is_writeable($this->_homedir)) {
                $arguments[] = '--no-random-seed-file';
            }
        }
        if ($this->_publicKeyring) {
            $arguments[] = '--keyring ' . escapeshellarg($this->_publicKeyring);
        }
        if ($this->_privateKeyring) {
            $arguments[] = '--secret-keyring ' .
                escapeshellarg($this->_privateKeyring);
        }
        if ($this->_trustDb) {
            $arguments[] = '--trustdb-name ' . escapeshellarg($this->_trustDb);
        }
        $commandLine .= ' ' . implode(' ', $arguments) . ' ' .
            $this->_operation;
        // Binary operations will not work on Windows with PHP < 5.2.6. This is
        // in case stream_select() ever works on Windows.
        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';
        $descriptorSpec = array(
            self::FD_INPUT   => array('pipe', $rb), // stdin
            self::FD_OUTPUT  => array('pipe', $wb), // stdout
            self::FD_ERROR   => array('pipe', $wb), // stderr
            self::FD_STATUS  => array('pipe', $wb), // status
            self::FD_COMMAND => array('pipe', $rb), // command
            self::FD_MESSAGE => array('pipe', $rb)  // message
        );
        $this->_debug('OPENING SUBPROCESS WITH THE FOLLOWING COMMAND:');
        $this->_debug($commandLine);
        $this->_process = proc_open(
            $commandLine,
            $descriptorSpec,
            $this->_pipes,
            null,
            $env,
            array('binary_pipes' => true)
        );
        if (!is_resource($this->_process)) {
            throw new Crypt_GPG_OpenSubprocessException(
                'Unable to open GPG subprocess.', 0, $commandLine);
        }
        $this->_openPipes = $this->_pipes;
        $this->_errorCode = Crypt_GPG::ERROR_NONE;
    }
    // }}}
    // {{{ _closeSubprocess()
    /**
     * Closes a the internal GPG subprocess
     *
     * Closes the internal GPG subprocess. Sets the private class property
     * {@link Crypt_GPG_Engine::$_process} to null.
     *
     * @return void
     *
     * @see Crypt_GPG_Engine::_openSubprocess()
     * @see Crypt_GPG_Engine::$_process
     */
    private function _closeSubprocess()
    {
        if (is_resource($this->_process)) {
            $this->_debug('CLOSING SUBPROCESS');
            // close remaining open pipes
            foreach (array_keys($this->_openPipes) as $pipeNumber) {
                $this->_closePipe($pipeNumber);
            }
            $exitCode = proc_close($this->_process);
            if ($exitCode != 0) {
                $this->_debug(
                    '=> subprocess returned an unexpected exit code: ' .
                    $exitCode
                );
                if ($this->_errorCode === Crypt_GPG::ERROR_NONE) {
                    if ($this->_needPassphrase > 0) {
                        $this->_errorCode = Crypt_GPG::ERROR_MISSING_PASSPHRASE;
                    } else {
                        $this->_errorCode = Crypt_GPG::ERROR_UNKNOWN;
                    }
                }
            }
            $this->_process = null;
            $this->_pipes   = array();
        }
    }
    // }}}
    // {{{ _closePipe()
    /**
     * Closes an opened pipe used to communicate with the GPG subprocess
     *
     * If the pipe is already closed, it is ignored. If the pipe is open, it
     * is flushed and then closed.
     *
     * @param integer $pipeNumber the file descriptor number of the pipe to
     *                            close.
     *
     * @return void
     */
    private function _closePipe($pipeNumber)
    {
        $pipeNumber = intval($pipeNumber);
        if (array_key_exists($pipeNumber, $this->_openPipes)) {
            fflush($this->_openPipes[$pipeNumber]);
            fclose($this->_openPipes[$pipeNumber]);
            unset($this->_openPipes[$pipeNumber]);
        }
    }
    // }}}
    // {{{ _getBinary()
    /**
     * Gets the name of the GPG binary for the current operating system
     *
     * This method is called if the '<kbd>binary</kbd>' option is <i>not</i>
     * specified when creating this driver.
     *
     * @return string the name of the GPG binary for the current operating
     *                system. If no suitable binary could be found, an empty
     *                string is returned.
     */
    private function _getBinary()
    {
        $binary = '';
        if ($this->_isDarwin) {
            $binaryFiles = array(
                '/opt/local/bin/gpg', // MacPorts
                '/usr/local/bin/gpg', // Mac GPG
                '/sw/bin/gpg',        // Fink
                '/usr/bin/gpg'
            );
        } else {
            $binaryFiles = array(
                '/usr/bin/gpg',
                '/usr/local/bin/gpg'
            );
        }
        foreach ($binaryFiles as $binaryFile) {
            if (is_executable($binaryFile)) {
                $binary = $binaryFile;
                break;
            }
        }
        return $binary;
    }
    // }}}
    // {{{ _debug()
    /**
     * Displays debug text if debugging is turned on
     *
     * Debugging text is prepended with a debug identifier and echoed to stdout.
     *
     * @param string $text the debugging text to display.
     *
     * @return void
     */
    private function _debug($text)
    {
        if ($this->_debug) {
            if (array_key_exists('SHELL', $_ENV)) {
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
                }
            } else {
                // running on a web server, format debug output nicely
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: <strong>", $line,
                        '</strong><br />', PHP_EOL;
                }
            }
        }
    }
    // }}}
    // {{{ _byteLength()
    /**
     * Gets the length of a string in bytes even if mbstring function
     * overloading is turned on
     *
     * This is used for stream-based communication with the GPG subprocess.
     *
     * @param string $string the string for which to get the length.
     *
     * @return integer the length of the string in bytes.
     *
     * @see Crypt_GPG_Engine::$_mbStringOverload
     */
    private static function _byteLength($string)
    {
        if (self::$_mbStringOverload) {
            return mb_strlen($string, '8bit');
        }
        return strlen((binary)$string);
    }
    // }}}
    // {{{ _byteSubstring()
    /**
     * Gets the substring of a string in bytes even if mbstring function
     * overloading is turned on
     *
     * This is used for stream-based communication with the GPG subprocess.
     *
     * @param string  $string the input string.
     * @param integer $start  the starting point at which to get the substring.
     * @param integer $length optional. The length of the substring.
     *
     * @return string the extracted part of the string. Unlike the default PHP
     *                <kbd>substr()</kbd> function, the returned value is
     *                always a string and never false.
     *
     * @see Crypt_GPG_Engine::$_mbStringOverload
     */
    private static function _byteSubstring($string, $start, $length = null)
    {
        if (self::$_mbStringOverload) {
            if ($length === null) {
                return mb_substr(
                    $string,
                    $start,
                    self::_byteLength($string) - $start, '8bit'
                );
            }
            return mb_substr($string, $start, $length, '8bit');
        }
        if ($length === null) {
            return (string)substr((binary)$string, $start);
        }
        return (string)substr((binary)$string, $start, $length);
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/Exceptions.php
New file
@@ -0,0 +1,473 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Various exception handling classes for Crypt_GPG
 *
 * Crypt_GPG provides an object oriented interface to GNU Privacy
 * Guard (GPG). It requires the GPG executable to be on the system.
 *
 * This file contains various exception classes used by the Crypt_GPG package.
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: Exceptions.php 273745 2009-01-18 05:24:25Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 */
/**
 * PEAR Exception handler and base class
 */
require_once 'PEAR/Exception.php';
// {{{ class Crypt_GPG_Exception
/**
 * An exception thrown by the Crypt_GPG package
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_Exception extends PEAR_Exception
{
}
// }}}
// {{{ class Crypt_GPG_FileException
/**
 * An exception thrown when a file is used in ways it cannot be used
 *
 * For example, if an output file is specified and the file is not writeable, or
 * if an input file is specified and the file is not readable, this exception
 * is thrown.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2007-2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_FileException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * The name of the file that caused this exception
     *
     * @var string
     */
    private $_filename = '';
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_FileException
     *
     * @param string  $message  an error message.
     * @param integer $code     a user defined error code.
     * @param string  $filename the name of the file that caused this exception.
     */
    public function __construct($message, $code = 0, $filename = '')
    {
        $this->_filename = $filename;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getFilename()
    /**
     * Returns the filename of the file that caused this exception
     *
     * @return string the filename of the file that caused this exception.
     *
     * @see Crypt_GPG_FileException::$_filename
     */
    public function getFilename()
    {
        return $this->_filename;
    }
    // }}}
}
// }}}
// {{{ class Crypt_GPG_OpenSubprocessException
/**
 * An exception thrown when the GPG subprocess cannot be opened
 *
 * This exception is thrown when the {@link Crypt_GPG_Engine} tries to open a
 * new subprocess and fails.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_OpenSubprocessException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * The command used to try to open the subprocess
     *
     * @var string
     */
    private $_command = '';
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_OpenSubprocessException
     *
     * @param string  $message an error message.
     * @param integer $code    a user defined error code.
     * @param string  $command the command that was called to open the
     *                         new subprocess.
     *
     * @see Crypt_GPG::_openSubprocess()
     */
    public function __construct($message, $code = 0, $command = '')
    {
        $this->_command = $command;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getCommand()
    /**
     * Returns the contents of the internal _command property
     *
     * @return string the command used to open the subprocess.
     *
     * @see Crypt_GPG_OpenSubprocessException::$_command
     */
    public function getCommand()
    {
        return $this->_command;
    }
    // }}}
}
// }}}
// {{{ class Crypt_GPG_InvalidOperationException
/**
 * An exception thrown when an invalid GPG operation is attempted
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_InvalidOperationException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * The attempted operation
     *
     * @var string
     */
    private $_operation = '';
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_OpenSubprocessException
     *
     * @param string  $message   an error message.
     * @param integer $code      a user defined error code.
     * @param string  $operation the operation.
     */
    public function __construct($message, $code = 0, $operation = '')
    {
        $this->_operation = $operation;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getOperation()
    /**
     * Returns the contents of the internal _operation property
     *
     * @return string the attempted operation.
     *
     * @see Crypt_GPG_InvalidOperationException::$_operation
     */
    public function getOperation()
    {
        return $this->_operation;
    }
    // }}}
}
// }}}
// {{{ class Crypt_GPG_KeyNotFoundException
/**
 * An exception thrown when Crypt_GPG fails to find the key for various
 * operations
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_KeyNotFoundException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * The key identifier that was searched for
     *
     * @var string
     */
    private $_keyId = '';
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_KeyNotFoundException
     *
     * @param string  $message an error message.
     * @param integer $code    a user defined error code.
     * @param string  $keyId   the key identifier of the key.
     */
    public function __construct($message, $code = 0, $keyId= '')
    {
        $this->_keyId = $keyId;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getKeyId()
    /**
     * Gets the key identifier of the key that was not found
     *
     * @return string the key identifier of the key that was not found.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }
    // }}}
}
// }}}
// {{{ class Crypt_GPG_NoDataException
/**
 * An exception thrown when Crypt_GPG cannot find valid data for various
 * operations
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2006 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_NoDataException extends Crypt_GPG_Exception
{
}
// }}}
// {{{ class Crypt_GPG_BadPassphraseException
/**
 * An exception thrown when a required passphrase is incorrect or missing
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2006-2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_BadPassphraseException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * Keys for which the passhprase is missing
     *
     * This contains primary user ids indexed by sub-key id.
     *
     * @var array
     */
    private $_missingPassphrases = array();
    /**
     * Keys for which the passhprase is incorrect
     *
     * This contains primary user ids indexed by sub-key id.
     *
     * @var array
     */
    private $_badPassphrases = array();
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_BadPassphraseException
     *
     * @param string  $message            an error message.
     * @param integer $code               a user defined error code.
     * @param string  $badPassphrases     an array containing user ids of keys
     *                                    for which the passphrase is incorrect.
     * @param string  $missingPassphrases an array containing user ids of keys
     *                                    for which the passphrase is missing.
     */
    public function __construct($message, $code = 0,
        array $badPassphrases = array(), array $missingPassphrases = array()
    ) {
        $this->_badPassphrases     = $badPassphrases;
        $this->_missingPassphrases = $missingPassphrases;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getBadPassphrases()
    /**
     * Gets keys for which the passhprase is incorrect
     *
     * @return array an array of keys for which the passphrase is incorrect.
     *               The array contains primary user ids indexed by the sub-key
     *               id.
     */
    public function getBadPassphrases()
    {
        return $this->_badPassphrases;
    }
    // }}}
    // {{{ getMissingPassphrases()
    /**
     * Gets keys for which the passhprase is missing
     *
     * @return array an array of keys for which the passphrase is missing.
     *               The array contains primary user ids indexed by the sub-key
     *               id.
     */
    public function getMissingPassphrases()
    {
        return $this->_missingPassphrases;
    }
    // }}}
}
// }}}
// {{{ class Crypt_GPG_DeletePrivateKeyException
/**
 * An exception thrown when an attempt is made to delete public key that has an
 * associated private key on the keyring
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 */
class Crypt_GPG_DeletePrivateKeyException extends Crypt_GPG_Exception
{
    // {{{ private class properties
    /**
     * The key identifier the deletion attempt was made upon
     *
     * @var string
     */
    private $_keyId = '';
    // }}}
    // {{{ __construct()
    /**
     * Creates a new Crypt_GPG_DeletePrivateKeyException
     *
     * @param string  $message an error message.
     * @param integer $code    a user defined error code.
     * @param string  $keyId   the key identifier of the public key that was
     *                         attempted to delete.
     *
     * @see Crypt_GPG::deletePublicKey()
     */
    public function __construct($message, $code = 0, $keyId = '')
    {
        $this->_keyId = $keyId;
        parent::__construct($message, $code);
    }
    // }}}
    // {{{ getKeyId()
    /**
     * Gets the key identifier of the key that was not found
     *
     * @return string the key identifier of the key that was not found.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/Key.php
New file
@@ -0,0 +1,223 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Contains a class representing GPG keys
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: Key.php 295621 2010-03-01 04:18:54Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 */
/**
 * Sub-key class definition
 */
require_once 'Crypt/GPG/SubKey.php';
/**
 * User id class definition
 */
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Key
/**
 * A data class for GPG key information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 */
class Crypt_GPG_Key
{
    // {{{ class properties
    /**
     * The user ids associated with this key
     *
     * This is an array of {@link Crypt_GPG_UserId} objects.
     *
     * @var array
     *
     * @see Crypt_GPG_Key::addUserId()
     * @see Crypt_GPG_Key::getUserIds()
     */
    private $_userIds = array();
    /**
     * The subkeys of this key
     *
     * This is an array of {@link Crypt_GPG_SubKey} objects.
     *
     * @var array
     *
     * @see Crypt_GPG_Key::addSubKey()
     * @see Crypt_GPG_Key::getSubKeys()
     */
    private $_subKeys = array();
    // }}}
    // {{{ getSubKeys()
    /**
     * Gets the sub-keys of this key
     *
     * @return array the sub-keys of this key.
     *
     * @see Crypt_GPG_Key::addSubKey()
     */
    public function getSubKeys()
    {
        return $this->_subKeys;
    }
    // }}}
    // {{{ getUserIds()
    /**
     * Gets the user ids of this key
     *
     * @return array the user ids of this key.
     *
     * @see Crypt_GPG_Key::addUserId()
     */
    public function getUserIds()
    {
        return $this->_userIds;
    }
    // }}}
    // {{{ getPrimaryKey()
    /**
     * Gets the primary sub-key of this key
     *
     * The primary key is the first added sub-key.
     *
     * @return Crypt_GPG_SubKey the primary sub-key of this key.
     */
    public function getPrimaryKey()
    {
        $primary_key = null;
        if (count($this->_subKeys) > 0) {
            $primary_key = $this->_subKeys[0];
        }
        return $primary_key;
    }
    // }}}
    // {{{ canSign()
    /**
     * Gets whether or not this key can sign data
     *
     * This key can sign data if any sub-key of this key can sign data.
     *
     * @return boolean true if this key can sign data and false if this key
     *                 cannot sign data.
     */
    public function canSign()
    {
        $canSign = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canSign()) {
                $canSign = true;
                break;
            }
        }
        return $canSign;
    }
    // }}}
    // {{{ canEncrypt()
    /**
     * Gets whether or not this key can encrypt data
     *
     * This key can encrypt data if any sub-key of this key can encrypt data.
     *
     * @return boolean true if this key can encrypt data and false if this
     *                 key cannot encrypt data.
     */
    public function canEncrypt()
    {
        $canEncrypt = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canEncrypt()) {
                $canEncrypt = true;
                break;
            }
        }
        return $canEncrypt;
    }
    // }}}
    // {{{ addSubKey()
    /**
     * Adds a sub-key to this key
     *
     * The first added sub-key will be the primary key of this key.
     *
     * @param Crypt_GPG_SubKey $subKey the sub-key to add.
     *
     * @return Crypt_GPG_Key the current object, for fluent interface.
     */
    public function addSubKey(Crypt_GPG_SubKey $subKey)
    {
        $this->_subKeys[] = $subKey;
        return $this;
    }
    // }}}
    // {{{ addUserId()
    /**
     * Adds a user id to this key
     *
     * @param Crypt_GPG_UserId $userId the user id to add.
     *
     * @return Crypt_GPG_Key the current object, for fluent interface.
     */
    public function addUserId(Crypt_GPG_UserId $userId)
    {
        $this->_userIds[] = $userId;
        return $this;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/Signature.php
New file
@@ -0,0 +1,428 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * A class representing GPG signatures
 *
 * This file contains a data class representing a GPG signature.
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: Signature.php 302773 2010-08-25 14:16:28Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 */
/**
 * User id class definition
 */
require_once 'Crypt/GPG/UserId.php';
// {{{ class Crypt_GPG_Signature
/**
 * A class for GPG signature information
 *
 * This class is used to store the results of the Crypt_GPG::verify() method.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::verify()
 */
class Crypt_GPG_Signature
{
    // {{{ class properties
    /**
     * A base64-encoded string containing a unique id for this signature if
     * this signature has been verified as ok
     *
     * This id is used to prevent replay attacks and is not present for all
     * types of signatures.
     *
     * @var string
     */
    private $_id = '';
    /**
     * The fingerprint of the key used to create the signature
     *
     * @var string
     */
    private $_keyFingerprint = '';
    /**
     * The id of the key used to create the signature
     *
     * @var string
     */
    private $_keyId = '';
    /**
     * The creation date of this signature
     *
     * This is a Unix timestamp.
     *
     * @var integer
     */
    private $_creationDate = 0;
    /**
     * The expiration date of the signature
     *
     * This is a Unix timestamp. If this signature does not expire, this will
     * be zero.
     *
     * @var integer
     */
    private $_expirationDate = 0;
    /**
     * The user id associated with this signature
     *
     * @var Crypt_GPG_UserId
     */
    private $_userId = null;
    /**
     * Whether or not this signature is valid
     *
     * @var boolean
     */
    private $_isValid = false;
    // }}}
    // {{{ __construct()
    /**
     * Creates a new signature
     *
     * Signatures can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string  id</kbd>          - the unique id of this signature.
     * - <kbd>string  fingerprint</kbd> - the fingerprint of the key used to
     *                                    create the signature. The fingerprint
     *                                    should not contain formatting
     *                                    characters.
     * - <kbd>string  keyId</kbd>       - the id of the key used to create the
     *                                    the signature.
     * - <kbd>integer creation</kbd>    - the date the signature was created.
     *                                    This is a UNIX timestamp.
     * - <kbd>integer expiration</kbd>  - the date the signature expired. This
     *                                    is a UNIX timestamp. If the signature
     *                                    does not expire, use 0.
     * - <kbd>boolean valid</kbd>       - whether or not the signature is valid.
     * - <kbd>string  userId</kbd>      - the user id associated with the
     *                                    signature. This may also be a
     *                                    {@link Crypt_GPG_UserId} object.
     *
     * @param Crypt_GPG_Signature|array $signature optional. Either an existing
     *        signature object, which is copied; or an array of initial values.
     */
    public function __construct($signature = null)
    {
        // copy from object
        if ($signature instanceof Crypt_GPG_Signature) {
            $this->_id             = $signature->_id;
            $this->_keyFingerprint = $signature->_keyFingerprint;
            $this->_keyId          = $signature->_keyId;
            $this->_creationDate   = $signature->_creationDate;
            $this->_expirationDate = $signature->_expirationDate;
            $this->_isValid        = $signature->_isValid;
            if ($signature->_userId instanceof Crypt_GPG_UserId) {
                $this->_userId = clone $signature->_userId;
            } else {
                $this->_userId = $signature->_userId;
            }
        }
        // initialize from array
        if (is_array($signature)) {
            if (array_key_exists('id', $signature)) {
                $this->setId($signature['id']);
            }
            if (array_key_exists('fingerprint', $signature)) {
                $this->setKeyFingerprint($signature['fingerprint']);
            }
            if (array_key_exists('keyId', $signature)) {
                $this->setKeyId($signature['keyId']);
            }
            if (array_key_exists('creation', $signature)) {
                $this->setCreationDate($signature['creation']);
            }
            if (array_key_exists('expiration', $signature)) {
                $this->setExpirationDate($signature['expiration']);
            }
            if (array_key_exists('valid', $signature)) {
                $this->setValid($signature['valid']);
            }
            if (array_key_exists('userId', $signature)) {
                $userId = new Crypt_GPG_UserId($signature['userId']);
                $this->setUserId($userId);
            }
        }
    }
    // }}}
    // {{{ getId()
    /**
     * Gets the id of this signature
     *
     * @return string a base64-encoded string containing a unique id for this
     *                signature. This id is used to prevent replay attacks and
     *                is not present for all types of signatures.
     */
    public function getId()
    {
        return $this->_id;
    }
    // }}}
    // {{{ getKeyFingerprint()
    /**
     * Gets the fingerprint of the key used to create this signature
     *
     * @return string the fingerprint of the key used to create this signature.
     */
    public function getKeyFingerprint()
    {
        return $this->_keyFingerprint;
    }
    // }}}
    // {{{ getKeyId()
    /**
     * Gets the id of the key used to create this signature
     *
     * Whereas the fingerprint of the signing key may not always be available
     * (for example if the signature is bad), the id should always be
     * available.
     *
     * @return string the id of the key used to create this signature.
     */
    public function getKeyId()
    {
        return $this->_keyId;
    }
    // }}}
    // {{{ getCreationDate()
    /**
     * Gets the creation date of this signature
     *
     * @return integer the creation date of this signature. This is a Unix
     *                 timestamp.
     */
    public function getCreationDate()
    {
        return $this->_creationDate;
    }
    // }}}
    // {{{ getExpirationDate()
    /**
     * Gets the expiration date of the signature
     *
     * @return integer the expiration date of this signature. This is a Unix
     *                 timestamp. If this signature does not expire, this will
     *                 be zero.
     */
    public function getExpirationDate()
    {
        return $this->_expirationDate;
    }
    // }}}
    // {{{ getUserId()
    /**
     * Gets the user id associated with this signature
     *
     * @return Crypt_GPG_UserId the user id associated with this signature.
     */
    public function getUserId()
    {
        return $this->_userId;
    }
    // }}}
    // {{{ isValid()
    /**
     * Gets whether or no this signature is valid
     *
     * @return boolean true if this signature is valid and false if it is not.
     */
    public function isValid()
    {
        return $this->_isValid;
    }
    // }}}
    // {{{ setId()
    /**
     * Sets the id of this signature
     *
     * @param string $id a base64-encoded string containing a unique id for
     *                   this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     *
     * @see Crypt_GPG_Signature::getId()
     */
    public function setId($id)
    {
        $this->_id = strval($id);
        return $this;
    }
    // }}}
    // {{{ setKeyFingerprint()
    /**
     * Sets the key fingerprint of this signature
     *
     * @param string $fingerprint the key fingerprint of this signature. This
     *                            is the fingerprint of the primary key used to
     *                            create this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setKeyFingerprint($fingerprint)
    {
        $this->_keyFingerprint = strval($fingerprint);
        return $this;
    }
    // }}}
    // {{{ setKeyId()
    /**
     * Sets the key id of this signature
     *
     * @param string $id the key id of this signature. This is the id of the
     *                   primary key used to create this signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setKeyId($id)
    {
        $this->_keyId = strval($id);
        return $this;
    }
    // }}}
    // {{{ setCreationDate()
    /**
     * Sets the creation date of this signature
     *
     * @param integer $creationDate the creation date of this signature. This
     *                              is a Unix timestamp.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setCreationDate($creationDate)
    {
        $this->_creationDate = intval($creationDate);
        return $this;
    }
    // }}}
    // {{{ setExpirationDate()
    /**
     * Sets the expiration date of this signature
     *
     * @param integer $expirationDate the expiration date of this signature.
     *                                This is a Unix timestamp. Specify zero if
     *                                this signature does not expire.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setExpirationDate($expirationDate)
    {
        $this->_expirationDate = intval($expirationDate);
        return $this;
    }
    // }}}
    // {{{ setUserId()
    /**
     * Sets the user id associated with this signature
     *
     * @param Crypt_GPG_UserId $userId the user id associated with this
     *                                 signature.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setUserId(Crypt_GPG_UserId $userId)
    {
        $this->_userId = $userId;
        return $this;
    }
    // }}}
    // {{{ setValid()
    /**
     * Sets whether or not this signature is valid
     *
     * @param boolean $isValid true if this signature is valid and false if it
     *                         is not.
     *
     * @return Crypt_GPG_Signature the current object, for fluent interface.
     */
    public function setValid($isValid)
    {
        $this->_isValid = ($isValid) ? true : false;
        return $this;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/SubKey.php
New file
@@ -0,0 +1,649 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Contains a class representing GPG sub-keys and constants for GPG algorithms
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: SubKey.php 302768 2010-08-25 13:45:52Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 */
// {{{ class Crypt_GPG_SubKey
/**
 * A class for GPG sub-key information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method. Sub-key objects are members of a {@link Crypt_GPG_Key} object.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @author    Nathan Fredrickson <nathan@silverorange.com>
 * @copyright 2005-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 * @see       Crypt_GPG_Key::getSubKeys()
 */
class Crypt_GPG_SubKey
{
    // {{{ class constants
    /**
     * RSA encryption algorithm.
     */
    const ALGORITHM_RSA = 1;
    /**
     * Elgamal encryption algorithm (encryption only).
     */
    const ALGORITHM_ELGAMAL_ENC = 16;
    /**
     * DSA encryption algorithm (sometimes called DH, sign only).
     */
    const ALGORITHM_DSA = 17;
    /**
     * Elgamal encryption algorithm (signage and encryption - should not be
     * used).
     */
    const ALGORITHM_ELGAMAL_ENC_SGN = 20;
    // }}}
    // {{{ class properties
    /**
     * The id of this sub-key
     *
     * @var string
     */
    private $_id = '';
    /**
     * The algorithm used to create this sub-key
     *
     * The value is one of the Crypt_GPG_SubKey::ALGORITHM_* constants.
     *
     * @var integer
     */
    private $_algorithm = 0;
    /**
     * The fingerprint of this sub-key
     *
     * @var string
     */
    private $_fingerprint = '';
    /**
     * Length of this sub-key in bits
     *
     * @var integer
     */
    private $_length = 0;
    /**
     * Date this sub-key was created
     *
     * This is a Unix timestamp.
     *
     * @var integer
     */
    private $_creationDate = 0;
    /**
     * Date this sub-key expires
     *
     * This is a Unix timestamp. If this sub-key does not expire, this will be
     * zero.
     *
     * @var integer
     */
    private $_expirationDate = 0;
    /**
     * Whether or not this sub-key can sign data
     *
     * @var boolean
     */
    private $_canSign = false;
    /**
     * Whether or not this sub-key can encrypt data
     *
     * @var boolean
     */
    private $_canEncrypt = false;
    /**
     * Whether or not the private key for this sub-key exists in the keyring
     *
     * @var boolean
     */
    private $_hasPrivate = false;
    /**
     * Whether or not this sub-key is revoked
     *
     * @var boolean
     */
    private $_isRevoked = false;
    // }}}
    // {{{ __construct()
    /**
     * Creates a new sub-key object
     *
     * Sub-keys can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string  id</kbd>          - the key id of the sub-key.
     * - <kbd>integer algorithm</kbd>   - the encryption algorithm of the
     *                                    sub-key.
     * - <kbd>string  fingerprint</kbd> - the fingerprint of the sub-key. The
     *                                    fingerprint should not contain
     *                                    formatting characters.
     * - <kbd>integer length</kbd>      - the length of the sub-key in bits.
     * - <kbd>integer creation</kbd>    - the date the sub-key was created.
     *                                    This is a UNIX timestamp.
     * - <kbd>integer expiration</kbd>  - the date the sub-key expires. This
     *                                    is a UNIX timestamp. If the sub-key
     *                                    does not expire, use 0.
     * - <kbd>boolean canSign</kbd>     - whether or not the sub-key can be
     *                                    used to sign data.
     * - <kbd>boolean canEncrypt</kbd>  - whether or not the sub-key can be
     *                                    used to encrypt data.
     * - <kbd>boolean hasPrivate</kbd>  - whether or not the private key for
     *                                    the sub-key exists in the keyring.
     * - <kbd>boolean isRevoked</kbd>   - whether or not this sub-key is
     *                                    revoked.
     *
     * @param Crypt_GPG_SubKey|string|array $key optional. Either an existing
     *        sub-key object, which is copied; a sub-key string, which is
     *        parsed; or an array of initial values.
     */
    public function __construct($key = null)
    {
        // parse from string
        if (is_string($key)) {
            $key = self::parse($key);
        }
        // copy from object
        if ($key instanceof Crypt_GPG_SubKey) {
            $this->_id             = $key->_id;
            $this->_algorithm      = $key->_algorithm;
            $this->_fingerprint    = $key->_fingerprint;
            $this->_length         = $key->_length;
            $this->_creationDate   = $key->_creationDate;
            $this->_expirationDate = $key->_expirationDate;
            $this->_canSign        = $key->_canSign;
            $this->_canEncrypt     = $key->_canEncrypt;
            $this->_hasPrivate     = $key->_hasPrivate;
            $this->_isRevoked      = $key->_isRevoked;
        }
        // initialize from array
        if (is_array($key)) {
            if (array_key_exists('id', $key)) {
                $this->setId($key['id']);
            }
            if (array_key_exists('algorithm', $key)) {
                $this->setAlgorithm($key['algorithm']);
            }
            if (array_key_exists('fingerprint', $key)) {
                $this->setFingerprint($key['fingerprint']);
            }
            if (array_key_exists('length', $key)) {
                $this->setLength($key['length']);
            }
            if (array_key_exists('creation', $key)) {
                $this->setCreationDate($key['creation']);
            }
            if (array_key_exists('expiration', $key)) {
                $this->setExpirationDate($key['expiration']);
            }
            if (array_key_exists('canSign', $key)) {
                $this->setCanSign($key['canSign']);
            }
            if (array_key_exists('canEncrypt', $key)) {
                $this->setCanEncrypt($key['canEncrypt']);
            }
            if (array_key_exists('hasPrivate', $key)) {
                $this->setHasPrivate($key['hasPrivate']);
            }
            if (array_key_exists('isRevoked', $key)) {
                $this->setRevoked($key['isRevoked']);
            }
        }
    }
    // }}}
    // {{{ getId()
    /**
     * Gets the id of this sub-key
     *
     * @return string the id of this sub-key.
     */
    public function getId()
    {
        return $this->_id;
    }
    // }}}
    // {{{ getAlgorithm()
    /**
     * Gets the algorithm used by this sub-key
     *
     * The algorithm should be one of the Crypt_GPG_SubKey::ALGORITHM_*
     * constants.
     *
     * @return integer the algorithm used by this sub-key.
     */
    public function getAlgorithm()
    {
        return $this->_algorithm;
    }
    // }}}
    // {{{ getCreationDate()
    /**
     * Gets the creation date of this sub-key
     *
     * This is a Unix timestamp.
     *
     * @return integer the creation date of this sub-key.
     */
    public function getCreationDate()
    {
        return $this->_creationDate;
    }
    // }}}
    // {{{ getExpirationDate()
    /**
     * Gets the date this sub-key expires
     *
     * This is a Unix timestamp. If this sub-key does not expire, this will be
     * zero.
     *
     * @return integer the date this sub-key expires.
     */
    public function getExpirationDate()
    {
        return $this->_expirationDate;
    }
    // }}}
    // {{{ getFingerprint()
    /**
     * Gets the fingerprint of this sub-key
     *
     * @return string the fingerprint of this sub-key.
     */
    public function getFingerprint()
    {
        return $this->_fingerprint;
    }
    // }}}
    // {{{ getLength()
    /**
     * Gets the length of this sub-key in bits
     *
     * @return integer the length of this sub-key in bits.
     */
    public function getLength()
    {
        return $this->_length;
    }
    // }}}
    // {{{ canSign()
    /**
     * Gets whether or not this sub-key can sign data
     *
     * @return boolean true if this sub-key can sign data and false if this
     *                 sub-key can not sign data.
     */
    public function canSign()
    {
        return $this->_canSign;
    }
    // }}}
    // {{{ canEncrypt()
    /**
     * Gets whether or not this sub-key can encrypt data
     *
     * @return boolean true if this sub-key can encrypt data and false if this
     *                 sub-key can not encrypt data.
     */
    public function canEncrypt()
    {
        return $this->_canEncrypt;
    }
    // }}}
    // {{{ hasPrivate()
    /**
     * Gets whether or not the private key for this sub-key exists in the
     * keyring
     *
     * @return boolean true the private key for this sub-key exists in the
     *                 keyring and false if it does not.
     */
    public function hasPrivate()
    {
        return $this->_hasPrivate;
    }
    // }}}
    // {{{ isRevoked()
    /**
     * Gets whether or not this sub-key is revoked
     *
     * @return boolean true if this sub-key is revoked and false if it is not.
     */
    public function isRevoked()
    {
        return $this->_isRevoked;
    }
    // }}}
    // {{{ setCreationDate()
    /**
     * Sets the creation date of this sub-key
     *
     * The creation date is a Unix timestamp.
     *
     * @param integer $creationDate the creation date of this sub-key.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setCreationDate($creationDate)
    {
        $this->_creationDate = intval($creationDate);
        return $this;
    }
    // }}}
    // {{{ setExpirationDate()
    /**
     * Sets the expiration date of this sub-key
     *
     * The expiration date is a Unix timestamp. Specify zero if this sub-key
     * does not expire.
     *
     * @param integer $expirationDate the expiration date of this sub-key.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setExpirationDate($expirationDate)
    {
        $this->_expirationDate = intval($expirationDate);
        return $this;
    }
    // }}}
    // {{{ setId()
    /**
     * Sets the id of this sub-key
     *
     * @param string $id the id of this sub-key.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setId($id)
    {
        $this->_id = strval($id);
        return $this;
    }
    // }}}
    // {{{ setAlgorithm()
    /**
     * Sets the algorithm used by this sub-key
     *
     * @param integer $algorithm the algorithm used by this sub-key.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setAlgorithm($algorithm)
    {
        $this->_algorithm = intval($algorithm);
        return $this;
    }
    // }}}
    // {{{ setFingerprint()
    /**
     * Sets the fingerprint of this sub-key
     *
     * @param string $fingerprint the fingerprint of this sub-key.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setFingerprint($fingerprint)
    {
        $this->_fingerprint = strval($fingerprint);
        return $this;
    }
    // }}}
    // {{{ setLength()
    /**
     * Sets the length of this sub-key in bits
     *
     * @param integer $length the length of this sub-key in bits.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setLength($length)
    {
        $this->_length = intval($length);
        return $this;
    }
    // }}}
    // {{{ setCanSign()
    /**
     * Sets whether of not this sub-key can sign data
     *
     * @param boolean $canSign true if this sub-key can sign data and false if
     *                         it can not.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setCanSign($canSign)
    {
        $this->_canSign = ($canSign) ? true : false;
        return $this;
    }
    // }}}
    // {{{ setCanEncrypt()
    /**
     * Sets whether of not this sub-key can encrypt data
     *
     * @param boolean $canEncrypt true if this sub-key can encrypt data and
     *                            false if it can not.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setCanEncrypt($canEncrypt)
    {
        $this->_canEncrypt = ($canEncrypt) ? true : false;
        return $this;
    }
    // }}}
    // {{{ setHasPrivate()
    /**
     * Sets whether of not the private key for this sub-key exists in the
     * keyring
     *
     * @param boolean $hasPrivate true if the private key for this sub-key
     *                            exists in the keyring and false if it does
     *                            not.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setHasPrivate($hasPrivate)
    {
        $this->_hasPrivate = ($hasPrivate) ? true : false;
        return $this;
    }
    // }}}
    // {{{ setRevoked()
    /**
     * Sets whether or not this sub-key is revoked
     *
     * @param boolean $isRevoked whether or not this sub-key is revoked.
     *
     * @return Crypt_GPG_SubKey the current object, for fluent interface.
     */
    public function setRevoked($isRevoked)
    {
        $this->_isRevoked = ($isRevoked) ? true : false;
        return $this;
    }
    // }}}
    // {{{ parse()
    /**
     * Parses a sub-key object from a sub-key string
     *
     * See <b>doc/DETAILS</b> in the
     * {@link http://www.gnupg.org/download/ GPG distribution} for information
     * on how the sub-key string is parsed.
     *
     * @param string $string the string containing the sub-key.
     *
     * @return Crypt_GPG_SubKey the sub-key object parsed from the string.
     */
    public static function parse($string)
    {
        $tokens = explode(':', $string);
        $subKey = new Crypt_GPG_SubKey();
        $subKey->setId($tokens[4]);
        $subKey->setLength($tokens[2]);
        $subKey->setAlgorithm($tokens[3]);
        $subKey->setCreationDate(self::_parseDate($tokens[5]));
        $subKey->setExpirationDate(self::_parseDate($tokens[6]));
        if ($tokens[1] == 'r') {
            $subKey->setRevoked(true);
        }
        if (strpos($tokens[11], 's') !== false) {
            $subKey->setCanSign(true);
        }
        if (strpos($tokens[11], 'e') !== false) {
            $subKey->setCanEncrypt(true);
        }
        return $subKey;
    }
    // }}}
    // {{{ _parseDate()
    /**
     * Parses a date string as provided by GPG into a UNIX timestamp
     *
     * @param string $string the date string.
     *
     * @return integer the UNIX timestamp corresponding to the provided date
     *                 string.
     */
    private static function _parseDate($string)
    {
        if ($string == '') {
            $timestamp = 0;
        } else {
            // all times are in UTC according to GPG documentation
            $timeZone = new DateTimeZone('UTC');
            if (strpos($string, 'T') === false) {
                // interpret as UNIX timestamp
                $string = '@' . $string;
            }
            $date = new DateTime($string, $timeZone);
            // convert to UNIX timestamp
            $timestamp = intval($date->format('U'));
        }
        return $timestamp;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/UserId.php
New file
@@ -0,0 +1,373 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Contains a data class representing a GPG user id
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: UserId.php 295621 2010-03-01 04:18:54Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 */
// {{{ class Crypt_GPG_UserId
/**
 * A class for GPG user id information
 *
 * This class is used to store the results of the {@link Crypt_GPG::getKeys()}
 * method. User id objects are members of a {@link Crypt_GPG_Key} object.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008-2010 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @see       Crypt_GPG::getKeys()
 * @see       Crypt_GPG_Key::getUserIds()
 */
class Crypt_GPG_UserId
{
    // {{{ class properties
    /**
     * The name field of this user id
     *
     * @var string
     */
    private $_name = '';
    /**
     * The comment field of this user id
     *
     * @var string
     */
    private $_comment = '';
    /**
     * The email field of this user id
     *
     * @var string
     */
    private $_email = '';
    /**
     * Whether or not this user id is revoked
     *
     * @var boolean
     */
    private $_isRevoked = false;
    /**
     * Whether or not this user id is valid
     *
     * @var boolean
     */
    private $_isValid = true;
    // }}}
    // {{{ __construct()
    /**
     * Creates a new user id
     *
     * User ids can be initialized from an array of named values. Available
     * names are:
     *
     * - <kbd>string  name</kbd>    - the name field of the user id.
     * - <kbd>string  comment</kbd> - the comment field of the user id.
     * - <kbd>string  email</kbd>   - the email field of the user id.
     * - <kbd>boolean valid</kbd>   - whether or not the user id is valid.
     * - <kbd>boolean revoked</kbd> - whether or not the user id is revoked.
     *
     * @param Crypt_GPG_UserId|string|array $userId optional. Either an
     *        existing user id object, which is copied; a user id string, which
     *        is parsed; or an array of initial values.
     */
    public function __construct($userId = null)
    {
        // parse from string
        if (is_string($userId)) {
            $userId = self::parse($userId);
        }
        // copy from object
        if ($userId instanceof Crypt_GPG_UserId) {
            $this->_name      = $userId->_name;
            $this->_comment   = $userId->_comment;
            $this->_email     = $userId->_email;
            $this->_isRevoked = $userId->_isRevoked;
            $this->_isValid   = $userId->_isValid;
        }
        // initialize from array
        if (is_array($userId)) {
            if (array_key_exists('name', $userId)) {
                $this->setName($userId['name']);
            }
            if (array_key_exists('comment', $userId)) {
                $this->setComment($userId['comment']);
            }
            if (array_key_exists('email', $userId)) {
                $this->setEmail($userId['email']);
            }
            if (array_key_exists('revoked', $userId)) {
                $this->setRevoked($userId['revoked']);
            }
            if (array_key_exists('valid', $userId)) {
                $this->setValid($userId['valid']);
            }
        }
    }
    // }}}
    // {{{ getName()
    /**
     * Gets the name field of this user id
     *
     * @return string the name field of this user id.
     */
    public function getName()
    {
        return $this->_name;
    }
    // }}}
    // {{{ getComment()
    /**
     * Gets the comments field of this user id
     *
     * @return string the comments field of this user id.
     */
    public function getComment()
    {
        return $this->_comment;
    }
    // }}}
    // {{{ getEmail()
    /**
     * Gets the email field of this user id
     *
     * @return string the email field of this user id.
     */
    public function getEmail()
    {
        return $this->_email;
    }
    // }}}
    // {{{ isRevoked()
    /**
     * Gets whether or not this user id is revoked
     *
     * @return boolean true if this user id is revoked and false if it is not.
     */
    public function isRevoked()
    {
        return $this->_isRevoked;
    }
    // }}}
    // {{{ isValid()
    /**
     * Gets whether or not this user id is valid
     *
     * @return boolean true if this user id is valid and false if it is not.
     */
    public function isValid()
    {
        return $this->_isValid;
    }
    // }}}
    // {{{ __toString()
    /**
     * Gets a string representation of this user id
     *
     * The string is formatted as:
     * <b><kbd>name (comment) <email-address></kbd></b>.
     *
     * @return string a string representation of this user id.
     */
    public function __toString()
    {
        $components = array();
        if (strlen($this->_name) > 0) {
            $components[] = $this->_name;
        }
        if (strlen($this->_comment) > 0) {
            $components[] = '(' . $this->_comment . ')';
        }
        if (strlen($this->_email) > 0) {
            $components[] = '<' . $this->_email. '>';
        }
        return implode(' ', $components);
    }
    // }}}
    // {{{ setName()
    /**
     * Sets the name field of this user id
     *
     * @param string $name the name field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setName($name)
    {
        $this->_name = strval($name);
        return $this;
    }
    // }}}
    // {{{ setComment()
    /**
     * Sets the comment field of this user id
     *
     * @param string $comment the comment field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setComment($comment)
    {
        $this->_comment = strval($comment);
        return $this;
    }
    // }}}
    // {{{ setEmail()
    /**
     * Sets the email field of this user id
     *
     * @param string $email the email field of this user id.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setEmail($email)
    {
        $this->_email = strval($email);
        return $this;
    }
    // }}}
    // {{{ setRevoked()
    /**
     * Sets whether or not this user id is revoked
     *
     * @param boolean $isRevoked whether or not this user id is revoked.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setRevoked($isRevoked)
    {
        $this->_isRevoked = ($isRevoked) ? true : false;
        return $this;
    }
    // }}}
    // {{{ setValid()
    /**
     * Sets whether or not this user id is valid
     *
     * @param boolean $isValid whether or not this user id is valid.
     *
     * @return Crypt_GPG_UserId the current object, for fluent interface.
     */
    public function setValid($isValid)
    {
        $this->_isValid = ($isValid) ? true : false;
        return $this;
    }
    // }}}
    // {{{ parse()
    /**
     * Parses a user id object from a user id string
     *
     * A user id string is of the form:
     * <b><kbd>name (comment) <email-address></kbd></b> with the <i>comment</i>
     * and <i>email-address</i> fields being optional.
     *
     * @param string $string the user id string to parse.
     *
     * @return Crypt_GPG_UserId the user id object parsed from the string.
     */
    public static function parse($string)
    {
        $userId  = new Crypt_GPG_UserId();
        $email   = '';
        $comment = '';
        // get email address from end of string if it exists
        $matches = array();
        if (preg_match('/^(.+?) <([^>]+)>$/', $string, $matches) === 1) {
            $string = $matches[1];
            $email  = $matches[2];
        }
        // get comment from end of string if it exists
        $matches = array();
        if (preg_match('/^(.+?) \(([^\)]+)\)$/', $string, $matches) === 1) {
            $string  = $matches[1];
            $comment = $matches[2];
        }
        $name = $string;
        $userId->setName($name);
        $userId->setComment($comment);
        $userId->setEmail($email);
        return $userId;
    }
    // }}}
}
// }}}
?>
plugins/enigma/lib/Crypt/GPG/VerifyStatusHandler.php
New file
@@ -0,0 +1,216 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * Crypt_GPG is a package to use GPG from PHP
 *
 * This file contains an object that handles GPG's status output for the verify
 * operation.
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @version   CVS: $Id: VerifyStatusHandler.php 302908 2010-08-31 03:56:54Z gauthierm $
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
/**
 * Signature object class definition
 */
require_once 'Crypt/GPG/Signature.php';
/**
 * Status line handler for the verify operation
 *
 * This class is used internally by Crypt_GPG and does not need be used
 * directly. See the {@link Crypt_GPG} class for end-user API.
 *
 * This class is responsible for building signature objects that are returned
 * by the {@link Crypt_GPG::verify()} method. See <b>doc/DETAILS</b> in the
 * {@link http://www.gnupg.org/download/ GPG distribution} for detailed
 * information on GPG's status output for the verify operation.
 *
 * @category  Encryption
 * @package   Crypt_GPG
 * @author    Michael Gauthier <mike@silverorange.com>
 * @copyright 2008 silverorange
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
 * @link      http://pear.php.net/package/Crypt_GPG
 * @link      http://www.gnupg.org/
 */
class Crypt_GPG_VerifyStatusHandler
{
    // {{{ protected properties
    /**
     * The current signature id
     *
     * Ths signature id is emitted by GPG before the new signature line so we
     * must remember it temporarily.
     *
     * @var string
     */
    protected $signatureId = '';
    /**
     * List of parsed {@link Crypt_GPG_Signature} objects
     *
     * @var array
     */
    protected $signatures = array();
    /**
     * Array index of the current signature
     *
     * @var integer
     */
    protected $index = -1;
    // }}}
    // {{{ handle()
    /**
     * Handles a status line
     *
     * @param string $line the status line to handle.
     *
     * @return void
     */
    public function handle($line)
    {
        $tokens = explode(' ', $line);
        switch ($tokens[0]) {
        case 'GOODSIG':
        case 'EXPSIG':
        case 'EXPKEYSIG':
        case 'REVKEYSIG':
        case 'BADSIG':
            $signature = new Crypt_GPG_Signature();
            // if there was a signature id, set it on the new signature
            if ($this->signatureId != '') {
                $signature->setId($this->signatureId);
                $this->signatureId = '';
            }
            // Detect whether fingerprint or key id was returned and set
            // signature values appropriately. Key ids are strings of either
            // 16 or 8 hexadecimal characters. Fingerprints are strings of 40
            // hexadecimal characters. The key id is the last 16 characters of
            // the key fingerprint.
            if (strlen($tokens[1]) > 16) {
                $signature->setKeyFingerprint($tokens[1]);
                $signature->setKeyId(substr($tokens[1], -16));
            } else {
                $signature->setKeyId($tokens[1]);
            }
            // get user id string
            $string = implode(' ', array_splice($tokens, 2));
            $string = rawurldecode($string);
            $signature->setUserId(Crypt_GPG_UserId::parse($string));
            $this->index++;
            $this->signatures[$this->index] = $signature;
            break;
        case 'ERRSIG':
            $signature = new Crypt_GPG_Signature();
            // if there was a signature id, set it on the new signature
            if ($this->signatureId != '') {
                $signature->setId($this->signatureId);
                $this->signatureId = '';
            }
            // Detect whether fingerprint or key id was returned and set
            // signature values appropriately. Key ids are strings of either
            // 16 or 8 hexadecimal characters. Fingerprints are strings of 40
            // hexadecimal characters. The key id is the last 16 characters of
            // the key fingerprint.
            if (strlen($tokens[1]) > 16) {
                $signature->setKeyFingerprint($tokens[1]);
                $signature->setKeyId(substr($tokens[1], -16));
            } else {
                $signature->setKeyId($tokens[1]);
            }
            $this->index++;
            $this->signatures[$this->index] = $signature;
            break;
        case 'VALIDSIG':
            if (!array_key_exists($this->index, $this->signatures)) {
                break;
            }
            $signature = $this->signatures[$this->index];
            $signature->setValid(true);
            $signature->setKeyFingerprint($tokens[1]);
            if (strpos($tokens[3], 'T') === false) {
                $signature->setCreationDate($tokens[3]);
            } else {
                $signature->setCreationDate(strtotime($tokens[3]));
            }
            if (array_key_exists(4, $tokens)) {
                if (strpos($tokens[4], 'T') === false) {
                    $signature->setExpirationDate($tokens[4]);
                } else {
                    $signature->setExpirationDate(strtotime($tokens[4]));
                }
            }
            break;
        case 'SIG_ID':
            // note: signature id comes before new signature line and may not
            // exist for some signature types
            $this->signatureId = $tokens[1];
            break;
        }
    }
    // }}}
    // {{{ getSignatures()
    /**
     * Gets the {@link Crypt_GPG_Signature} objects parsed by this handler
     *
     * @return array the signature objects parsed by this handler.
     */
    public function getSignatures()
    {
        return $this->signatures;
    }
    // }}}
}
?>
plugins/enigma/lib/enigma_driver.php
New file
@@ -0,0 +1,106 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Abstract driver for the Enigma Plugin                                   |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
abstract class enigma_driver
{
    /**
     * Class constructor.
     *
     * @param string User name (email address)
     */
    abstract function __construct($user);
    /**
     * Driver initialization.
     *
     * @return mixed NULL on success, enigma_error on failure
     */
    abstract function init();
    /**
     * Encryption.
     */
    abstract function encrypt($text, $keys);
    /**
     * Decryption..
     */
    abstract function decrypt($text, $key, $passwd);
    /**
     * Signing.
     */
    abstract function sign($text, $key, $passwd);
    /**
     * Signature verification.
     *
     * @param string Message body
     * @param string Signature, if message is of type PGP/MIME and body doesn't contain it
     *
     * @return mixed Signature information (enigma_signature) or enigma_error
     */
    abstract function verify($text, $signature);
    /**
     * Key/Cert file import.
     *
     * @param string  File name or file content
     * @param bollean True if first argument is a filename
     *
     * @return mixed Import status array or enigma_error
     */
    abstract function import($content, $isfile=false);
    /**
     * Keys listing.
     *
     * @param string Optional pattern for key ID, user ID or fingerprint
     *
     * @return mixed Array of enigma_key objects or enigma_error
     */
    abstract function list_keys($pattern='');
    /**
     * Single key information.
     *
     * @param string Key ID, user ID or fingerprint
     *
     * @return mixed Key (enigma_key) object or enigma_error
     */
    abstract function get_key($keyid);
    /**
     * Key pair generation.
     *
     * @param array Key/User data
     *
     * @return mixed Key (enigma_key) object or enigma_error
     */
    abstract function gen_key($data);
    /**
     * Key deletion.
     */
    abstract function del_key($keyid);
}
plugins/enigma/lib/enigma_driver_gnupg.php
New file
@@ -0,0 +1,305 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | GnuPG (PGP) driver for the Enigma Plugin                                |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
require_once 'Crypt/GPG.php';
class enigma_driver_gnupg extends enigma_driver
{
    private $rc;
    private $gpg;
    private $homedir;
    private $user;
    function __construct($user)
    {
        $rcmail = rcmail::get_instance();
        $this->rc = $rcmail;
        $this->user = $user;
    }
    /**
     * Driver initialization and environment checking.
     * Should only return critical errors.
     *
     * @return mixed NULL on success, enigma_error on failure
     */
    function init()
    {
        $homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . '/plugins/enigma/home');
        if (!$homedir)
            return new enigma_error(enigma_error::E_INTERNAL,
                "Option 'enigma_pgp_homedir' not specified");
        // check if homedir exists (create it if not) and is readable
        if (!file_exists($homedir))
            return new enigma_error(enigma_error::E_INTERNAL,
                "Keys directory doesn't exists: $homedir");
        if (!is_writable($homedir))
            return new enigma_error(enigma_error::E_INTERNAL,
                "Keys directory isn't writeable: $homedir");
        $homedir = $homedir . '/' . $this->user;
        // check if user's homedir exists (create it if not) and is readable
        if (!file_exists($homedir))
            mkdir($homedir, 0700);
        if (!file_exists($homedir))
            return new enigma_error(enigma_error::E_INTERNAL,
                "Unable to create keys directory: $homedir");
        if (!is_writable($homedir))
            return new enigma_error(enigma_error::E_INTERNAL,
                "Unable to write to keys directory: $homedir");
        $this->homedir = $homedir;
        // Create Crypt_GPG object
        try {
            $this->gpg = new Crypt_GPG(array(
                'homedir'   => $this->homedir,
//                'debug'     => true,
          ));
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    function encrypt($text, $keys)
    {
/*
        foreach ($keys as $key) {
            $this->gpg->addEncryptKey($key);
        }
        $enc = $this->gpg->encrypt($text);
        return $enc;
*/
    }
    function decrypt($text, $key, $passwd)
    {
//        $this->gpg->addDecryptKey($key, $passwd);
        try {
            $dec = $this->gpg->decrypt($text);
            return $dec;
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    function sign($text, $key, $passwd)
    {
/*
        $this->gpg->addSignKey($key, $passwd);
        $signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED);
        return $signed;
*/
    }
    function verify($text, $signature)
    {
        try {
            $verified = $this->gpg->verify($text, $signature);
              return $this->parse_signature($verified[0]);
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    public function import($content, $isfile=false)
    {
        try {
            if ($isfile)
                return $this->gpg->importKeyFile($content);
            else
                return $this->gpg->importKey($content);
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    public function list_keys($pattern='')
    {
        try {
            $keys = $this->gpg->getKeys($pattern);
            $result = array();
//print_r($keys);
            foreach ($keys as $idx => $key) {
                $result[] = $this->parse_key($key);
                unset($keys[$idx]);
            }
//print_r($result);
              return $result;
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    public function get_key($keyid)
    {
        $list = $this->list_keys($keyid);
        if (is_array($list))
            return array_shift($list);
        // error
        return $list;
    }
    public function gen_key($data)
    {
    }
    public function del_key($keyid)
    {
//        $this->get_key($keyid);
    }
    public function del_privkey($keyid)
    {
        try {
            $this->gpg->deletePrivateKey($keyid);
            return true;
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    public function del_pubkey($keyid)
    {
        try {
            $this->gpg->deletePublicKey($keyid);
            return true;
        }
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    /**
     * Converts Crypt_GPG exception into Enigma's error object
     *
     * @param mixed Exception object
     *
     * @return enigma_error Error object
     */
    private function get_error_from_exception($e)
    {
        $data = array();
        if ($e instanceof Crypt_GPG_KeyNotFoundException) {
            $error = enigma_error::E_KEYNOTFOUND;
            $data['id'] = $e->getKeyId();
        }
        else if ($e instanceof Crypt_GPG_BadPassphraseException) {
            $error = enigma_error::E_BADPASS;
            $data['bad']     = $e->getBadPassphrases();
            $data['missing'] = $e->getMissingPassphrases();
        }
        else if ($e instanceof Crypt_GPG_NoDataException)
            $error = enigma_error::E_NODATA;
        else if ($e instanceof Crypt_GPG_DeletePrivateKeyException)
            $error = enigma_error::E_DELKEY;
        else
            $error = enigma_error::E_INTERNAL;
        $msg = $e->getMessage();
        return new enigma_error($error, $msg, $data);
    }
    /**
     * Converts Crypt_GPG_Signature object into Enigma's signature object
     *
     * @param Crypt_GPG_Signature Signature object
     *
     * @return enigma_signature Signature object
     */
    private function parse_signature($sig)
    {
        $user = $sig->getUserId();
        $data = new enigma_signature();
        $data->id          = $sig->getId();
        $data->valid       = $sig->isValid();
        $data->fingerprint = $sig->getKeyFingerprint();
        $data->created     = $sig->getCreationDate();
        $data->expires     = $sig->getExpirationDate();
        $data->name        = $user->getName();
        $data->comment     = $user->getComment();
        $data->email       = $user->getEmail();
        return $data;
    }
    /**
     * Converts Crypt_GPG_Key object into Enigma's key object
     *
     * @param Crypt_GPG_Key Key object
     *
     * @return enigma_key Key object
     */
    private function parse_key($key)
    {
        $ekey = new enigma_key();
        foreach ($key->getUserIds() as $idx => $user) {
            $id = new enigma_userid();
            $id->name    = $user->getName();
            $id->comment = $user->getComment();
            $id->email   = $user->getEmail();
            $id->valid   = $user->isValid();
            $id->revoked = $user->isRevoked();
            $ekey->users[$idx] = $id;
        }
        $ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
        foreach ($key->getSubKeys() as $idx => $subkey) {
                $skey = new enigma_subkey();
                $skey->id          = $subkey->getId();
                $skey->revoked     = $subkey->isRevoked();
                $skey->created     = $subkey->getCreationDate();
                $skey->expires     = $subkey->getExpirationDate();
                $skey->fingerprint = $subkey->getFingerprint();
                $skey->has_private = $subkey->hasPrivate();
                $skey->can_sign    = $subkey->canSign();
                $skey->can_encrypt = $subkey->canEncrypt();
                $ekey->subkeys[$idx] = $skey;
        };
        $ekey->id = $ekey->subkeys[0]->id;
        return $ekey;
    }
}
plugins/enigma/lib/enigma_engine.php
New file
@@ -0,0 +1,547 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Engine of the Enigma Plugin                                             |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
/*
    RFC2440: OpenPGP Message Format
    RFC3156: MIME Security with OpenPGP
    RFC3851: S/MIME
*/
class enigma_engine
{
    private $rc;
    private $enigma;
    private $pgp_driver;
    private $smime_driver;
    public $decryptions = array();
    public $signatures = array();
    public $signed_parts = array();
    /**
     * Plugin initialization.
     */
    function __construct($enigma)
    {
        $rcmail = rcmail::get_instance();
        $this->rc = $rcmail;
        $this->enigma = $enigma;
    }
    /**
     * PGP driver initialization.
     */
    function load_pgp_driver()
    {
        if ($this->pgp_driver)
            return;
        $driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
        $username = $this->rc->user->get_username();
        // Load driver
        $this->pgp_driver = new $driver($username);
        if (!$this->pgp_driver) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: Unable to load PGP driver: $driver"
            ), true, true);
        }
        // Initialise driver
        $result = $this->pgp_driver->init();
        if ($result instanceof enigma_error) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: ".$result->getMessage()
            ), true, true);
        }
    }
    /**
     * S/MIME driver initialization.
     */
    function load_smime_driver()
    {
        if ($this->smime_driver)
            return;
        // NOT IMPLEMENTED!
        return;
        $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
        $username = $this->rc->user->get_username();
        // Load driver
        $this->smime_driver = new $driver($username);
        if (!$this->smime_driver) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: Unable to load S/MIME driver: $driver"
            ), true, true);
        }
        // Initialise driver
        $result = $this->smime_driver->init();
        if ($result instanceof enigma_error) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: ".$result->getMessage()
            ), true, true);
        }
    }
    /**
     * Handler for plain/text message.
     *
     * @param array Reference to hook's parameters
     */
    function parse_plain(&$p)
    {
        $part = $p['structure'];
        // Get message body from IMAP server
        $this->set_part_body($part, $p['object']->uid);
        // @TODO: big message body can be a file resource
        // PGP signed message
        if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $part->body)) {
            $this->parse_plain_signed($p);
        }
        // PGP encrypted message
        else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $part->body)) {
            $this->parse_plain_encrypted($p);
        }
    }
    /**
     * Handler for multipart/signed message.
     *
     * @param array Reference to hook's parameters
     */
    function parse_signed(&$p)
    {
        $struct = $p['structure'];
        // S/MIME
        if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
            $this->parse_smime_signed($p);
        }
        // PGP/MIME:
        // The multipart/signed body MUST consist of exactly two parts.
        // The first part contains the signed data in MIME canonical format,
        // including a set of appropriate content headers describing the data.
        // The second body MUST contain the PGP digital signature.  It MUST be
        // labeled with a content type of "application/pgp-signature".
        else if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature') {
            $this->parse_pgp_signed($p);
        }
    }
    /**
     * Handler for multipart/encrypted message.
     *
     * @param array Reference to hook's parameters
     */
    function parse_encrypted(&$p)
    {
        $struct = $p['structure'];
        // S/MIME
        if ($struct->mimetype == 'application/pkcs7-mime') {
            $this->parse_smime_encrypted($p);
        }
        // PGP/MIME:
        // The multipart/encrypted MUST consist of exactly two parts.  The first
        // MIME body part must have a content type of "application/pgp-encrypted".
        // This body contains the control information.
        // The second MIME body part MUST contain the actual encrypted data.  It
        // must be labeled with a content type of "application/octet-stream".
        else if ($struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted' &&
            $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
        ) {
            $this->parse_pgp_encrypted($p);
        }
    }
    /**
     * Handler for plain signed message.
     * Excludes message and signature bodies and verifies signature.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_plain_signed(&$p)
    {
        $this->load_pgp_driver();
        $part = $p['structure'];
        // Verify signature
        if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
            $sig = $this->pgp_verify($part->body);
        }
        // @TODO: Handle big bodies using (temp) files
        // In this way we can use fgets on string as on file handle
        $fh = fopen('php://memory', 'br+');
        // @TODO: fopen/fwrite errors handling
        if ($fh) {
            fwrite($fh, $part->body);
            rewind($fh);
        }
        $part->body = null;
        // Extract body (and signature?)
        while (!feof($fh)) {
            $line = fgets($fh, 1024);
            if ($part->body === null)
                $part->body = '';
            else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
                break;
            else
                $part->body .= $line;
        }
        // Remove "Hash" Armor Headers
        $part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body);
        // de-Dash-Escape (RFC2440)
        $part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body);
        // Store signature data for display
        if (!empty($sig)) {
            $this->signed_parts[$part->mime_id] = $part->mime_id;
            $this->signatures[$part->mime_id] = $sig;
        }
        fclose($fh);
    }
    /**
     * Handler for PGP/MIME signed message.
     * Verifies signature.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_pgp_signed(&$p)
    {
        $this->load_pgp_driver();
        $struct = $p['structure'];
        // Verify signature
        if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
            $msg_part = $struct->parts[0];
            $sig_part = $struct->parts[1];
            // Get bodies
            $this->set_part_body($msg_part, $p['object']->uid);
            $this->set_part_body($sig_part, $p['object']->uid);
            // Verify
            $sig = $this->pgp_verify($msg_part->body, $sig_part->body);
            // Store signature data for display
            $this->signatures[$struct->mime_id] = $sig;
            // Message can be multipart (assign signature to each subpart)
            if (!empty($msg_part->parts)) {
                foreach ($msg_part->parts as $part)
                    $this->signed_parts[$part->mime_id] = $struct->mime_id;
            }
            else
                $this->signed_parts[$msg_part->mime_id] = $struct->mime_id;
            // Remove signature file from attachments list
            unset($struct->parts[1]);
        }
    }
    /**
     * Handler for S/MIME signed message.
     * Verifies signature.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_smime_signed(&$p)
    {
        $this->load_smime_driver();
    }
    /**
     * Handler for plain encrypted message.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_plain_encrypted(&$p)
    {
        $this->load_pgp_driver();
        $part = $p['structure'];
        // Get body
        $this->set_part_body($part, $p['object']->uid);
        // Decrypt
        $result = $this->pgp_decrypt($part->body);
        // Store decryption status
        $this->decryptions[$part->mime_id] = $result;
        // Parse decrypted message
        if ($result === true) {
            // @TODO
        }
    }
    /**
     * Handler for PGP/MIME encrypted message.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_pgp_encrypted(&$p)
    {
        $this->load_pgp_driver();
        $struct = $p['structure'];
        $part = $struct->parts[1];
        // Get body
        $this->set_part_body($part, $p['object']->uid);
        // Decrypt
        $result = $this->pgp_decrypt($part->body);
        $this->decryptions[$part->mime_id] = $result;
//print_r($part);
        // Parse decrypted message
        if ($result === true) {
            // @TODO
        }
        else {
            // Make sure decryption status message will be displayed
            $part->type = 'content';
            $p['object']->parts[] = $part;
        }
    }
    /**
     * Handler for S/MIME encrypted message.
     *
     * @param array Reference to hook's parameters
     */
    private function parse_smime_encrypted(&$p)
    {
        $this->load_smime_driver();
    }
    /**
     * PGP signature verification.
     *
     * @param mixed Message body
     * @param mixed Signature body (for MIME messages)
     *
     * @return mixed enigma_signature or enigma_error
     */
    private function pgp_verify(&$msg_body, $sig_body=null)
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
         $sig = $this->pgp_driver->verify($msg_body, $sig_body);
         if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::E_KEYNOTFOUND)
             raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: " . $error->getMessage()
                ), true, false);
//print_r($sig);
        return $sig;
    }
    /**
     * PGP message decryption.
     *
     * @param mixed Message body
     *
     * @return mixed True or enigma_error
     */
    private function pgp_decrypt(&$msg_body)
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
        $result = $this->pgp_driver->decrypt($msg_body, $key, $pass);
//print_r($result);
        if ($result instanceof enigma_error) {
            $err_code = $result->getCode();
            if (!in_array($err_code, array(enigma_error::E_KEYNOTFOUND, enigma_error::E_BADPASS)))
                raise_error(array(
                    'code' => 600, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Enigma plugin: " . $result->getMessage()
                    ), true, false);
            return $result;
        }
//        $msg_body = $result;
        return true;
    }
    /**
     * PGP keys listing.
     *
     * @param mixed Key ID/Name pattern
     *
     * @return mixed Array of keys or enigma_error
     */
    function list_keys($pattern='')
    {
        $this->load_pgp_driver();
        $result = $this->pgp_driver->list_keys($pattern);
        if ($result instanceof enigma_error) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: " . $result->getMessage()
                ), true, false);
        }
        return $result;
    }
    /**
     * PGP key details.
     *
     * @param mixed Key ID
     *
     * @return mixed enigma_key or enigma_error
     */
    function get_key($keyid)
    {
        $this->load_pgp_driver();
        $result = $this->pgp_driver->get_key($keyid);
        if ($result instanceof enigma_error) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: " . $result->getMessage()
                ), true, false);
        }
        return $result;
    }
    /**
     * PGP keys/certs importing.
     *
     * @param mixed   Import file name or content
     * @param boolean True if first argument is a filename
     *
     * @return mixed Import status data array or enigma_error
     */
    function import_key($content, $isfile=false)
    {
        $this->load_pgp_driver();
        $result = $this->pgp_driver->import($content, $isfile);
        if ($result instanceof enigma_error) {
            raise_error(array(
                'code' => 600, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Enigma plugin: " . $result->getMessage()
                ), true, false);
        }
        else {
            $result['imported'] = $result['public_imported'] + $result['private_imported'];
            $result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged'];
        }
        return $result;
    }
    /**
     * Handler for keys/certs import request action
     */
    function import_file()
    {
        $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);
        if ($uid && $mime_id) {
            $part = $this->rc->imap->get_message_part($uid, $mime_id);
        }
        if ($part && is_array($result = $this->import_key($part))) {
            $this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
                array('new' => $result['imported'], 'old' => $result['unchanged']));
        }
        else
            $this->rc->output->show_message('enigma.keysimportfailed', 'error');
        $this->rc->output->send();
    }
    /**
     * Checks if specified message part contains body data.
     * If body is not set it will be fetched from IMAP server.
     *
     * @param rcube_message_part Message part object
     * @param integer            Message UID
     */
    private function set_part_body($part, $uid)
    {
        // @TODO: Create such function in core
        // @TODO: Handle big bodies using file handles
        if (!isset($part->body)) {
            $part->body = $this->rc->imap->get_message_part(
                $uid, $part->mime_id, $part);
        }
    }
    /**
     * Adds CSS style file to the page header.
     */
    private function add_css()
    {
        $skin = $this->rc->config->get('skin');
        if (!file_exists($this->home . "/skins/$skin/enigma.css"))
            $skin = 'default';
        $this->include_stylesheet("skins/$skin/enigma.css");
    }
}
plugins/enigma/lib/enigma_error.php
New file
@@ -0,0 +1,62 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Error class for the Enigma Plugin                                       |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_error
{
    private $code;
    private $message;
    private $data = array();
    // error codes
    const E_OK = 0;
    const E_INTERNAL = 1;
    const E_NODATA = 2;
    const E_KEYNOTFOUND = 3;
    const E_DELKEY = 4;
    const E_BADPASS = 5;
    function __construct($code = null, $message = '', $data = array())
    {
        $this->code = $code;
        $this->message = $message;
        $this->data = $data;
    }
    function getCode()
    {
        return $this->code;
    }
    function getMessage()
    {
        return $this->message;
    }
    function getData($name)
    {
        if ($name)
            return $this->data[$name];
        else
            return $this->data;
    }
}
plugins/enigma/lib/enigma_key.php
New file
@@ -0,0 +1,129 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Key class for the Enigma Plugin                                         |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_key
{
    public $id;
    public $name;
    public $users = array();
    public $subkeys = array();
    const TYPE_UNKNOWN = 0;
    const TYPE_KEYPAIR = 1;
    const TYPE_PUBLIC = 2;
    /**
     * Keys list sorting callback for usort()
     */
    static function cmp($a, $b)
    {
        return strcmp($a->name, $b->name);
    }
    /**
     * Returns key type
     */
    function get_type()
    {
        if ($this->subkeys[0]->has_private)
            return enigma_key::TYPE_KEYPAIR;
        else if (!empty($this->subkeys[0]))
            return enigma_key::TYPE_PUBLIC;
        return enigma_key::TYPE_UNKNOWN;
    }
    /**
     * Returns true if all user IDs are revoked
     */
    function is_revoked()
    {
        foreach ($this->subkeys as $subkey)
            if (!$subkey->revoked)
                return false;
        return true;
    }
    /**
     * Returns true if any user ID is valid
     */
    function is_valid()
    {
        foreach ($this->users as $user)
            if ($user->valid)
                return true;
        return false;
    }
    /**
     * Returns true if any of subkeys is not expired
     */
    function is_expired()
    {
        $now = time();
        foreach ($this->subkeys as $subkey)
            if (!$subkey->expires || $subkey->expires > $now)
                return true;
        return false;
    }
    /**
     * Converts long ID or Fingerprint to short ID
     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
     *
     * @param string Key ID or fingerprint
     * @return string Key short ID
     */
    static function format_id($id)
    {
        // E.g. 04622F2089E037A5 => 89E037A5
        return substr($id, -8);
    }
    /**
     * Formats fingerprint string
     *
     * @param string Key fingerprint
     *
     * @return string Formatted fingerprint (with spaces)
     */
    static function format_fingerprint($fingerprint)
    {
        if (!$fingerprint)
            return '';
        $result = '';
        for ($i=0; $i<40; $i++) {
            if ($i % 4 == 0)
                $result .= ' ';
            $result .= $fingerprint[$i];
        }
        return $result;
    }
}
plugins/enigma/lib/enigma_signature.php
New file
@@ -0,0 +1,34 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Signature class for the Enigma Plugin                                   |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_signature
{
    public $id;
    public $valid;
    public $fingerprint;
    public $created;
    public $expires;
    public $name;
    public $comment;
    public $email;
}
plugins/enigma/lib/enigma_subkey.php
New file
@@ -0,0 +1,57 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | SubKey class for the Enigma Plugin                                      |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_subkey
{
    public $id;
    public $fingerprint;
    public $expires;
    public $created;
    public $revoked;
    public $has_private;
    public $can_sign;
    public $can_encrypt;
    /**
     * Converts internal ID to short ID
     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
     *
     * @return string Key ID
     */
    function get_short_id()
    {
        // E.g. 04622F2089E037A5 => 89E037A5
        return enigma_key::format_id($this->id);
    }
    /**
     * Getter for formatted fingerprint
     *
     * @return string Formatted fingerprint
     */
    function get_fingerprint()
    {
        return enigma_key::format_fingerprint($this->fingerprint);
    }
}
plugins/enigma/lib/enigma_ui.php
New file
@@ -0,0 +1,459 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | User Interface for the Enigma Plugin                                    |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_ui
{
    private $rc;
    private $enigma;
    private $home;
    private $css_added;
    private $data;
    function __construct($enigma_plugin, $home='')
    {
        $this->enigma = $enigma_plugin;
        $this->rc = $enigma_plugin->rc;
        // we cannot use $enigma_plugin->home here
        $this->home = $home;
    }
    /**
     * UI initialization and requests handlers.
     *
     * @param string Preferences section
     */
    function init($section='')
    {
        $this->enigma->include_script('enigma.js');
        // Enigma actions
        if ($this->rc->action == 'plugin.enigma') {
            $action = get_input_value('_a', RCUBE_INPUT_GPC);
            switch ($action) {
                case 'keyedit':
                    $this->key_edit();
                    break;
                case 'keyimport':
                    $this->key_import();
                    break;
                case 'keysearch':
                case 'keylist':
                    $this->key_list();
                    break;
                case 'keyinfo':
                default:
                    $this->key_info();
            }
        }
        // Message composing UI
        else if ($this->rc->action == 'compose') {
            $this->compose_ui();
        }
        // Preferences UI
        else { // if ($this->rc->action == 'edit-prefs') {
            if ($section == 'enigmacerts') {
                $this->rc->output->add_handlers(array(
                    'keyslist' => array($this, 'tpl_certs_list'),
                    'keyframe' => array($this, 'tpl_cert_frame'),
                    'countdisplay' => array($this, 'tpl_certs_rowcount'),
                    'searchform' => array($this->rc->output, 'search_form'),
                ));
                $this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
                $this->rc->output->send('enigma.certs');
            }
            else {
                $this->rc->output->add_handlers(array(
                    'keyslist' => array($this, 'tpl_keys_list'),
                    'keyframe' => array($this, 'tpl_key_frame'),
                    'countdisplay' => array($this, 'tpl_keys_rowcount'),
                    'searchform' => array($this->rc->output, 'search_form'),
                ));
                $this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
                $this->rc->output->send('enigma.keys');
            }
        }
    }
   /**
     * Adds CSS style file to the page header.
     */
    function add_css()
    {
        if ($this->css_loaded)
            return;
        $skin = $this->rc->config->get('skin');
        if (!file_exists($this->home . "/skins/$skin/enigma.css"))
            $skin = 'default';
        $this->enigma->include_stylesheet("skins/$skin/enigma.css");
        $this->css_added = true;
    }
    /**
     * Template object for key info/edit frame.
     *
     * @param array Object attributes
     *
     * @return string HTML output
     */
    function tpl_key_frame($attrib)
    {
        if (!$attrib['id']) {
            $attrib['id'] = 'rcmkeysframe';
        }
        $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);
    }
    /**
     * Template object for list of keys.
     *
     * @param array Object attributes
     *
     * @return string HTML content
     */
    function tpl_keys_list($attrib)
    {
        // add id to message list table if not specified
        if (!strlen($attrib['id'])) {
            $attrib['id'] = 'rcmenigmakeyslist';
        }
        // define list of cols to be displayed
        $a_show_cols = array('name');
        // create XHTML table
        $out = rcube_table_output($attrib, array(), $a_show_cols, 'id');
        // set client env
        $this->rc->output->add_gui_object('keyslist', $attrib['id']);
        $this->rc->output->include_script('list.js');
        // add some labels to client
        $this->rc->output->add_label('enigma.keyconfirmdelete');
        return $out;
    }
    /**
     * Key listing (and searching) request handler
     */
    private function key_list()
    {
        $this->enigma->load_engine();
        $pagesize = $this->rc->config->get('pagesize', 100);
        $page     = max(intval(get_input_value('_p', RCUBE_INPUT_GPC)), 1);
        $search   = get_input_value('_q', RCUBE_INPUT_GPC);
        // define list of cols to be displayed
        $a_show_cols = array('name');
        $result = array();
        // Get the list
        $list = $this->enigma->engine->list_keys($search);
        if ($list && ($list instanceof enigma_error))
            $this->rc->output->show_message('enigma.keylisterror', 'error');
        else if (empty($list))
            $this->rc->output->show_message('enigma.nokeysfound', 'notice');
        else {
            if (is_array($list)) {
                // Save the size
                $listsize = count($list);
                // Sort the list by key (user) name
                usort($list, array('enigma_key', 'cmp'));
                // Slice current page
                $list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
                $size = count($list);
                // Add rows
                foreach($list as $idx => $key) {
                    $this->rc->output->command('enigma_add_list_row',
                        array('name' => Q($key->name), 'id' => $key->id));
                }
            }
        }
        $this->rc->output->set_env('search_request', $search);
        $this->rc->output->set_env('pagecount', ceil($listsize/$pagesize));
        $this->rc->output->set_env('current_page', $page);
        $this->rc->output->command('set_rowcount',
            $this->get_rowcount_text($listsize, $size, $page));
        $this->rc->output->send();
    }
    /**
     * Template object for list records counter.
     *
     * @param array Object attributes
     *
     * @return string HTML output
     */
    function tpl_keys_rowcount($attrib)
    {
        if (!$attrib['id'])
            $attrib['id'] = 'rcmcountdisplay';
        $this->rc->output->add_gui_object('countdisplay', $attrib['id']);
        return html::span($attrib, $this->get_rowcount_text());
    }
    /**
     * Returns text representation of list records counter
     */
    private function get_rowcount_text($all=0, $curr_count=0, $page=1)
    {
        if (!$curr_count)
            $out = $this->enigma->gettext('nokeysfound');
        else {
            $pagesize = $this->rc->config->get('pagesize', 100);
            $first = ($page - 1) * $pagesize;
            $out = $this->enigma->gettext(array(
                'name' => 'keysfromto',
                'vars' => array(
                    'from'  => $first + 1,
                    'to'    => $first + $curr_count,
                    'count' => $all)
            ));
        }
        return $out;
    }
    /**
     * Key information page handler
     */
    private function key_info()
    {
        $id = get_input_value('_id', RCUBE_INPUT_GET);
        $this->enigma->load_engine();
        $res = $this->enigma->engine->get_key($id);
        if ($res instanceof enigma_key)
            $this->data = $res;
        else { // error
            $this->rc->output->show_message('enigma.keyopenerror', 'error');
            $this->rc->output->command('parent.enigma_loadframe');
            $this->rc->output->send('iframe');
        }
        $this->rc->output->add_handlers(array(
            'keyname' => array($this, 'tpl_key_name'),
            'keydata' => array($this, 'tpl_key_data'),
        ));
        $this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
        $this->rc->output->send('enigma.keyinfo');
    }
    /**
     * Template object for key name
     */
    function tpl_key_name($attrib)
    {
        return Q($this->data->name);
    }
    /**
     * Template object for key information page content
     */
    function tpl_key_data($attrib)
    {
        $out = '';
        $table = new html_table(array('cols' => 2));
        // Key user ID
        $table->add('title', $this->enigma->gettext('keyuserid'));
        $table->add(null, Q($this->data->name));
        // Key ID
        $table->add('title', $this->enigma->gettext('keyid'));
        $table->add(null, $this->data->subkeys[0]->get_short_id());
        // Key type
        $keytype = $this->data->get_type();
        if ($keytype == enigma_key::TYPE_KEYPAIR)
            $type = $this->enigma->gettext('typekeypair');
        else if ($keytype == enigma_key::TYPE_PUBLIC)
            $type = $this->enigma->gettext('typepublickey');
        $table->add('title', $this->enigma->gettext('keytype'));
        $table->add(null, $type);
        // Key fingerprint
        $table->add('title', $this->enigma->gettext('fingerprint'));
        $table->add(null, $this->data->subkeys[0]->get_fingerprint());
        $out .= html::tag('fieldset', null,
            html::tag('legend', null,
                $this->enigma->gettext('basicinfo')) . $table->show($attrib));
        // Subkeys
        $table = new html_table(array('cols' => 6));
        // Columns: Type, ID, Algorithm, Size, Created, Expires
        $out .= html::tag('fieldset', null,
            html::tag('legend', null,
                $this->enigma->gettext('subkeys')) . $table->show($attrib));
        // Additional user IDs
        $table = new html_table(array('cols' => 2));
        // Columns: User ID, Validity
        $out .= html::tag('fieldset', null,
            html::tag('legend', null,
                $this->enigma->gettext('userids')) . $table->show($attrib));
        return $out;
    }
    /**
     * Key import page handler
     */
    private function key_import()
    {
        // Import process
        if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) {
            $this->enigma->load_engine();
            $result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true);
            if (is_array($result)) {
                // reload list if any keys has been added
                if ($result['imported']) {
                    $this->rc->output->command('parent.enigma_list', 1);
                }
                else
                    $this->rc->output->command('parent.enigma_loadframe');
                $this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
                    array('new' => $result['imported'], 'old' => $result['unchanged']));
                $this->rc->output->send('iframe');
            }
            else
                $this->rc->output->show_message('enigma.keysimportfailed', 'error');
        }
        else if ($err = $_FILES['_file']['error']) {
            if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
                $this->rc->output->show_message('filesizeerror', 'error',
                    array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
            } else {
                $this->rc->output->show_message('fileuploaderror', 'error');
            }
        }
        $this->rc->output->add_handlers(array(
            'importform' => array($this, 'tpl_key_import_form'),
        ));
        $this->rc->output->set_pagetitle($this->enigma->gettext('keyimport'));
        $this->rc->output->send('enigma.keyimport');
    }
    /**
     * Template object for key import (upload) form
     */
    function tpl_key_import_form($attrib)
    {
        $attrib += array('id' => 'rcmKeyImportForm');
        $upload = new html_inputfield(array('type' => 'file', 'name' => '_file',
            'id' => 'rcmimportfile', 'size' => 30));
        $form = html::p(null,
            Q($this->enigma->gettext('keyimporttext'), 'show')
            . html::br() . html::br() . $upload->show()
        );
        $this->rc->output->add_label('selectimportfile', 'importwait');
        $this->rc->output->add_gui_object('importform', $attrib['id']);
        $out = $this->rc->output->form_tag(array(
            'action' => $this->rc->url(array('action' => 'plugin.enigma', 'a' => 'keyimport')),
            'method' => 'post',
            'enctype' => 'multipart/form-data') + $attrib,
            $form);
        return $out;
    }
    private function compose_ui()
    {
        if (!is_array($_SESSION['compose']) || $_SESSION['compose']['id'] != get_input_value('_id', RCUBE_INPUT_GET))
            return;
        // Options menu button
        // @TODO: make this work with non-default skins
        $this->enigma->add_button(array(
            'name' => 'enigmamenu',
            'imagepas' => 'skins/default/enigma.png',
            'imageact' => 'skins/default/enigma.png',
            'onclick' => "rcmail_ui.show_popup('enigmamenu', true); return false",
            'title' => 'securityoptions',
            'domain' => 'enigma',
            ), 'toolbar');
        // Options menu contents
        $this->enigma->add_hook('render_page', array($this, 'compose_menu'));
    }
    function compose_menu($p)
    {
        $menu = new html_table(array('cols' => 2));
        $chbox = new html_checkbox(array('value' => 1));
        $menu->add(null, html::label(array('for' => 'enigmadefaultopt'),
            Q($this->enigma->gettext('identdefault'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_default', 'id' => 'enigmadefaultopt')));
        $menu->add(null, html::label(array('for' => 'enigmasignopt'),
            Q($this->enigma->gettext('signmsg'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_sign', 'id' => 'enigmasignopt')));
        $menu->add(null, html::label(array('for' => 'enigmacryptopt'),
            Q($this->enigma->gettext('encryptmsg'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_crypt', 'id' => 'enigmacryptopt')));
        $menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'),
            $menu->show());
        $p['content'] = preg_replace('/(<form name="form"[^>]+>)/i', '\\1'."\n$menu", $p['content']);
        return $p;
    }
}
plugins/enigma/lib/enigma_userid.php
New file
@@ -0,0 +1,31 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | User ID class for the Enigma Plugin                                     |
 |                                                                         |
 | 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.             |
 |                                                                         |
 +-------------------------------------------------------------------------+
 | Author: Aleksander Machniak <alec@alec.pl>                              |
 +-------------------------------------------------------------------------+
*/
class enigma_userid
{
    public $revoked;
    public $valid;
    public $name;
    public $comment;
    public $email;
}
plugins/enigma/localization/en_US.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels = array();
$labels['enigmasettings'] = 'Enigma: Settings';
$labels['enigmacerts'] = 'Enigma: Certificates (S/MIME)';
$labels['enigmakeys'] = 'Enigma: Keys (PGP)';
$labels['keysfromto'] = 'Keys $from to $to of $count';
$labels['keyname'] = 'Name';
$labels['keyid'] = 'Key ID';
$labels['keyuserid'] = 'User ID';
$labels['keytype'] = 'Key type';
$labels['fingerprint'] = 'Fingerprint';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = 'Basic Information';
$labels['userids'] = 'Additional User IDs';
$labels['typepublickey'] = 'public key';
$labels['typekeypair'] = 'key pair';
$labels['keyattfound'] = 'This message contains attached PGP key(s).';
$labels['keyattimport'] = 'Import key(s)';
$labels['createkeys'] = 'Create a new key pair';
$labels['importkeys'] = 'Import key(s)';
$labels['exportkeys'] = 'Export key(s)';
$labels['deletekeys'] = 'Delete key(s)';
$labels['keyactions'] = 'Key actions...';
$labels['keydisable'] = 'Disable key';
$labels['keyrevoke'] = 'Revoke key';
$labels['keysend'] = 'Send public key in a message';
$labels['keychpass'] = 'Change password';
$labels['securityoptions'] = 'Message security options...';
$labels['identdefault'] = 'Use settings of selected identity';
$labels['encryptmsg'] = 'Encrypt this message';
$labels['signmsg'] = 'Digitally sign this message';
$messages = array();
$messages['sigvalid'] = 'Verified signature from $sender.';
$messages['siginvalid'] = 'Invalid signature from $sender.';
$messages['signokey'] = 'Unverified signature. Public key not found. Key ID: $keyid.';
$messages['sigerror'] = 'Unverified signature. Internal error.';
$messages['decryptok'] = 'Message decrypted.';
$messages['decrypterror'] = 'Decryption failed.';
$messages['decryptnokey'] = 'Decryption failed. Private key not found. Key ID: $keyid.';
$messages['decryptbadpass'] = 'Decryption failed. Bad password.';
$messages['nokeysfound'] = 'No keys found';
$messages['keyopenerror'] = 'Unable to get key information! Internal error.';
$messages['keylisterror'] = 'Unable to list keys! Internal error.';
$messages['keysimportfailed'] = 'Unable to import key(s)! Internal error.';
$messages['keysimportsuccess'] = 'Key(s) imported successfully. Imported: $new, unchanged: $old.';
$messages['keyconfirmdelete'] = 'Are you sure, you want to delete selected key(s)?';
$messages['keyimporttext'] = 'You can import private and public key(s) or revocation signatures in ASCII-Armor format.';
?>
plugins/enigma/localization/ja_JP.inc
New file
@@ -0,0 +1,55 @@
<?php
//  EN-Revision: 4203
$labels = array();
$labels['enigmasettings'] = 'Enigma: 設定';
$labels['enigmacerts'] = 'Enigma: 証明書 (S/MIME)';
$labels['enigmakeys'] = 'Enigma: 鍵 (PGP)';
$labels['keysfromto'] = '鍵の一覧 $from ~ $to (合計: $count )';
$labels['keyname'] = '名前';
$labels['keyid'] = '鍵 ID';
$labels['keyuserid'] = 'ユーザー ID';
$labels['keytype'] = '鍵の種類';
$labels['fingerprint'] = '指紋';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = '基本情報';
$labels['userids'] = '追加のユーザー ID';
$labels['typepublickey'] = '公開鍵';
$labels['typekeypair'] = '鍵のペア';
$labels['keyattfound'] = 'このメールは PGP 鍵の添付があります。';
$labels['keyattimport'] = '鍵のインポート';
$labels['createkeys'] = '新しい鍵のペアを作成する';
$labels['importkeys'] = '鍵のインポート';
$labels['exportkeys'] = '鍵のエクスポート';
$labels['deletekeys'] = '鍵の削除';
$labels['keyactions'] = '鍵の操作...';
$labels['keydisable'] = '鍵を無効にする';
$labels['keyrevoke'] = '鍵を取り消す';
$labels['keysend'] = 'メッセージに公開鍵を含んで送信する';
$labels['keychpass'] = 'パスワードの変更';
$labels['securityoptions'] = 'メールのセキュリティ オプション...';
$labels['identdefault'] = '選択した識別子の設定を使う';
$labels['encryptmsg'] = 'このメールの暗号化';
$labels['signmsg'] = 'このメールのデジタル署名';
$messages = array();
$messages['sigvalid'] = '$sender からの署名を検証しました。';
$messages['siginvalid'] = '$sender からの署名が正しくありません。';
$messages['signokey'] = '署名は未検証です。公開鍵が見つかりません。鍵 ID: $keyid';
$messages['sigerror'] = '署名は未検証です。内部エラーです。';
$messages['decryptok'] = 'メールを復号しました。';
$messages['decrypterror'] = '復号に失敗しました。';
$messages['decryptnokey'] = '復号に失敗しました。秘密鍵が見つかりません。鍵 ID: $keyid.';
$messages['decryptbadpass'] = '復号に失敗しました。パスワードが正しくありません。';
$messages['nokeysfound'] = '鍵が見つかりません。';
$messages['keyopenerror'] = '鍵情報の取得に失敗しました! 内部エラーです。';
$messages['keylisterror'] = '鍵情報のリストに失敗しました! 内部エラーです。';
$messages['keysimportfailed'] = '鍵のインポートに失敗しました! 内部エラーです。';
$messages['keysimportsuccess'] = '鍵をインポートしました。インポート: $new, 未変更: $old';
$messages['keyconfirmdelete'] = '選択した鍵を本当に削除しますか?';
$messages['keyimporttext'] = '秘密鍵と公開鍵のインポート、または ASCII 形式の署名を無効にできます。';
?>
plugins/enigma/skins/default/enigma.css
New file
@@ -0,0 +1,182 @@
/*** Style for Enigma plugin ***/
/***** Messages displaying *****/
#enigma-message,
/* fixes border-top */
#messagebody div #enigma-message
{
  margin: 0;
  margin-bottom: 5px;
  min-height: 20px;
  padding: 10px 10px 6px 46px;
}
div.enigmaerror,
/* fixes border-top */
#messagebody div.enigmaerror
{
  background: url(enigma_error.png) 6px 1px no-repeat;
  background-color: #EF9398;
  border: 1px solid #DC5757;
}
div.enigmanotice,
/* fixes border-top */
#messagebody div.enigmanotice
{
  background: url(enigma.png) 6px 1px no-repeat;
  background-color: #A6EF7B;
  border: 1px solid #76C83F;
}
div.enigmawarning,
/* fixes border-top */
#messagebody div.enigmawarning
{
  background: url(enigma.png) 6px 1px no-repeat;
  background-color: #F7FDCB;
  border: 1px solid #C2D071;
}
#enigma-message a
{
  color: #666666;
  padding-left: 10px;
}
#enigma-message a:hover
{
  color: #333333;
}
/***** Keys/Certs Management *****/
div.enigmascreen
{
  position: absolute;
  top: 65px;
  right: 10px;
  bottom: 10px;
  left: 10px;
}
#enigmacontent-box
{
  position: absolute;
  top: 0px;
  left: 290px;
  right: 0px;
  bottom: 0px;
  border: 1px solid #999999;
  overflow: hidden;
}
#enigmakeyslist
{
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  border: 1px solid #999999;
  background-color: #F9F9F9;
  overflow: hidden;
}
#keylistcountbar
{
  margin-top: 4px;
  margin-left: 4px;
}
#keys-table
{
  width: 100%;
  table-layout: fixed;
}
#keys-table td
{
  cursor: default;
  text-overflow: ellipsis;
  -o-text-overflow: ellipsis;
}
#key-details table td.title
{
  font-weight: bold;
  text-align: right;
}
#keystoolbar
{
  position: absolute;
  top: 30px;
  left: 10px;
  height: 35px;
}
#keystoolbar a
{
  padding-right: 10px;
}
#keystoolbar a.button,
#keystoolbar a.buttonPas,
#keystoolbar span.separator {
  display: block;
  float: left;
  width: 32px;
  height: 32px;
  padding: 0;
  margin-right: 10px;
  overflow: hidden;
  background: url(keys_toolbar.png) 0 0 no-repeat transparent;
  opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#keystoolbar a.buttonPas {
  opacity: 0.35;
}
#keystoolbar a.createSel {
  background-position: 0 -32px;
}
#keystoolbar a.create {
  background-position: 0 0;
}
#keystoolbar a.deleteSel {
  background-position: -32px -32px;
}
#keystoolbar a.delete {
  background-position: -32px 0;
}
#keystoolbar a.importSel {
  background-position: -64px -32px;
}
#keystoolbar a.import {
  background-position: -64px 0;
}
#keystoolbar a.exportSel {
  background-position: -96px -32px;
}
#keystoolbar a.export {
  background-position: -96px 0;
}
#keystoolbar a.keymenu {
  background-position: -128px 0;
  width: 36px;
}
#keystoolbar span.separator {
  width: 5px;
  background-position: -166px 0;
}
plugins/enigma/skins/default/enigma.png
plugins/enigma/skins/default/enigma_error.png
plugins/enigma/skins/default/key.png
plugins/enigma/skins/default/key_add.png
plugins/enigma/skins/default/keys_toolbar.png
plugins/enigma/skins/default/templates/keyimport.html
New file
@@ -0,0 +1,20 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
</head>
<body class="iframe">
<div id="keyimport-title" class="boxtitle"><roundcube:label name="enigma.importkeys" /></div>
<div id="import-form" class="boxcontent">
    <roundcube:object name="importform" />
    <p>
        <br /><roundcube:button command="plugin.enigma-import" type="input" class="button mainaction" label="import" />
    </p>
</div>
</body>
</html>
plugins/enigma/skins/default/templates/keyinfo.html
New file
@@ -0,0 +1,17 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
</head>
<body class="iframe">
<div id="keyinfo-title" class="boxtitle"><roundcube:object name="keyname" part="name" /></div>
<div id="key-details" class="boxcontent">
    <roundcube:object name="keydata" />
</div>
</body>
</html>
plugins/enigma/skins/default/templates/keys.html
New file
@@ -0,0 +1,76 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#enigmakeyslist { width: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter-5 : 210" />px; }
#enigmacontent-box { left: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter+5 : 220" />px;
<roundcube:exp expression="browser:ie ? ('width:expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:enigmaeviewsplitter) ? cookie:enigmaviewsplitter+5 : 220).')+\\'px\\');') : ''" />
}
</style>
</head>
<body class="iframe" onload="rcube_init_mail_ui()">
<div id="prefs-title" class="boxtitle"><roundcube:label name="enigma.enigmakeys" /></div>
<div id="prefs-details" class="boxcontent">
<div id="keystoolbar">
    <roundcube:button command="plugin.enigma-key-create" type="link" class="buttonPas create" classAct="button create" classSel="button createSel" title="enigma.createkeys" content=" " />
    <roundcube:button command="plugin.enigma-key-delete" type="link" class="buttonPas delete" classAct="button delete" classSel="button deleteSel" title="enigma.deletekeys" content=" " />
    <span class="separator">&nbsp;</span>
    <roundcube:button command="plugin.enigma-key-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="enigma.importkeys" content=" " />
    <roundcube:button command="plugin.enigma-key-export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="enigma.exportkeys" content=" " />
    <roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button keymenu" title="enigma.keyactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " />
</div>
<div id="quicksearchbar" style="top: 35px; right: 10px;">
    <roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
    <roundcube:object name="searchform" id="quicksearchbox" />
    <roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
</div>
<div class="enigmascreen">
<div id="enigmakeyslist">
<div class="boxtitle"><roundcube:label name="enigma.keyname" /></div>
<div class="boxlistcontent">
    <roundcube:object name="keyslist" id="keys-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="keylistcountbar" class="pagenav">
    <roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
    <roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
    <roundcube:object name="countdisplay" style="padding:0 .5em; float:left" />
    <roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
    <roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
</div>
<script type="text/javascript">
    var enigmaviewsplit = new rcube_splitter({id:'enigmaviewsplitter', p1: 'enigmakeyslist', p2: 'enigmacontent-box', orientation: 'v', relative: true, start: 215});
    rcmail.add_onload('enigmaviewsplit.init()');
</script>
<div id="enigmacontent-box">
    <roundcube:object name="keyframe" id="keyframe" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>
</div>
</div>
<div id="messagemenu" class="popupmenu">
    <ul class="toolbarmenu">
        <li><roundcube:button class="disablelink" command="enigma.key-disable" label="enigma.keydisable" target="_blank" classAct="disablelink active" /></li>
        <li><roundcube:button class="revokelink" command="enigma.key-revoke" label="enigma.keyrevoke" classAct="revokelink active" /></li>
        <li class="separator_below"><roundcube:button class="sendlink" command="enigma.key-send" label="enigma.keysend" classAct="sendlink active" /></li>
        <li><roundcube:button class="chpasslink" command="enigma.key-chpass" label="enigma.keychpass" classAct="chpasslink active" /></li>
    </ul>
</div>
</body>
</html>
plugins/example_addressbook/example_addressbook.php
New file
@@ -0,0 +1,49 @@
<?php
require_once(dirname(__FILE__) . '/example_addressbook_backend.php');
/**
 * Sample plugin to add a new address book
 * with just a static list of contacts
 */
class example_addressbook extends rcube_plugin
{
  private $abook_id = 'static';
  public function init()
  {
    $this->add_hook('addressbooks_list', array($this, 'address_sources'));
    $this->add_hook('addressbook_get', 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 = (array) $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)
  {
    $abook = new example_addressbook_backend;
    $p['sources'][$this->abook_id] = array(
      'id' => $this->abook_id,
      'name' => 'Static List',
      'readonly' => $abook->readonly,
      'groups' => $abook->groups,
    );
    return $p;
  }
  public function get_address_book($p)
  {
    if ($p['id'] === $this->abook_id) {
      $p['instance'] = new example_addressbook_backend;
    }
    return $p;
  }
}
plugins/example_addressbook/example_addressbook_backend.php
New file
@@ -0,0 +1,109 @@
<?php
/**
 * Example backend class for a custom address book
 *
 * This one just holds a static list of address records
 *
 * @author Thomas Bruederli
 */
class example_addressbook_backend extends rcube_addressbook
{
  public $primary_key = 'ID';
  public $readonly = true;
  public $groups = true;
  private $filter;
  private $result;
  public function __construct()
  {
    $this->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;
  }
  function list_groups($search = null)
  {
    return array(
      array('ID' => 'testgroup1', 'name' => "Testgroup"),
      array('ID' => 'testgroup2', 'name' => "Sample Group"),
    );
  }
  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, $nocount=false, $required=array())
  {
    // 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;
  }
  function create_group($name)
  {
    $result = false;
    return $result;
  }
  function delete_group($gid)
  {
    return false;
  }
  function rename_group($gid, $newname)
  {
    return $newname;
  }
  function add_to_group($group_id, $ids)
  {
    return false;
  }
  function remove_from_group($group_id, $ids)
  {
     return false;
  }
}
plugins/filesystem_attachments/filesystem_attachments.php
New file
@@ -0,0 +1,155 @@
<?php
/**
 * Filesystem Attachments
 *
 * This is a core plugin which provides basic, filesystem based
 * attachment temporary file handling.  This includes storing
 * attachments of messages currently being composed, writing attachments
 * to disk when drafts with attachments are re-opened and writing
 * attachments to disk for inline display in current html compositions.
 *
 * Developers may wish to extend this class when creating attachment
 * handler plugins:
 *   require_once('plugins/filesystem_attachments/filesystem_attachments.php');
 *   class myCustom_attachments extends filesystem_attachments
 *
 * @author Ziba Scott <ziba@umich.edu>
 * @author Thomas Bruederli <roundcube@gmail.com>
 *
 */
class filesystem_attachments extends rcube_plugin
{
    public $task = 'mail';
    function init()
    {
        // Save a newly uploaded attachment
        $this->add_hook('attachment_upload', array($this, 'upload'));
        // Save an attachment from a non-upload source (draft or forward)
        $this->add_hook('attachment_save', array($this, 'save'));
        // Remove an attachment from storage
        $this->add_hook('attachment_delete', array($this, 'remove'));
        // When composing an html message, image attachments may be shown
        $this->add_hook('attachment_display', array($this, 'display'));
        // Get the attachment from storage and place it on disk to be sent
        $this->add_hook('attachment_get', array($this, 'get'));
        // Delete all temp files associated with this user
        $this->add_hook('attachments_cleanup', array($this, 'cleanup'));
        $this->add_hook('session_destroy', 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
        $temp_dir = $rcmail->config->get('temp_dir');
        $tmpfname = tempnam($temp_dir, 'rcmAttmnt');
        if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) {
            $args['id'] = $this->file_id();
            $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 = $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'] = $this->file_id();
        $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($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;
    }
    function file_id()
    {
        $userid = rcmail::get_instance()->user->ID;
        list($usec, $sec) = explode(' ', microtime());
        return preg_replace('/[^0-9]/', '', $userid . $sec . $usec);
    }
}
plugins/help/config.inc.php.dist
New file
@@ -0,0 +1,5 @@
<?php
// Help content iframe source
// $rcmail_config['help_source'] = 'http://trac.roundcube.net/wiki';
$rcmail_config['help_source'] = '';
plugins/help/content/about.html
New file
@@ -0,0 +1,39 @@
<div id="helpabout">
<h3 align="center">Copyright &copy; 2005-2010, The Roundcube Dev Team</h3>
<p>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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<div align="center">
<h3>Project management and administration</h3>
<b>Thomas Bruederli (thomasb)</b> - Project leader and head developer<br />
<b>Till Klampäckel (till)</b> - Co-leader<br />
<b>Brett Patterson</b> - Forum administrator<br />
<b>Adam Grelck</b> - Trac administrator<br />
<b>Jason Fesler</b> - Mailing list administrator<br />
<b>Brennan Stehling</b> - Mentor, Coordinator
<h3>Developers</h3>
<b>Eric Stadtherr (estadtherr)</b><br />
<b>Robin Elfrink (robin, wobin)</b><br />
<b>Rich Sandberg (richs)</b><br />
<b>Tomasz Pajor (tomekp)</b><br />
<b>Fourat Zouari (fourat.zouari)</b><br />
<b>Aleksander Machniak (alec)</b>
<p><br/>Website: <a href="http://roundcube.net">roundcube.net</a></p>
</div>
</div>
plugins/help/content/license.html
New file
@@ -0,0 +1,387 @@
<div id="helplicense">
<h3>GNU GENERAL PUBLIC LICENSE</h3>
<p>
Version 2, June 1991
</p>
<pre>
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.
</pre>
<h3>Preamble</h3>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
  The precise terms and conditions for copying, distribution and
modification follow.
</p>
<h3>TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</h3>
<p>
<strong>0.</strong>
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".
</p>
<p>
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.
</p>
<p>
<strong>1.</strong>
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.
</p>
<p>
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.
</p>
<p>
<strong>2.</strong>
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:
</p>
<dl>
  <dt></dt>
    <dd>
      <strong>a)</strong>
      You must cause the modified files to carry prominent notices
      stating that you changed the files and the date of any change.
    </dd>
  <dt></dt>
    <dd>
      <strong>b)</strong>
      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.
    </dd>
  <dt></dt>
    <dd>
      <strong>c)</strong>
      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.)
    </dd>
</dl>
<p>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
<strong>3.</strong>
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:
</p>
<dl>
  <dt></dt>
    <dd>
      <strong>a)</strong>
      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,
    </dd>
  <dt></dt>
    <dd>
      <strong>b)</strong>
      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,
    </dd>
  <dt></dt>
    <dd>
      <strong>c)</strong>
      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.)
    </dd>
</dl>
<p>
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.
</p>
<p>
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.
</p>
<p>
<strong>4.</strong>
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.
</p>
<p>
<strong>5.</strong>
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.
</p>
<p>
<strong>6.</strong>
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.
</p>
<p>
<strong>7.</strong>
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.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
</p>
<p>
<strong>8.</strong>
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.
</p>
<p>
<strong>9.</strong>
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.
</p>
<p>
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.
</p>
<p>
<strong>10.</strong>
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.
</p>
<p><strong>NO WARRANTY</strong></p>
<p>
<strong>11.</strong>
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.
</p>
<p>
<strong>12.</strong>
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.
</p>
</div>
plugins/help/help.php
New file
@@ -0,0 +1,107 @@
<?php
/**
 * Help Plugin
 *
 * @author Aleksander 'A.L.E.C' Machniak
 * @licence GNU GPL
 *
 * Configuration (see config.inc.php.dist)
 *
 **/
class help extends rcube_plugin
{
    // all task excluding 'login' and 'logout'
    public $task = '?(?!login|logout).*';
    // we've got no ajax handlers
    public $noajax = true;
    // skip frames
    public $noframe = true;
    function init()
    {
        $rcmail = rcmail::get_instance();
        $this->add_texts('localization/', false);
        // register task
        $this->register_task('help');
        // register actions
        $this->register_action('', array($this, 'action'));
        $this->register_action('about', array($this, 'action'));
        $this->register_action('license', array($this, 'action'));
        // add taskbar button
        $this->add_button(array(
            'name'     => 'helptask',
            'class'    => 'button-help',
            'label'    => 'help.help',
            'href'    => './?_task=help',
            'onclick' => sprintf("return %s.command('help')", JS_OBJECT_NAME)
            ), 'taskbar');
        $rcmail->output->add_script(
            JS_OBJECT_NAME . ".enable_command('help', true);\n" .
            JS_OBJECT_NAME . ".help = function () { location.href = './?_task=help'; }",
            'head');
        $skin = $rcmail->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 == 'about')
            $rcmail->output->set_pagetitle($this->gettext('about'));
        else if ($rcmail->action == 'license')
            $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 == 'about') {
            return @file_get_contents($this->home.'/content/about.html');
        }
        else if ($rcmail->action == 'license') {
            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 <iframe> tag
        $attrib_str = create_attrib_string($attrib, array(
            'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
        $out = sprintf('<iframe name="%s"%s></iframe>'."\n", $attrib['id'], $attrib_str);
        return $out;
    }
}
plugins/help/localization/cs_CZ.inc
New file
@@ -0,0 +1,25 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube help plugin                            |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
| Author: Milan Kozak <hodza@hodza.net>                                 |
+-----------------------------------------------------------------------+
@version $Id: labels.inc 2993 2009-09-26 18:32:07Z alec $
*/
$labels = array();
$labels['help'] = 'Nápověda';
$labels['about'] = 'O aplikaci';
$labels['license'] = 'Licence';
?>
plugins/help/localization/da_DK.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Hjælp';
$labels['about'] = 'Om';
$labels['license'] = 'Licens';
?>
plugins/help/localization/de_DE.inc
New file
@@ -0,0 +1,8 @@
<?php
// translation done by Ulli Heist - http://heist.hobby-site.org/
$labels = array();
$labels['help'] = 'Hilfe';
$labels['about'] = '&Uuml;ber';
$labels['license'] = 'Lizenz';
?>
plugins/help/localization/en_GB.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Help';
$labels['about'] = 'About';
$labels['license'] = 'License';
?>
plugins/help/localization/en_US.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Help';
$labels['about'] = 'About';
$labels['license'] = 'License';
?>
plugins/help/localization/es_ES.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Ayuda';
$labels['about'] = 'Acerca de';
$labels['license'] = 'Licencia';
?>
plugins/help/localization/et_EE.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Abi';
$labels['about'] = 'Roundcube info';
$labels['license'] = 'Litsents';
?>
plugins/help/localization/hu_HU.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Segítség';
$labels['about'] = 'Névjegy';
$labels['license'] = 'Licenc';
?>
plugins/help/localization/ja_JP.inc
New file
@@ -0,0 +1,10 @@
<?php
//  EN-Revision: 3891
$labels = array();
$labels['help'] = 'ヘルプ';
$labels['about'] = '紹介';
$labels['license'] = 'ライセンス';
?>
plugins/help/localization/pl_PL.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Pomoc';
$labels['about'] = 'O programie';
$labels['license'] = 'Licencja';
?>
plugins/help/localization/ru_RU.inc
New file
@@ -0,0 +1,23 @@
<?php
/*
+-----------------------------------------------------------------------+
| plugins/help/localization/ru_RU.inc                                   |
|                                                                       |
| Language file of the Roundcube help plugin                            |
| Copyright (C) 2005-2010, Roundcube Dev. - Switzerland                 |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
| Author: Sergey Dukachev <iam@dukess.ru>                               |
+-----------------------------------------------------------------------+
*/
$labels = array();
$labels['help'] = 'Помощь';
$labels['about'] = 'О программе';
$labels['license'] = 'Лицензия';
?>
plugins/help/localization/sv_SE.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = 'Hjälp';
$labels['about'] = 'Om';
$labels['license'] = 'Licens';
?>
plugins/help/localization/zh_TW.inc
New file
@@ -0,0 +1,8 @@
<?php
$labels = array();
$labels['help'] = '說明';
$labels['about'] = '關於';
$labels['license'] = '許可證';
?>
plugins/help/skins/default/help.css
New file
@@ -0,0 +1,29 @@
/***** Roundcube|Mail Help task styles *****/
#taskbar a.button-help
{
  background-image: url('help.gif');
}
.help-box
{
  overflow: auto;
  background-color: #F2F2F2;
}
#helplicense, #helpabout
{
  width: 46em;
  padding: 1em 2em;
}
#helplicense a, #helpabout a
{
  color: #900;
}
#helpabout
{
  margin: 0 auto;
}
plugins/help/skins/default/help.gif
plugins/help/skins/default/templates/help.html
New file
@@ -0,0 +1,37 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/help.css" />
<link rel="stylesheet" type="text/css" href="/settings.css" />
<script type="text/javascript">
function help_init_settings_tabs()
{
    var action, tab = '#helptabdefault';
    if (window.rcmail && (action = rcmail.env.action)) {
        tab = '#helptab' + (action ? action : 'default');
    }
    $(tab).addClass('tablink-selected');
}
</script>
</head>
<body>
<roundcube:include file="/includes/taskbar.html" />
<roundcube:include file="/includes/header.html" />
<div id="tabsbar">
<span id="helptabdefault" class="tablink"><roundcube:button name="helpdefault" href="?_task=help" type="link" label="help.help" title="help.help" /></span>
<span id="helptababout" class="tablink"><roundcube:button name="helpabout" href="?_task=help&_action=about" type="link" label="help.about" title="help.about" class="tablink" /></span>
<span id="helptablicense" class="tablink"><roundcube:button name="helplicense" href="?_task=help&_action=license" type="link" label="help.license" title="help.license" class="tablink" /></span>
<roundcube:container name="helptabs" id="helptabsbar" />
<script type="text/javascript"> if (window.rcmail) rcmail.add_onload(help_init_settings_tabs);</script>
</div>
<div id="mainscreen" class="box help-box">
<roundcube:object name="helpcontent" id="helpcontentframe" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>
</body>
</html>
plugins/http_authentication/http_authentication.php
New file
@@ -0,0 +1,44 @@
<?php
/**
 * HTTP Basic Authentication
 *
 * Make use of an existing HTTP authentication and perform login with the existing user credentials
 *
 * @version 1.1
 * @author Thomas Bruederli
 */
class http_authentication extends rcube_plugin
{
  public $task = 'login';
  function init()
  {
    $this->add_hook('startup', array($this, 'startup'));
    $this->add_hook('authenticate', array($this, 'authenticate'));
  }
  function startup($args)
  {
    // change action to login
    if (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'];
    }
    $args['cookiecheck'] = false;
    return $args;
  }
}
plugins/kolab_addressbook/kolab_addressbook.php
New file
@@ -0,0 +1,140 @@
<?php
require_once(dirname(__FILE__) . '/rcube_kolab_contacts.php');
/**
 * Kolab address book
 *
 * Sample plugin to add a new address book source with data from Kolab storage
 * This is work-in-progress for the Roundcube+Kolab integration.
 *
 * @author Thomas Bruederli <roundcube@gmail.com>
 *
 */
class kolab_addressbook extends rcube_plugin
{
    private $folders;
    private $sources;
    /**
     * Required startup method of a Roundcube plugin
     */
    public function init()
    {
        // load required plugin
        $this->require_plugin('kolab_core');
        $this->add_texts('localization');
        // register hooks
        $this->add_hook('addressbooks_list', array($this, 'address_sources'));
        $this->add_hook('addressbook_get', array($this, 'get_address_book'));
        $this->add_hook('contact_form', array($this, 'contact_form'));
        // extend list of address sources to be used for autocompletion
        $rcmail = rcmail::get_instance();
        if ($rcmail->action == 'autocomplete' || $rcmail->action == 'group-expand') {
            $sources = (array) $rcmail->config->get('autocomplete_addressbooks', array());
            foreach ($this->_list_sources() as $abook_id => $abook) {
                if (!in_array($abook_id, $sources))
                    $sources[] = $abook_id;
            }
            $rcmail->config->set('autocomplete_addressbooks', $sources);
        }
    }
    /**
     * Handler for the addressbooks_list hook.
     *
     * This will add all instances of available Kolab-based address books
     * to the list of address sources of Roundcube.
     *
     * @param array Hash array with hook parameters
     * @return array Hash array with modified hook parameters
     */
    public function address_sources($p)
    {
        foreach ($this->_list_sources() as $abook_id => $abook) {
            // register this address source
            $p['sources'][$abook_id] = array(
                'id' => $abook_id,
                'name' => $abook->get_name(),
                'readonly' => $abook->readonly,
                'groups' => $abook->groups,
            );
        }
        return $p;
    }
    /**
     * Getter for the rcube_addressbook instance
     */
    public function get_address_book($p)
    {
        if ($this->sources[$p['id']]) {
            $p['instance'] = $this->sources[$p['id']];
        }
        return $p;
    }
    private function _list_sources()
    {
        // already read sources
        if (isset($this->sources))
            return $this->sources;
        // get all folders that have "contact" type
        $this->folders = rcube_kolab::get_folders('contact');
        $this->sources = array();
        if (PEAR::isError($this->folders)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Failed to list contact folders from Kolab server:" . $this->folders->getMessage()),
            true, false);
        }
        else {
            foreach ($this->folders as $c_folder) {
                // create instance of rcube_contacts
                $abook_id = strtolower(asciiwords(strtr($c_folder->name, '/.', '--')));
                $abook = new rcube_kolab_contacts($c_folder->name);
                $this->sources[$abook_id] = $abook;
            }
        }
        return $this->sources;
    }
    /**
     * Plugin hook called before rendering the contact form or detail view
     */
    public function contact_form($p)
    {
        // extend the list of contact fields to be displayed in the 'info' section
        if (is_array($p['form']['info'])) {
            $p['form']['info']['content']['initials'] = array('size' => 6);
            $p['form']['info']['content']['anniversary'] = array('size' => 12, 'render_func' => 'rcmail_format_date_col');
            // TODO: add more Kolab-specific fields
            // re-order fields according to the coltypes list
            $block = array();
            $contacts = reset($this->sources);
            foreach ($contacts->coltypes as $col => $prop) {
                if (isset($p['form']['info']['content'][$col]))
                    $block[$col] = $p['form']['info']['content'][$col];
            }
            $p['form']['info']['content'] = $block;
        }
        return $p;
    }
}
plugins/kolab_addressbook/localization/en_US.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['initials'] = 'Initials';
$labels['anniversary'] = 'Anniversary';
?>
plugins/kolab_addressbook/rcube_kolab_contacts.php
New file
@@ -0,0 +1,829 @@
<?php
/**
 * Backend class for a custom address book
 *
 * This part of the Roundcube+Kolab integration and connects the
 * rcube_addressbook interface with the rcube_kolab wrapper for Kolab_Storage
 *
 * @author Thomas Bruederli
 * @see rcube_addressbook
 */
class rcube_kolab_contacts extends rcube_addressbook
{
    public $primary_key = 'ID';
    public $readonly = false;
    public $groups = true;
    public $coltypes = array(
      'name'         => array('limit' => 1),
      'firstname'    => array('limit' => 1),
      'surname'      => array('limit' => 1),
      'middlename'   => array('limit' => 1),
      'prefix'       => array('limit' => 1),
      'suffix'       => array('limit' => 1),
      'nickname'     => array('limit' => 1),
      'jobtitle'     => array('limit' => 1),
      'organization' => array('limit' => 1),
      'department'   => array('limit' => 1),
      'gender'       => array('limit' => 1),
      'initials'     => array('type' => 'text', 'size' => 6, 'limit' => 1, 'label' => 'kolab_addressbook.initials'),
      'email'        => array('subtypes' => null),
      'phone'        => array(),
      'im'           => array('limit' => 1),
      'website'      => array('limit' => 1, 'subtypes' => null),
      'address'      => array('limit' => 2, 'subtypes' => array('home','business')),
      'birthday'     => array('limit' => 1),
      'anniversary'  => array('type' => 'date', 'size' => 12, 'limit' => 1, 'label' => 'kolab_addressbook.anniversary'),
      // TODO: define more Kolab-specific fields such as: office-location, profession, manager-name, assistant, spouse-name, children, language, latitude, longitude, pgp-publickey, free-busy-url
      'notes'        => array(),
    );
    private $gid;
    private $imap;
    private $kolab;
    private $folder;
    private $contactstorage;
    private $liststorage;
    private $contacts;
    private $distlists;
    private $groupmembers;
    private $id2uid;
    private $filter;
    private $result;
    private $imap_folder = 'INBOX/Contacts';
    private $gender_map = array(0 => 'male', 1 => 'female');
    private $phonetypemap = array('home' => 'home1', 'work' => 'business1', 'work2' => 'business2', 'workfax' => 'businessfax');
    private $addresstypemap = array('work' => 'business');
    private $fieldmap = array(
      // kolab       => roundcube
      'full-name'    => 'name',
      'given-name'   => 'firstname',
      'middle-names' => 'middlename',
      'last-name'    => 'surname',
      'prefix'       => 'prefix',
      'suffix'       => 'suffix',
      'nick-name'    => 'nickname',
      'organization' => 'organization',
      'department'   => 'department',
      'job-title'    => 'jobtitle',
      'initials'     => 'initials',
      'birthday'     => 'birthday',
      'anniversary'  => 'anniversary',
      'im-address'   => 'im:aim',
      'web-page'     => 'website',
      'body'         => 'notes',
    );
    public function __construct($imap_folder = null)
    {
        if ($imap_folder)
            $this->imap_folder = $imap_folder;
        // extend coltypes configuration
        $format = rcube_kolab::get_format('contact');
        $this->coltypes['phone']['subtypes'] = $format->_phone_types;
        $this->coltypes['address']['subtypes'] = $format->_address_types;
        // set localized labels for proprietary cols
        foreach ($this->coltypes as $col => $prop) {
            if (is_string($prop['label']))
                $this->coltypes[$col]['label'] = rcube_label($prop['label']);
        }
        // fetch objects from the given IMAP folder
        $this->contactstorage = rcube_kolab::get_storage($this->imap_folder);
        $this->liststorage = rcube_kolab::get_storage($this->imap_folder, 'distributionlist');
        $this->ready = !PEAR::isError($this->contactstorage) && !PEAR::isError($this->liststorage);
    }
    /**
     * Getter for the address book name to be displayed
     *
     * @return string Name of this address book
     */
    public function get_name()
    {
        return strtr(preg_replace('!^(INBOX|user)/!i', '', $this->imap_folder), '/', ':');
    }
    /**
     * Setter for the current group
     */
    public function set_group($gid)
    {
        $this->gid = $gid;
    }
    /**
     * Save a search string for future listings
     *
     * @param mixed Search params to use in listing method, obtained by get_search_set()
     */
    public function set_search_set($filter)
    {
        $this->filter = $filter;
    }
    /**
     * Getter for saved search properties
     *
     * @return mixed Search properties used by this class
     */
    public function get_search_set()
    {
        return $this->filter;
    }
    /**
     * Reset saved results and search parameters
     */
    public function reset()
    {
        $this->result = null;
        $this->filter = null;
    }
    /**
     * List all active contact groups of this source
     *
     * @param string  Optional search string to match group name
     * @return array  Indexed list of contact groups, each a hash array
     */
    function list_groups($search = null)
    {
        $this->_fetch_groups();
        $groups = array();
        foreach ((array)$this->distlists as $group) {
            if (!$search || strstr(strtolower($group['last-name']), strtolower($search)))
                $groups[] = array('ID' => $group['ID'], 'name' => $group['last-name']);
        }
        return $groups;
    }
    /**
     * 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
     */
    public function list_records($cols=null, $subset=0)
    {
        $this->result = $this->count();
        // list member of the selected group
        if ($this->gid) {
            $seen = array();
            $this->result->count = 0;
            foreach ((array)$this->distlists[$this->gid]['member'] as $member) {
                // skip member that don't match the search filter
                if ($this->filter && array_search($member['ID'], $this->filter) === false)
                    continue;
                if ($this->contacts[$member['ID']] && !$seen[$member['ID']]++)
                    $this->result->count++;
            }
            $ids = array_keys($seen);
        }
        else
            $ids = $this->filter ? $this->filter : array_keys($this->contacts);
        // fill contact data into the current result set
        $i = $j = 0;
        foreach ($ids as $id) {
            if ($i++ < $this->result->first)
                continue;
            $this->result->add($this->contacts[$id]);
            if (++$j == $this->page_size)
                break;
        }
        return $this->result;
    }
    /**
     * 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
     * @param boolean True to skip the count query (select only)
     * @param array   List of fields that cannot be empty
     * @return object rcube_result_set List of contact records and 'count' value
     */
    public function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array())
    {
        $this->_fetch_contacts();
        // search by ID
        if ($fields == $this->primary_key) {
            return $this->get_record($value);
        }
        $value = strtolower($value);
        if (!is_array($fields))
            $fields = array($fields);
        if (!is_array($required) && !empty($required))
            $required = array($required);
        $this->filter = array();
        // search be iterating over all records in memory
        foreach ($this->contacts as $id => $contact) {
            // check if current contact has required values, otherwise skip it
            if ($required) {
                foreach ($required as $f)
                    if (empty($contact[$f]))
                        continue 2;
            }
            foreach ($fields as $f) {
                foreach ((array)$contact[$f] as $val) {
                    $val = strtolower($val);
                    if (($strict && $val == $value) || (!$strict && strstr($val, $value))) {
                        $this->filter[] = $id;
                        break 2;
                    }
                }
            }
        }
        // list records (now limited by $this->filter)
        return $this->list_records();
    }
    /**
     * Count number of available contacts in database
     *
     * @return rcube_result_set Result set with values for 'count' and 'first'
     */
    public function count()
    {
        $this->_fetch_contacts();
        $this->_fetch_groups();
        $count = $this->gid ? count($this->distlists[$this->gid]['member']) : ($this->filter ? count($this->filter) : count($this->contacts));
        return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
    }
    /**
     * Return the last result set
     *
     * @return rcube_result_set Current result set or NULL if nothing selected yet
     */
    public function get_result()
    {
        return $this->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
     */
    public function get_record($id, $assoc=false)
    {
        $this->_fetch_contacts();
        if ($this->contacts[$id]) {
            $this->result = new rcube_result_set(1);
            $this->result->add($this->contacts[$id]);
            return $assoc ? $this->contacts[$id] : $this->result;
        }
        return false;
    }
    /**
     * Get group assignments of a specific contact record
     *
     * @param mixed Record identifier
     * @return array List of assigned groups as ID=>Name pairs
     */
    public function get_record_groups($id)
    {
        $out = array();
        $this->_fetch_groups();
        foreach ((array)$this->groupmembers[$id] as $gid) {
            if ($group = $this->distlists[$gid])
                $out[$gid] = $group['last-name'];
        }
        return $out;
    }
    /**
     * Create a new contact record
     *
     * @param array Assoziative array with save data
     *  Keys:   Field name with optional section in the form FIELD:SECTION
     *  Values: Field value. Can be either a string or an array of strings for multiple values
     * @param boolean True to check for duplicates first
     * @return mixed The created record ID on success, False on error
     */
    public function insert($save_data, $check=false)
    {
        if (!is_array($save_data))
            return false;
        $insert_id = $existing = false;
        // check for existing records by e-mail comparison
        if ($check) {
            foreach ($this->get_col_values('email', $save_data, true) as $email) {
                if (($res = $this->search('email', $email, true, false)) && $res->count) {
                    $existing = true;
                    break;
                }
            }
        }
        if (!$existing) {
            // generate new Kolab contact item
            $object = $this->_from_rcube_contact($save_data);
            $object['uid'] = $this->contactstorage->generateUID();
            $saved = $this->contactstorage->save($object);
            if (PEAR::isError($saved)) {
                raise_error(array(
                  'code' => 600, 'type' => 'php',
                  'file' => __FILE__, 'line' => __LINE__,
                  'message' => "Error saving contact object to Kolab server:" . $saved->getMessage()),
                true, false);
            }
            else {
                $contact = $this->_to_rcube_contact($object);
                $id = $contact['ID'];
                $this->contacts[$id] = $contact;
                $this->id2uid[$id] = $object['uid'];
                $insert_id = $id;
            }
        }
        return $insert_id;
    }
    /**
     * Update a specific contact record
     *
     * @param mixed Record identifier
     * @param array Assoziative array with save data
     *  Keys:   Field name with optional section in the form FIELD:SECTION
     *  Values: Field value. Can be either a string or an array of strings for multiple values
     * @return boolean True on success, False on error
     */
    public function update($id, $save_data)
    {
        $updated = false;
        $this->_fetch_contacts();
        if ($this->contacts[$id] && ($uid = $this->id2uid[$id])) {
            $old = $this->contactstorage->getObject($uid);
            $object = array_merge($old, $this->_from_rcube_contact($save_data));
            $saved = $this->contactstorage->save($object, $uid);
            if (PEAR::isError($saved)) {
                raise_error(array(
                  'code' => 600, 'type' => 'php',
                  'file' => __FILE__, 'line' => __LINE__,
                  'message' => "Error saving contact object to Kolab server:" . $saved->getMessage()),
                true, false);
            }
            else {
                $this->contacts[$id] = $this->_to_rcube_contact($object);
                $updated = true;
            }
        }
        return $updated;
    }
    /**
     * Mark one or more contact records as deleted
     *
     * @param array  Record identifiers
     */
    public function delete($ids)
    {
        $this->_fetch_contacts();
        $this->_fetch_groups();
        if (!is_array($ids))
            $ids = explode(',', $ids);
        $count = 0;
        foreach ($ids as $id) {
            if ($uid = $this->id2uid[$id]) {
                $deleted = $this->contactstorage->delete($uid);
                if (PEAR::isError($deleted)) {
                    raise_error(array(
                      'code' => 600, 'type' => 'php',
                      'file' => __FILE__, 'line' => __LINE__,
                      'message' => "Error deleting a contact object from the Kolab server:" . $deleted->getMessage()),
                    true, false);
                }
                else {
                    // remove from distribution lists
                    foreach ((array)$this->groupmembers[$id] as $gid)
                        $this->remove_from_group($gid, $id);
                    // clear internal cache
                    unset($this->contacts[$id], $this->id2uid[$id], $this->groupmembers[$id]);
                    $count++;
                }
            }
        }
        return $count;
    }
    /**
     * Remove all records from the database
     */
    public function delete_all()
    {
        if (!PEAR::isError($this->contactstorage->deleteAll())) {
            $this->contacts = array();
            $this->id2uid = array();
            $this->result = null;
        }
    }
    /**
     * Close connection to source
     * Called on script shutdown
     */
    public function close()
    {
        rcube_kolab::shutdown();
    }
    /**
     * Create a contact group with the given name
     *
     * @param string The group name
     * @return mixed False on error, array with record props in success
     */
    function create_group($name)
    {
        $this->_fetch_groups();
        $result = false;
        $list = array(
            'uid' => $this->liststorage->generateUID(),
            'last-name' => $name,
            'member' => array(),
        );
        $saved = $this->liststorage->save($list);
        if (PEAR::isError($saved)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
            true, false);
            return false;
        }
        else {
            $id = md5($list['uid']);
            $this->distlists[$record['ID']] = $list;
            $result = array('id' => $id, 'name' => $name);
        }
        return $result;
    }
    /**
     * Delete the given group and all linked group members
     *
     * @param string Group identifier
     * @return boolean True on success, false if no data was changed
     */
    function delete_group($gid)
    {
        $this->_fetch_groups();
        $result = false;
        if ($list = $this->distlists[$gid])
            $deleted = $this->liststorage->delete($list['uid']);
        if (PEAR::isError($deleted)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Error deleting distribution-list object from the Kolab server:" . $deleted->getMessage()),
            true, false);
        }
        else
            $result = true;
        return $result;
    }
    /**
     * Rename a specific contact group
     *
     * @param string Group identifier
     * @param string New name to set for this group
     * @return boolean New name on success, false if no data was changed
     */
    function rename_group($gid, $newname)
    {
        $this->_fetch_groups();
        $list = $this->distlists[$gid];
        if ($newname != $list['last-name']) {
            $list['last-name'] = $newname;
            $saved = $this->liststorage->save($list, $list['uid']);
        }
        if (PEAR::isError($saved)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
            true, false);
            return false;
        }
        return $newname;
    }
    /**
     * Add the given contact records the a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be added
     * @return int    Number of contacts added
     */
    function add_to_group($gid, $ids)
    {
        if (!is_array($ids))
            $ids = explode(',', $ids);
        $added = 0;
        $exists = array();
        $this->_fetch_groups();
        $this->_fetch_contacts();
        $list = $this->distlists[$gid];
        foreach ((array)$list['member'] as $i => $member)
            $exists[] = $member['ID'];
        // substract existing assignments from list
        $ids = array_diff($ids, $exists);
        foreach ($ids as $contact_id) {
            if ($uid = $this->id2uid[$contact_id]) {
                $contact = $this->contacts[$contact_id];
                foreach ($this->get_col_values('email', $contact, true) as $email) {
                    $list['member'][] = array(
                        'uid' => $uid,
                        'display-name' => $contact['name'],
                        'smtp-address' => $email,
                    );
                }
                $this->groupmembers[$contact_id][] = $gid;
                $added++;
            }
        }
        if ($added)
            $saved = $this->liststorage->save($list, $list['uid']);
        if (PEAR::isError($saved)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Error saving distribution-list to Kolab server:" . $saved->getMessage()),
            true, false);
            $added = false;
        }
        else {
            $this->distlists[$gid] = $list;
        }
        return $added;
    }
    /**
     * Remove the given contact records from a certain group
     *
     * @param string  Group identifier
     * @param array   List of contact identifiers to be removed
     * @return int    Number of deleted group members
     */
    function remove_from_group($gid, $ids)
    {
        if (!is_array($ids))
            $ids = explode(',', $ids);
        $this->_fetch_groups();
        if (!($list = $this->distlists[$gid]))
            return false;
        $new_member = array();
        foreach ((array)$list['member'] as $member) {
            if (!in_array($member['ID'], $ids))
                $new_member[] = $member;
        }
        // write distribution list back to server
        $list['member'] = $new_member;
        $saved = $this->liststorage->save($list, $list['uid']);
        if (PEAR::isError($saved)) {
            raise_error(array(
              'code' => 600, 'type' => 'php',
              'file' => __FILE__, 'line' => __LINE__,
              'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
            true, false);
        }
        else {
            // remove group assigments in local cache
            foreach ($ids as $id) {
                $j = array_search($gid, $this->groupmembers[$id]);
                unset($this->groupmembers[$id][$j]);
            }
            $this->distlists[$gid] = $list;
            return true;
        }
        return false;
    }
    /**
     * Simply fetch all records and store them in private member vars
     */
    private function _fetch_contacts()
    {
        if (!isset($this->contacts)) {
            // read contacts
            $this->contacts = $this->id2uid = array();
            foreach ((array)$this->contactstorage->getObjects() as $record) {
                $contact = $this->_to_rcube_contact($record);
                $id = $contact['ID'];
                $this->contacts[$id] = $contact;
                $this->id2uid[$id] = $record['uid'];
            }
            // TODO: sort data arrays according to desired list sorting
        }
    }
    /**
     * Read distribution-lists AKA groups from server
     */
    private function _fetch_groups()
    {
        if (!isset($this->distlists)) {
            $this->distlists = $this->groupmembers = array();
            foreach ((array)$this->liststorage->getObjects() as $record) {
                // FIXME: folders without any distribution-list objects return contacts instead ?!
                if ($record['__type'] != 'Group')
                    continue;
                $record['ID'] = md5($record['uid']);
                foreach ((array)$record['member'] as $i => $member) {
                    $mid = md5($member['uid']);
                    $record['member'][$i]['ID'] = $mid;
                    $this->groupmembers[$mid][] = $record['ID'];
                }
                $this->distlists[$record['ID']] = $record;
            }
        }
    }
    /**
     * Map fields from internal Kolab_Format to Roundcube contact format
     */
    private function _to_rcube_contact($record)
    {
        $out = array(
          'ID' => md5($record['uid']),
          'email' => array(),
          'phone' => array(),
        );
        foreach ($this->fieldmap as $kolab => $rcube) {
          if (strlen($record[$kolab]))
            $out[$rcube] = $record[$kolab];
        }
        if (isset($record['gender']))
            $out['gender'] = $this->gender_map[$record['gender']];
        foreach ((array)$record['email'] as $i => $email)
            $out['email'][] = $email['smtp-address'];
        if (!$record['email'] && $record['emails'])
            $out['email'] = preg_split('/,\s*/', $record['emails']);
        foreach ((array)$record['phone'] as $i => $phone)
            $out['phone:'.$phone['type']][] = $phone['number'];
        if (is_array($record['address'])) {
            foreach ($record['address'] as $i => $adr) {
                $key = 'address:' . $adr['type'];
                $out[$key][] = array(
                    'street' => $adr['street'],
                    'locality' => $adr['locality'],
                    'zipcode' => $adr['postal-code'],
                    'region' => $adr['region'],
                    'country' => $adr['country'],
                );
            }
        }
        // remove empty fields
        return array_filter($out);
    }
    private function _from_rcube_contact($contact)
    {
        $object = array();
        foreach (array_flip($this->fieldmap) as $rcube => $kolab) {
            if (isset($contact[$rcube]))
                $object[$kolab] = is_array($contact[$rcube]) ? $contact[$rcube][0] : $contact[$rcube];
            else if ($rcube .= ':home' && isset($contact[$rcube]))
                $object[$kolab] = is_array($contact[$rcube]) ? $contact[$rcube][0] : $contact[$rcube];
        }
        // format dates
        if ($object['birthday'] && ($date = @strtotime($object['birthday'])))
            $object['birthday'] = date('Y-m-d', $date);
        if ($object['anniversary'] && ($date = @strtotime($object['anniversary'])))
            $object['anniversary'] = date('Y-m-d', $date);
        $gendermap = array_flip($this->gender_map);
        if (isset($contact['gender']))
            $object['gender'] = $gendermap[$contact['gender']];
        $emails = $this->get_col_values('email', $contact, true);
        $object['emails'] = join(', ', $emails);
        foreach ($this->get_col_values('phone', $contact) as $type => $values) {
            if ($this->phonetypemap[$type])
                $type = $this->phonetypemap[$type];
            foreach ((array)$values as $phone)
                $object['phone'][] = array('number' => $phone, 'type' => $type);
        }
        foreach ($this->get_col_values('address', $contact) as $type => $values) {
            if ($this->addresstypemap[$type])
                $type = $this->addresstypemap[$type];
            $basekey = 'addr-' . $type . '-';
            foreach ((array)$values as $adr) {
                // switch type if slot is already taken
                if (isset($object[$basekey . 'type'])) {
                    $type = $type == 'home' ? 'business' : 'home';
                    $basekey = 'addr-' . $type . '-';
                }
                if (!isset($object[$basekey . 'type'])) {
                    $object[$basekey . 'type'] = $type;
                    $object[$basekey . 'street'] = $adr['street'];
                    $object[$basekey . 'locality'] = $adr['locality'];
                    $object[$basekey . 'postal-code'] = $adr['zipcode'];
                    $object[$basekey . 'region'] = $adr['region'];
                    $object[$basekey . 'country'] = $adr['country'];
                }
                else {
                    $object['address'][] = array(
                        'type' => $type,
                        'street' => $adr['street'],
                        'locality' => $adr['locality'],
                        'postal-code' => $adr['zipcode'],
                        'region' => $adr['region'],
                        'country' => $adr['country'],
                    );
                }
            }
        }
        return $object;
    }
}
plugins/kolab_core/README.txt
New file
@@ -0,0 +1,32 @@
Kolab Integration Plugin README
-------------------------------
This plugin relies on classes from the Horde project. In order to have all
the required files available you need to install the following packages from
Horde:
    Horde_Framework
    Kolab_Format
    Kolab_Storage
    Horde_NLS
    Horde_DOM
This is best done using PEAR. Make sure that the local PEAR directory is in
the PHP isntall path and execute the following commands to install the
required packages:
pear channel-discover pear.horde.org
pear install horde/Horde_Framework
pear install horde/Horde_DOM
pear install horde/Horde_NLS
pear install horde/Horde_Share
pear install horde/Log
pear install horde/Kolab_Format
pear install horde/Kolab_Storage
Configuration
-------------
Rename the config.inc.php.dist to config.inc.php within this plugin directory
and add the corresponding values for your local Kolab server.
plugins/kolab_core/config.inc.php.dist
New file
@@ -0,0 +1,8 @@
<?php
// Sample configuration for Kolab LDAP binding used by Kolab_Storage
$rcmail_config['kolab']['ldap']['basedn'] = 'dc=kolabserver,dc=local';
$rcmail_config['kolab']['ldap']['phpdn'] = 'cn=nobody,cn=internal,dc=kolabserver,dc=local';
$rcmail_config['kolab']['ldap']['phppw'] = '<ldap-pwd-goes-here>';
?>
plugins/kolab_core/kolab_core.php
New file
@@ -0,0 +1,30 @@
<?php
/**
 * Kolab core library
 *
 * Plugin to setup a basic environment for interaction with a Kolab server.
 * Other Kolab-related plugins will depend on it and can use the static API rcube_core
 *
 * This is work-in-progress for the Roundcube+Kolab integration.
 *
 * @author Thomas Bruederli <roundcube@gmail.com>
 *
 */
class kolab_core extends rcube_plugin
{
    /**
     * Required startup method of a Roundcube plugin
     */
    public function init()
    {
        // load local config
        $this->load_config();
        // extend include path to load bundled Horde classes
        $include_path = $this->home . PATH_SEPARATOR . ini_get('include_path');
        set_include_path($include_path);
    }
}
plugins/kolab_core/rcube_kolab.php
New file
@@ -0,0 +1,109 @@
<?php
require_once 'Horde/Kolab/Storage/List.php';
require_once 'Horde/Kolab/Format.php';
require_once 'Horde/Auth.php';
require_once 'Horde/Auth/kolab.php';
require_once 'Horde/Perms.php';
/**
 * Glue class to handle access to the Kolab data using the Kolab_* classes
 * from the Horde project.
 *
 * @author Thomas Bruederli
 */
class rcube_kolab
{
    private static $horde_auth;
    /**
     * Setup the environment needed by the Kolab_* classes to access Kolab data
     */
    public static function setup()
    {
        global $conf;
        // setup already done
        if (self::$horde_auth)
            return;
        $rcmail = rcmail::get_instance();
        // load ldap credentials from local config
        $conf['kolab'] = $rcmail->config->get('kolab');
        $conf['kolab']['ldap']['server'] = 'ldap://' . $_SESSION['imap_host'] . ':389';
        $conf['kolab']['imap']['server'] = $_SESSION['imap_host'];
        $conf['kolab']['imap']['port'] = $_SESSION['imap_port'];
        // pass the current IMAP authentication credentials to the Horde auth system
        self::$horde_auth = Auth::singleton('kolab');
        if (self::$horde_auth->authenticate($_SESSION['username'], array('password' => ($pwd = $rcmail->decrypt($_SESSION['password']))), false)) {
            $_SESSION['__auth'] = array(
                'authenticated' => true,
                'userId' => $_SESSION['username'],
                'timestamp' => time(),
                'remote_addr' => $_SERVER['REMOTE_ADDR'],
            );
            Auth::setCredential('password', $pwd);
        }
    }
    /**
     * Get instance of a Kolab (XML) format object
     *
     * @param string Data type (contact,event,task,note)
     * @return object Horde_Kolab_Format_XML The format object
     */
    public static function get_format($type)
    {
      self::setup();
      return Horde_Kolab_Format::factory('XML', $type);
    }
    /**
     * Get a list of storage folders for the given data type
     *
     * @param string Data type to list folders for (contact,event,task,note)
     * @return array List of Kolab_Folder objects
     */
    public static function get_folders($type)
    {
        self::setup();
        $kolab = Kolab_List::singleton();
        return $kolab->getByType($type);
    }
    /**
     * Get storage object for read/write access to the Kolab backend
     *
     * @param string IMAP folder to access
     * @param string Object type to deal with (leave empty for auto-detection using annotations)
     * @return object Kolab_Data The data storage object
     */
    public static function get_storage($folder, $data_type = null)
    {
        self::setup();
        $kolab = Kolab_List::singleton();
        return $kolab->getFolder($folder)->getData($data_type);
    }
    /**
     * Cleanup session data when done
     */
    public static function shutdown()
    {
        if (isset($_SESSION['__auth'])) {
            // unset auth data from session. no need to store it persistantly
            unset($_SESSION['__auth']);
            // FIXME: remove strange numeric entries
            foreach ($_SESSION as $key => $val) {
                if (!$val && is_numeric($key))
                    unset($_SESSION[$key]);
            }
        }
    }
}
plugins/managesieve/Changelog
New file
@@ -0,0 +1,160 @@
- Added support for SASL proxy authentication (#1486691)
- Fixed parsing of scripts with \r\n line separator
- Apply forgotten changes for form errors handling
* version 2.10 [2010-10-10]
-----------------------------------------------------------
- Fixed import from Avelsieve
- Use localized size units (#1486976)
- Added support for relational operators and i;ascii-numeric comparator
- Added popups with form errors
* version 2.9 [2010-08-02]
-----------------------------------------------------------
- Fixed vacation parameters parsing (#1486883)
* version 2.8 [2010-07-08]
-----------------------------------------------------------
- Added managesieve_auth_type option (#1486731)
* version 2.7 [2010-07-06]
-----------------------------------------------------------
- Update Net_Sieve to version 1.3.0 (fixes LOGIN athentication)
- Added support for copying and copy sending of messages (COPY extension)
* version 2.6 [2010-06-03]
-----------------------------------------------------------
- Support %n and %d variables in managesieve_host option
* version 2.5 [2010-05-04]
-----------------------------------------------------------
- Fix filters set label after activation
- Fix filters set activation, add possibility to deactivate sets (#1486699)
- Fix download button state when sets list is empty
- Fix errors when sets list is empty
* version 2.4 [2010-04-01]
-----------------------------------------------------------
- Fixed bug in DIGEST-MD5 authentication (http://pear.php.net/bugs/bug.php?id=17285)
- Fixed disabling rules with many tests
- Small css unification with core
- Scripts import/export
* version 2.3 [2010-03-18]
-----------------------------------------------------------
- Added import from Horde-INGO
- Support for more than one match using if+stop instead of if+elsif structures (#1486078)
- Support for selectively disabling rules within a single sieve script (#1485882)
- Added vertical splitter
* version 2.2 [2010-02-06]
-----------------------------------------------------------
- Fix handling of "<>" characters in filter names (#1486477)
* version 2.1 [2010-01-12]
-----------------------------------------------------------
- Fix "require" structure generation when many modules are used
- Fix problem with '<' and '>' characters in header tests
* version 2.0 [2009-11-02]
-----------------------------------------------------------
- Added 'managesieve_debug' option
- Added multi-script support
- Small css improvements + sprite image buttons
- PEAR::NetSieve 1.2.0b1
* version 1.7 [2009-09-20]
-----------------------------------------------------------
- Support multiple managesieve hosts using %h variable
  in managesieve_host option
- Fix first rule deleting (#1486140)
* version 1.6 [2009-09-08]
-----------------------------------------------------------
- Fix warning when importing squirrelmail rules
- Fix handling of "true" as "anyof (true)" test
* version 1.5 [2009-09-04]
-----------------------------------------------------------
- Added es_ES, ua_UA localizations
- Added 'managesieve_mbox_encoding' option
* 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.
plugins/managesieve/config.inc.php.dist
New file
@@ -0,0 +1,53 @@
<?php
// managesieve server port
$rcmail_config['managesieve_port'] = 2000;
// managesieve server address, default is localhost.
// Replacement variables supported in host name:
// %h - user's IMAP hostname
// %n - http hostname ($_SERVER['SERVER_NAME'])
// %d - domain (http hostname without the first part)
// For example %n = mail.domain.tld, %d = domain.tld
$rcmail_config['managesieve_host'] = 'localhost';
// authentication method. Can be CRAM-MD5, DIGEST-MD5, PLAIN, LOGIN, EXTERNAL
// or none. Optional, defaults to best method supported by server.
$rcmail_config['managesieve_auth_type'] = null;
// Optional managesieve authentication identifier to be used as authorization proxy.
// Authenticate as a different user but act on behalf of the logged in user.
// Works with PLAIN and DIGEST-MD5 auth.
$rcmail_config['managesieve_auth_cid'] = null;
// Optional managesieve authentication password to be used for imap_auth_cid
$rcmail_config['managesieve_auth_pw'] = null;
// use or not TLS for managesieve server connection
// it's because I've problems with TLS and dovecot's managesieve plugin
// and it's not needed on localhost
$rcmail_config['managesieve_usetls'] = false;
// default contents of filters script (eg. default spam filter)
$rcmail_config['managesieve_default'] = '/etc/dovecot/sieve/global';
// Sieve RFC says that we should use UTF-8 endcoding for mailbox names,
// but some implementations does not covert UTF-8 to modified UTF-7.
// Defaults to UTF7-IMAP
$rcmail_config['managesieve_mbox_encoding'] = 'UTF-8';
// I need this because my dovecot (with listescape plugin) uses
// ':' delimiter, but creates folders with dot delimiter
$rcmail_config['managesieve_replace_delimiter'] = '';
// disabled sieve extensions (body, copy, date, editheader, encoded-character,
// envelope, environment, ereject, fileinto, ihave, imap4flags, index,
// mailbox, mboxmetadata, regex, reject, relational, servermetadata,
// spamtest, spamtestplus, subaddress, vacation, variables, virustest, etc.
// Note: not all extensions are implemented
$rcmail_config['managesieve_disabled_extensions'] = array();
// Enables debugging of conversation with sieve server. Logs it into <log_dir>/sieve
$rcmail_config['managesieve_debug'] = false;
?>
plugins/managesieve/lib/Net/Sieve.php
New file
@@ -0,0 +1,1211 @@
<?php
/**
 * This file contains the Net_Sieve class.
 *
 * PHP version 4
 *
 * +-----------------------------------------------------------------------+
 * | All rights reserved.                                                  |
 * |                                                                       |
 * | Redistribution and use in source and binary forms, with or without    |
 * | modification, are permitted provided that the following conditions    |
 * | are met:                                                              |
 * |                                                                       |
 * | o Redistributions of source code must retain the above copyright      |
 * |   notice, this list of conditions and the following disclaimer.       |
 * | o Redistributions in binary form must reproduce the above copyright   |
 * |   notice, this list of conditions and the following disclaimer in the |
 * |   documentation and/or other materials provided with the distribution.|
 * |                                                                       |
 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
 * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
 * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
 * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
 * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
 * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
 * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
 * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
 * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
 * +-----------------------------------------------------------------------+
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD
 * @version   SVN: $Id: Sieve.php 300898 2010-07-01 09:49:02Z yunosh $
 * @link      http://pear.php.net/package/Net_Sieve
 */
require_once 'PEAR.php';
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.
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD
 * @version   Release: 1.3.0
 * @link      http://pear.php.net/package/Net_Sieve
 * @link      http://www.ietf.org/rfc/rfc3028.txt RFC 3028 (Sieve: A Mail
 *            Filtering Language)
 * @link      http://tools.ietf.org/html/draft-ietf-sieve-managesieve A
 *            Protocol for Remotely Managing Sieve Scripts
 */
class Net_Sieve
{
    /**
     * The authentication methods this class supports.
     *
     * Can be overwritten if having problems with certain methods.
     *
     * @var array
     */
    var $supportedAuthMethods = array('DIGEST-MD5', 'CRAM-MD5', 'EXTERNAL',
                                      'PLAIN' , 'LOGIN');
    /**
     * SASL authentication methods that require Auth_SASL.
     *
     * @var array
     */
    var $supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5');
    /**
     * The socket handle.
     *
     * @var resource
     */
    var $_sock;
    /**
     * Parameters and connection information.
     *
     * @var array
     */
    var $_data;
    /**
     * Current state of the connection.
     *
     * One of the NET_SIEVE_STATE_* constants.
     *
     * @var integer
     */
    var $_state;
    /**
     * Constructor error.
     *
     * @var PEAR_Error
     */
    var $_error;
    /**
     * Whether to enable debugging.
     *
     * @var boolean
     */
    var $_debug = false;
    /**
     * Debug output handler.
     *
     * This has to be a valid callback.
     *
     * @var string|array
     */
    var $_debug_handler = null;
    /**
     * Whether to pick up 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;
    /**
     * Maximum number of 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 (see
     *                            $supportedAuthMethods).
     * @param string  $euser      Effective user. If authenticating as an
     *                            administrator, login as this user.
     * @param boolean $debug      Whether to enable debugging (@see setDebug()).
     * @param string  $bypassAuth Skip the authentication phase. Useful if the
     *                            socket is already open.
     * @param boolean $useTLS     Use TLS if available.
     * @param array   $options    Additional options for
     *                            stream_context_create().
     * @param mixed   $handler    A callback handler for the debug output.
     */
    function Net_Sieve($user = null, $pass  = null, $host = 'localhost',
                       $port = 2000, $logintype = '', $euser = '',
                       $debug = false, $bypassAuth = false, $useTLS = true,
                       $options = null, $handler = 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->_bypassAuth        = $bypassAuth;
        $this->_useTLS            = $useTLS;
        $this->_options           = $options;
        $this->setDebug($debug, $handler);
        /* Try to 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) {
            $this->_debug('Auth_SASL not present');
            foreach ($this->supportedSASLAuthMethods as $SASLMethod) {
                $pos = array_search($SASLMethod, $this->supportedAuthMethods);
                $this->_debug('Disabling method ' . $SASLMethod);
                unset($this->supportedAuthMethods[$pos]);
            }
        }
        if (strlen($user) && strlen($pass)) {
            $this->_error = $this->_handleConnectAndLogin();
        }
    }
    /**
     * Returns any error that may have been generated in the constructor.
     *
     * @return boolean|PEAR_Error  False if no error, PEAR_Error otherwise.
     */
    function getError()
    {
        return PEAR::isError($this->_error) ? $this->_error : false;
    }
    /**
     * Sets the debug state and handler function.
     *
     * @param boolean $debug   Whether to enable debugging.
     * @param string  $handler A custom debug handler. Must be a valid callback.
     *
     * @return void
     */
    function setDebug($debug = true, $handler = null)
    {
        $this->_debug = $debug;
        $this->_debug_handler = $handler;
    }
    /**
     * Connects to the server and logs in.
     *
     * @return boolean  True on success, 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;
    }
    /**
     * Handles connecting to the server and checks the response validity.
     *
     * @param string  $host    Hostname of server.
     * @param string  $port    Port of server.
     * @param array   $options List of options to pass to
     *                         stream_context_create().
     * @param boolean $useTLS  Use TLS if available.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function connect($host, $port, $options = null, $useTLS = true)
    {
        if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
            return PEAR::raiseError('Not currently in DISCONNECTED state', 1);
        }
        if (PEAR::isError($res = $this->_sock->connect($host, $port, false, 5, $options))) {
            return $res;
        }
        if ($this->_bypassAuth) {
            $this->_state = NET_SIEVE_STATE_TRANSACTION;
        } else {
            $this->_state = NET_SIEVE_STATE_AUTHORISATION;
            if (PEAR::isError($res = $this->_doCmd())) {
                return $res;
            }
        }
        // Explicitly ask for the capabilities in case the connection is
        // picked up from an existing connection.
        if (PEAR::isError($res = $this->_cmdCapability())) {
            return PEAR::raiseError(
                'Failed to connect, server said: ' . $res->getMessage(), 2
            );
        }
        // Check if we can enable TLS via STARTTLS.
        if ($useTLS && !empty($this->_capability['starttls'])
            && function_exists('stream_socket_enable_crypto')
        ) {
            if (PEAR::isError($res = $this->_startTLS())) {
                return $res;
            }
        }
        return true;
    }
    /**
     * Disconnect from the Sieve server.
     *
     * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
     *                               disconnecting.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function disconnect($sendLogoutCMD = true)
    {
        return $this->_cmdLogout($sendLogoutCMD);
    }
    /**
     * 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 boolean  True on success, PEAR_Error otherwise.
     */
    function login($user, $pass, $logintype = null, $euser = '', $bypassAuth = false)
    {
        if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        if (!$bypassAuth ) {
            if (PEAR::isError($res = $this->_cmdAuthenticate($user, $pass, $logintype, $euser))) {
                return $res;
            }
        }
        $this->_state = NET_SIEVE_STATE_TRANSACTION;
        return true;
    }
    /**
     * Returns an indexed array of scripts currently on the server.
     *
     * @return array  Indexed array of scriptnames.
     */
    function listScripts()
    {
        if (is_array($scripts = $this->_cmdListScripts())) {
            $this->_active = $scripts[1];
            return $scripts[0];
        } else {
            return $scripts;
        }
    }
    /**
     * Returns the active script.
     *
     * @return string  The active scriptname.
     */
    function getActive()
    {
        if (!empty($this->_active)) {
            return $this->_active;
        }
        if (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 boolean  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 string  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 content.
     * @param boolean $makeactive Whether to make this the active script.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function installScript($scriptname, $script, $makeactive = false)
    {
        if (PEAR::isError($res = $this->_cmdPutScript($scriptname, $script))) {
            return $res;
        }
        if ($makeactive) {
            return $this->_cmdSetActive($scriptname);
        }
        return true;
    }
    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function removeScript($scriptname)
    {
        return $this->_cmdDeleteScript($scriptname);
    }
    /**
     * 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 boolean|PEAR_Error  True if there is space, PEAR_Error otherwise.
     *
     * @todo Rename to hasSpace()
     */
    function haveSpace($scriptname, $size)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in TRANSACTION state', 1);
        }
        if (PEAR::isError($res = $this->_doCmd(sprintf('HAVESPACE "%s" %d', $scriptname, $size)))) {
            return $res;
        }
        return true;
    }
    /**
     * Returns the list of extensions the server supports.
     *
     * @return array  List of extensions or PEAR_Error on failure.
     */
    function getExtensions()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 7);
        }
        return $this->_capability['extensions'];
    }
    /**
     * Returns whether the server supports an extension.
     *
     * @param string $extension The extension to check.
     *
     * @return boolean  Whether the extension is supported or PEAR_Error on
     *                  failure.
     */
    function hasExtension($extension)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 7);
        }
        $extension = trim($this->_toUpper($extension));
        if (is_array($this->_capability['extensions'])) {
            foreach ($this->_capability['extensions'] as $ext) {
                if ($ext == $extension) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Returns the list of authentication methods the server supports.
     *
     * @return array  List of authentication methods or PEAR_Error on failure.
     */
    function getAuthMechs()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 7);
        }
        return $this->_capability['sasl'];
    }
    /**
     * Returns whether the server supports an authentication method.
     *
     * @param string $method The method to check.
     *
     * @return boolean  Whether the method is supported or PEAR_Error on
     *                  failure.
     */
    function hasAuthMech($method)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 7);
        }
        $method = trim($this->_toUpper($method));
        if (is_array($this->_capability['sasl'])) {
            foreach ($this->_capability['sasl'] as $sasl) {
                if ($sasl == $method) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 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 empty, the class chooses
     *                           the best (strongest) available method.
     * @param string $euser      The effective uid to authenticate as.
     *
     * @return void
     */
    function _cmdAuthenticate($uid, $pwd, $userMethod = null, $euser = '')
    {
        if (PEAR::isError($method = $this->_getBestAuthMethod($userMethod))) {
            return $method;
        }
        switch ($method) {
        case 'DIGEST-MD5':
            return $this->_authDigestMD5($uid, $pwd, $euser);
        case 'CRAM-MD5':
            $result = $this->_authCRAMMD5($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 = PEAR::raiseError(
                $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 void
     */
    function _authPLAIN($user, $pass, $euser)
    {
        return $this->_sendCmd(
            sprintf(
                'AUTHENTICATE "PLAIN" "%s"',
                base64_encode($euser . chr(0) . $user . chr(0) . $pass)
            )
        );
    }
    /**
     * Authenticates the user using the LOGIN 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 void
     */
    function _authLOGIN($user, $pass, $euser)
    {
        if (PEAR::isError($result = $this->_sendCmd('AUTHENTICATE "LOGIN"'))) {
            return $result;
        }
        if (PEAR::isError($result = $this->_doCmd('"' . base64_encode($user) . '"', true))) {
            return $result;
        }
        return $this->_doCmd('"' . base64_encode($pass) . '"', true);
    }
    /**
     * Authenticates the user using the CRAM-MD5 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 void
     */
    function _authCRAMMD5($user, $pass, $euser)
    {
        if (PEAR::isError($challenge = $this->_doCmd('AUTHENTICATE "CRAM-MD5"', true))) {
            return $challenge;
        }
        $challenge = base64_decode(trim($challenge));
        $cram = Auth_SASL::factory('crammd5');
        if (PEAR::isError($response = $cram->getResponse($user, $pass, $challenge))) {
            return $response;
        }
        return $this->_sendStringResponse(base64_encode($response));
    }
    /**
     * Authenticates the user using the DIGEST-MD5 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 void
     */
    function _authDigestMD5($user, $pass, $euser)
    {
        if (PEAR::isError($challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"', true))) {
            return $challenge;
        }
        $challenge = base64_decode(trim($challenge));
        $digest = Auth_SASL::factory('digestmd5');
        // @todo Really 'localhost'?
        if (PEAR::isError($response = $digest->getResponse($user, $pass, $challenge, 'localhost', 'sieve', $euser))) {
            return $response;
        }
        if (PEAR::isError($result = $this->_sendStringResponse(base64_encode($response)))) {
            return $result;
        }
        if (PEAR::isError($result = $this->_doCmd('', true))) {
            return $result;
        }
        if ($this->_toUpper(substr($result, 0, 2)) == 'OK') {
            return;
        }
        /* 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($result = $this->_sendStringResponse(''))) {
            return $result;
        }
        return $this->_doCmd();
    }
    /**
     * 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 void
     *
     * @since  1.1.7
     */
    function _authEXTERNAL($user, $pass, $euser)
    {
        $cmd = sprintf(
            'AUTHENTICATE "EXTERNAL" "%s"',
            base64_encode(strlen($euser) ? $euser : $user)
        );
        return $this->_sendCmd($cmd);
    }
    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script to delete.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdDeleteScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        if (PEAR::isError($res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname)))) {
            return $res;
        }
        return true;
    }
    /**
     * Retrieves the contents of the named script.
     *
     * @param string $scriptname Name of the script to retrieve.
     *
     * @return string  The script if successful, PEAR_Error otherwise.
     */
    function _cmdGetScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        if (PEAR::isError($res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname)))) {
            return $res;
        }
        return preg_replace('/{[0-9]+}\r\n/', '', $res);
    }
    /**
     * Sets the active script, i.e. the one that gets run on new mail by the
     * server.
     *
     * @param string $scriptname The name of the script to mark as active.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
    */
    function _cmdSetActive($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        if (PEAR::isError($res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname)))) {
            return $res;
        }
        $this->_activeScript = $scriptname;
        return true;
    }
    /**
     * Returns the list of scripts on the server.
     *
     * @return array  An array with the list of scripts in the first element
     *                and the active script in the second element on success,
     *                PEAR_Error otherwise.
     */
    function _cmdListScripts()
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        if (PEAR::isError($res = $this->_doCmd('LISTSCRIPTS'))) {
            return $res;
        }
        $scripts = array();
        $activescript = null;
        $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);
    }
    /**
     * Adds a script to the server.
     *
     * @param string $scriptname Name of the new script.
     * @param string $scriptdata The new script.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdPutScript($scriptname, $scriptdata)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return PEAR::raiseError('Not currently in AUTHORISATION state', 1);
        }
        $stringLength = $this->_getLineLength($scriptdata);
        if (PEAR::isError($res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, $stringLength, $scriptdata)))) {
            return $res;
        }
        return true;
    }
    /**
     * Logs out of the server and terminates the connection.
     *
     * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
     *                               disconnecting.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdLogout($sendLogoutCMD = true)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 1);
        }
        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
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdCapability()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return PEAR::raiseError('Not currently connected', 1);
        }
        if (PEAR::isError($res = $this->_doCmd('CAPABILITY'))) {
            return $res;
        }
        $this->_parseCapability($res);
        return true;
    }
    /**
     * Parses the response from the CAPABILITY command and stores the result
     * in $_capability.
     *
     * @param string $data The response from the capability command.
     *
     * @return void
     */
    function _parseCapability($data)
    {
        // Clear the cached capabilities.
        $this->_capability = array('sasl' => array(),
                                   'extensions' => array());
        $data = preg_split('/\r?\n/', $this->_toUpper($data), -1, PREG_SPLIT_NO_EMPTY);
        for ($i = 0; $i < count($data); $i++) {
            if (!preg_match('/^"([A-Z]+)"( "(.*)")?$/', $data[$i], $matches)) {
                continue;
            }
            switch ($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
     *
     * @param string $cmd The command to send.
     *
     * @return void
     */
    function _sendCmd($cmd)
    {
        $status = $this->_sock->getStatus();
        if (PEAR::isError($status) || $status['eof']) {
            return PEAR::raiseError('Failed to write to socket: connection lost');
        }
        if (PEAR::isError($error = $this->_sock->write($cmd . "\r\n"))) {
            return PEAR::raiseError(
                'Failed to write to socket: ' . $error->getMessage()
            );
        }
        $this->_debug("C: $cmd");
    }
    /**
     * Sends a string response to the server.
     *
     * @param string $str The string to send.
     *
     * @return void
     */
    function _sendStringResponse($str)
    {
        return $this->_sendCmd('{' . $this->_getLineLength($str) . "+}\r\n" . $str);
    }
    /**
     * Receives a single line from the server.
     *
     * @return string  The server response line.
     */
    function _recvLn()
    {
        if (PEAR::isError($lastline = $this->_sock->gets(8192))) {
            return PEAR::raiseError(
                'Failed to read from socket: ' . $lastline->getMessage()
            );
        }
        $lastline = rtrim($lastline);
        $this->_debug("S: $lastline");
        if ($lastline === '') {
            return PEAR::raiseError('Failed to read from socket');
        }
        return $lastline;
    }
    /**
     * Send a command and retrieves a response from the server.
     *
     * @param string $cmd   The command to send.
     * @param boolean $auth Whether this is an authentication command.
     *
     * @return string|PEAR_Error  Reponse string if an OK response, PEAR_Error
     *                            if a NO response.
     */
    function _doCmd($cmd = '', $auth = false)
    {
        $referralCount = 0;
        while ($referralCount < $this->_maxReferralCount) {
            if (strlen($cmd)) {
                if (PEAR::isError($error = $this->_sendCmd($cmd))) {
                    return $error;
                }
            }
            $response = '';
            while (true) {
                if (PEAR::isError($line = $this->_recvLn())) {
                    return $line;
                }
                $uc_line = $this->_toUpper($line);
                if ('OK' == substr($uc_line, 0, 2)) {
                    $response .= $line;
                    return rtrim($response);
                }
                if ('NO' == substr($uc_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)
                        );
                        $this->_debug("S: $line");
                    }
                    return PEAR::raiseError(trim($response . substr($line, 2)), 3);
                }
                if ('BYE' == substr($uc_line, 0, 3)) {
                    if (PEAR::isError($error = $this->disconnect(false))) {
                        return PEAR::raiseError(
                            'Cannot handle BYE, the error was: '
                            . $error->getMessage(),
                            4
                        );
                    }
                    // Check for referral, then follow it.  Otherwise, carp an
                    // error.
                    if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
                        // 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())) {
                            return PEAR::raiseError(
                                'Cannot follow referral to '
                                . $this->_data['host'] . ', the error was: '
                                . $error->getMessage(),
                                5
                            );
                        }
                        break;
                    }
                    return PEAR::raiseError(trim($response . $line), 6);
                }
                if (preg_match('/^{([0-9]+)\+?}/i', $line, $matches)) {
                    // Matches String Responses.
                    $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);
                    }
                    $this->_debug("S: $line");
                    if (!$auth) {
                        // Receive the pending OK only if we aren't
                        // authenticating since string responses during
                        // authentication don't need an OK.
                        $this->_recvLn();
                    }
                    return $line;
                }
                if ($auth) {
                    // String responses during authentication don't need an
                    // OK.
                    $response .= $line;
                    return rtrim($response);
                }
                $response .= $line . "\r\n";
                $referralCount++;
            }
        }
        return PEAR::raiseError('Max referral count (' . $referralCount . ') reached. Cyrus murder loop error?', 7);
    }
    /**
     * Returns the name of the best authentication method that the server
     * has advertised.
     *
     * @param string $userMethod Only consider this method as available.
     *
     * @return string  The name of the best supported authentication method or
     *                 a PEAR_Error object on failure.
     */
    function _getBestAuthMethod($userMethod = null)
    {
        if (!isset($this->_capability['sasl'])) {
            return PEAR::raiseError('This server doesn\'t support any authentication methods. SASL problem?');
        }
        if (!$this->_capability['sasl']) {
            return PEAR::raiseError('This server doesn\'t support any authentication methods.');
        }
        if ($userMethod) {
            if (in_array($userMethod, $this->_capability['sasl'])) {
                return $userMethod;
            }
            return PEAR::raiseError(
                sprintf('No supported authentication method found. The server supports these methods: %s, but we want to use: %s',
                        implode(', ', $this->_capability['sasl']),
                        $userMethod));
        }
        foreach ($this->supportedAuthMethods as $method) {
            if (in_array($method, $this->_capability['sasl'])) {
                return $method;
            }
        }
        return PEAR::raiseError(
            sprintf('No supported authentication method found. The server supports these methods: %s, but we only support: %s',
                    implode(', ', $this->_capability['sasl']),
                    implode(', ', $this->supportedAuthMethods)));
    }
    /**
     * Starts a TLS connection.
     *
     * @return boolean  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)) {
            return PEAR::raiseError('Failed to establish TLS connection', 2);
        }
        $this->_debug('STARTTLS negotiation successful');
        // 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())) {
            return PEAR::raiseError(
                'Failed to connect, server said: ' . $res->getMessage(), 2
            );
        }
        return true;
    }
    /**
     * Returns the length of a string.
     *
     * @param string $string A string.
     *
     * @return integer  The length of the string.
     */
    function _getLineLength($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, 'latin1');
        } else {
            return strlen($string);
        }
    }
    /**
     * Locale independant strtoupper() implementation.
     *
     * @param string $string The string to convert to lowercase.
     *
     * @return string  The lowercased string, based on ASCII encoding.
     */
    function _toUpper($string)
    {
        $language = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $string = strtoupper($string);
        setlocale(LC_CTYPE, $language);
        return $string;
    }
    /**
     * Write debug text to the current debug output handler.
     *
     * @param string $message Debug message text.
     *
     * @return void
     */
    function _debug($message)
    {
        if ($this->_debug) {
            if ($this->_debug_handler) {
                call_user_func_array($this->_debug_handler, array(&$this, $message));
            } else {
                echo "$message\n";
            }
        }
    }
}
plugins/managesieve/lib/rcube_sieve.php
New file
@@ -0,0 +1,990 @@
<?php
/*
  Classes for managesieve operations (using PEAR::Net_Sieve)
  Author: Aleksander Machniak <alec@alec.pl>
  $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_DELETE', 6);        // script deletion
define('SIEVE_ERROR_INTERNAL', 7);      // internal error
define('SIEVE_ERROR_DEACTIVATE', 8);    // script activation
define('SIEVE_ERROR_OTHER', 255);       // other/unknown error
class rcube_sieve
{
    private $sieve;                 // Net_Sieve object
    private $error = false;         // error flag
    private $list = array();        // scripts list
    public $script;                 // rcube_sieve_script object
    public $current;                // name of currently loaded script
    private $disabled;              // array of disabled extensions
    /**
     * Object constructor
     *
     * @param string  Username (for managesieve login)
     * @param string  Password (for managesieve login)
     * @param string  Managesieve server hostname/address
     * @param string  Managesieve server port number
     * @param string  Managesieve authentication method
     * @param boolean Enable/disable TLS use
     * @param array   Disabled extensions
     * @param boolean Enable/disable debugging
     * @param string  Proxy authentication identifier
     * @param string  Proxy authentication password
     */
    public function __construct($username, $password='', $host='localhost', $port=2000,
        $auth_type=null, $usetls=true, $disabled=array(), $debug=false,
        $auth_cid=null, $auth_pw=null)
    {
        $this->sieve = new Net_Sieve();
        if ($debug) {
            $this->sieve->setDebug(true, array($this, 'debug_handler'));
        }
        if (PEAR::isError($this->sieve->connect($host, $port, NULL, $usetls))) {
            return $this->_set_error(SIEVE_ERROR_CONNECTION);
        }
        if (!empty($auth_cid)) {
            $authz    = $username;
            $username = $auth_cid;
            $password = $auth_pw;
        }
        if (PEAR::isError($this->sieve->login($username, $password,
            $auth_type ? strtoupper($auth_type) : null, $authz))
        ) {
            return $this->_set_error(SIEVE_ERROR_LOGIN);
        }
        $this->disabled = $disabled;
    }
    public function __destruct() {
        $this->sieve->disconnect();
    }
    /**
     * Getter for error code
     */
    public function error()
    {
        return $this->error ? $this->error : false;
    }
    /**
     * Saves current script into server
     */
    public function save($name = null)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (!$this->script)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (!$name)
            $name = $this->current;
        $script = $this->script->as_text();
        if (!$script)
            $script = '/* empty script */';
        if (PEAR::isError($this->sieve->installScript($name, $script)))
            return $this->_set_error(SIEVE_ERROR_INSTALL);
        return true;
    }
    /**
     * Saves text script into server
     */
    public function save_script($name, $content = null)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (!$content)
            $content = '/* empty script */';
        if (PEAR::isError($this->sieve->installScript($name, $content)))
            return $this->_set_error(SIEVE_ERROR_INSTALL);
        return true;
    }
    /**
     * Activates specified script
     */
    public function activate($name = null)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (!$name)
            $name = $this->current;
        if (PEAR::isError($this->sieve->setActive($name)))
            return $this->_set_error(SIEVE_ERROR_ACTIVATE);
        return true;
    }
    /**
     * De-activates specified script
     */
    public function deactivate()
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (PEAR::isError($this->sieve->setActive('')))
            return $this->_set_error(SIEVE_ERROR_DEACTIVATE);
        return true;
    }
    /**
     * Removes specified script
     */
    public function remove($name = null)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if (!$name)
            $name = $this->current;
        // script must be deactivated first
        if ($name == $this->sieve->getActive())
            if (PEAR::isError($this->sieve->setActive('')))
                return $this->_set_error(SIEVE_ERROR_DELETE);
        if (PEAR::isError($this->sieve->removeScript($name)))
            return $this->_set_error(SIEVE_ERROR_DELETE);
        if ($name == $this->current)
            $this->current = null;
        return true;
    }
    /**
     * Gets list of supported by server Sieve extensions
     */
    public function get_extensions()
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        $ext = $this->sieve->getExtensions();
        // we're working on lower-cased names
        $ext = array_map('strtolower', (array) $ext);
        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);
    }
    /**
     * Gets list of scripts from server
     */
    public function get_scripts()
    {
        if (!$this->list) {
            if (!$this->sieve)
                return $this->_set_error(SIEVE_ERROR_INTERNAL);
            $this->list = $this->sieve->listScripts();
            if (PEAR::isError($this->list))
                return $this->_set_error(SIEVE_ERROR_OTHER);
        }
        return $this->list;
    }
    /**
     * Returns active script name
     */
    public function get_active()
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        return $this->sieve->getActive();
    }
    /**
     * Loads script by name
     */
    public function load($name)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if ($this->current == $name)
            return true;
        $script = $this->sieve->getScript($name);
        if (PEAR::isError($script))
            return $this->_set_error(SIEVE_ERROR_OTHER);
        // try to parse from Roundcube format
        $this->script = $this->_parse($script);
        $this->current = $name;
        return true;
    }
    /**
     * Loads script from text content
     */
    public function load_script($script)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        // try to parse from Roundcube format
        $this->script = $this->_parse($script);
    }
    /**
     * Creates rcube_sieve_script object from text script
     */
    private function _parse($txt)
    {
        // try to parse from Roundcube format
        $script = new rcube_sieve_script($txt, $this->disabled);
        // ... else try to import from different formats
        if (empty($script->content)) {
            $script = $this->_import_rules($txt);
            $script = new rcube_sieve_script($script, $this->disabled);
        }
        // replace all elsif with if+stop, we support only ifs
        foreach ($script->content as $idx => $rule) {
            if (!isset($script->content[$idx+1])
                || preg_match('/^else|elsif$/', $script->content[$idx+1]['type'])) {
                // 'stop' not found?
                if (!preg_match('/^(stop|vacation)$/', $rule['actions'][count($rule['actions'])-1]['type'])) {
                    $script->content[$idx]['actions'][] = array(
                        'type' => 'stop'
                    );
                }
            }
        }
        return $script;
    }
    /**
     * Gets specified script as text
     */
    public function get_script($name)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        $content = $this->sieve->getScript($name);
        if (PEAR::isError($content))
            return $this->_set_error(SIEVE_ERROR_OTHER);
        return $content;
    }
    /**
     * Creates empty script or copy of other script
     */
    public function copy($name, $copy)
    {
        if (!$this->sieve)
            return $this->_set_error(SIEVE_ERROR_INTERNAL);
        if ($copy) {
            $content = $this->sieve->getScript($copy);
            if (PEAR::isError($content))
                return $this->_set_error(SIEVE_ERROR_OTHER);
        }
        return $this->save_script($name, $content);
    }
    private function _import_rules($script)
    {
        $i = 0;
        $name = array();
        // Squirrelmail (Avelsieve)
        if ($tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\r?\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])) {
                    // This preg_replace is added because I've found some Avelsieve scripts
                    // with rules containing "if" here. I'm not sure it was working
                    // before without this or not.
                    $token = preg_replace('/^if\s+/', '', trim($token));
                    $content .= "if $token\n";
                    $i++;
                }
            }
        }
        // Horde (INGO)
        else if ($tokens = preg_split('/(# .+)\r?\n/i', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
            foreach($tokens as $token) {
                if (preg_match('/^# (.+)/i', $token, $matches)) {
                    $name[$i] = $matches[1];
                    $content .= "# rule:[" . $name[$i] . "]\n";
                }
                elseif (isset($name[$i])) {
                    $token = str_replace(":comparator \"i;ascii-casemap\" ", "", $token);
                    $content .= $token . "\n";
                    $i++;
                }
            }
        }
        return $content;
    }
    private function _set_error($error)
    {
        $this->error = $error;
        return false;
    }
    /**
     * This is our own debug handler for connection
     */
    public function debug_handler(&$sieve, $message)
    {
        write_log('sieve', preg_replace('/\r\n$/', '', $message));
    }
}
class rcube_sieve_script
{
    public $content = array();      // script rules array
    private $supported = array(     // extensions supported by class
        'fileinto',
        'reject',
        'ereject',
        'copy',                     // RFC3894
        'vacation',                 // RFC5230
        'relational',               // RFC3431
    // 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=NULL)
    {
        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();
        $idx = 0;
        // rules
        foreach ($this->content as $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 ' : '');
                    // relational operator + comparator
                    if (preg_match('/^(value|count)-([gteqnl]{2})/', $test['type'], $m)) {
                        array_push($exts, 'relational');
                        array_push($exts, 'comparator-i;ascii-numeric');
                        $tests[$i] .= 'header :' . $m[1] . ' "' . $m[2] . '" :comparator "i;ascii-numeric"';
                    }
                    else
                        $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 (');
            // disabled rule: if false #....
            $script .= 'if' . ($rule['disabled'] ? ' false #' : '');
            $script .= $rule['join'] ? ' allof (' : ' anyof (';
            if (sizeof($tests) > 1)
                $script .= implode(", ", $tests);
            else if (sizeof($tests))
                $script .= $tests[0];
            else
                $script .= 'true';
            $script .= ")\n{\n";
            // action(s)
            foreach ($rule['actions'] as $action) {
                switch ($action['type']) {
                case 'fileinto':
                    array_push($exts, 'fileinto');
                    $script .= "\tfileinto ";
                    if ($action['copy']) {
                        $script .= ':copy ';
                        array_push($exts, 'copy');
                    }
                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
                    break;
                case 'redirect':
                    $script .= "\tredirect ";
                    if ($action['copy']) {
                        $script .= ':copy ';
                        array_push($exts, 'copy');
                    }
                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
                    break;
                case 'reject':
                case 'ereject':
                    array_push($exts, $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':
                    array_push($exts, '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";
            $idx++;
        }
        // requires
        if (!empty($exts))
            $script = 'require ["' . implode('","', array_unique($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];
                }
                else if (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+((true|false|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm',
            trim($content), $matches)) {
            $tests = trim($matches[2]);
            // disabled rule (false + comment): if false #.....
            if ($matches[3] == 'false') {
                $tests = preg_replace('/^false\s+#\s+/', '', $tests);
                $disabled = true;
            }
            else
                $disabled = false;
            list($tests, $join) = $this->_parse_tests($tests);
            $actions = $this->_parse_actions(trim($matches[5]));
            if ($tests && $actions)
                $result = array(
                    'type'     => $matches[1],
                    'tests'    => $tests,
                    'actions'  => $actions,
                    'join'     => $join,
                    'disabled' => $disabled,
            );
        }
        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('\s*$)|(', $patterns) . '$\s*)/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]);
                }
                else if(preg_match('/^fileinto/', $content)) {
                    $target = $m[sizeof($m)-1];
                    $copy = false;
                    if (preg_match('/^:copy\s+/', $target)) {
                        $target = preg_replace('/^:copy\s+/', '', $target);
                        $copy = true;
                    }
                    $result[] = array('type' => 'fileinto', 'copy' => $copy,
                        'target' => $this->_parse_string($target));
                }
                else if(preg_match('/^redirect/', $content)) {
                    $target = $m[sizeof($m)-1];
                    $copy = false;
                    if (preg_match('/^:copy\s+/', $target)) {
                        $target = preg_replace('/^:copy\s+/', '', $target);
                        $copy = true;
                    }
                    $result[] = array('type' => 'redirect', 'copy' => $copy,
                        'target' => $this->_parse_string($target));
                }
                else if(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) {
                    $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2]));
                }
                else if(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) {
                    $vacation = array('type' => 'vacation');
                    if (preg_match('/:days\s+([0-9]+)/', $content, $vm)) {
                        $vacation['days'] = $vm[1];
                        $content = preg_replace('/:days\s+([0-9]+)/', '', $content);
                    }
                    if (preg_match('/:subject\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['subject'] = $vm[1];
                        $content = preg_replace('/:subject\s+"(.*?[^\\\])"/', '', $content);
                    }
                    if (preg_match('/:addresses\s+\[(.*?[^\\\])\]/', $content, $vm)) {
                        $vacation['addresses'] = $this->_parse_list($vm[1]);
                        $content = preg_replace('/:addresses\s+\[(.*?[^\\\])\]/', '', $content);
                    }
                    if (preg_match('/:handle\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['handle'] = $vm[1];
                        $content = preg_replace('/:handle\s+"(.*?[^\\\])"/', '', $content);
                    }
                    if (preg_match('/:from\s+"(.*?[^\\\])"/', $content, $vm)) {
                        $vacation['from'] = $vm[1];
                        $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+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+(".*?[^\\\]")\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(count\s+"[gtleqn]{2}"|value\s+"[gtleqn]{2}")(\s+:comparator\s+"(.*?[^\\\])")?\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
                    );
                }
                else if (preg_match('/^(not\s+)?header/', $match[0])) {
                    $type = $match[$size-5];
                    if (preg_match('/^(count|value)\s+"([gtleqn]{2})"/', $type, $m))
                        $type = $m[1] . '-' . $m[2];
                    $result[] = array(
                        'test' => 'header',
                        'type' => $type, // is/contains/matches
                        'not'  => $match[$size-7] ? true : false,
                        'arg1' => $this->_parse_list($match[$size-2]), // header(s)
                        'arg2' => $this->_parse_list($match[$size-1]), // string(s)
                    );
                }
                else if (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)
                    );
                }
                else if (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]);
        else if (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;
        else if (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) . '"]';
    }
}
plugins/managesieve/localization/bg_BG.inc
New file
@@ -0,0 +1,50 @@
<?php
$labels = array();
$labels['filters'] = 'Филтри';
$labels['managefilters'] = 'Управление на филтри за входяща поща';
$labels['filtername'] = 'Име на филтър';
$labels['newfilter'] = 'Нов филтър';
$labels['filteradd'] = 'Добавяне на филтър';
$labels['filterdel'] = 'Изтриване на филтър';
$labels['moveup'] = 'Преместване нагоре';
$labels['movedown'] = 'Преместване надолу';
$labels['filterallof'] = 'съвпадение на всички следващи правила';
$labels['filteranyof'] = 'съвпадение на някое от следните правила';
$labels['filterany'] = 'всички съобщения';
$labels['filtercontains'] = 'съдържа';
$labels['filternotcontains'] = 'не съдържа';
$labels['filteris'] = 'е равно на';
$labels['filterisnot'] = 'не е равно на';
$labels['filterexists'] = 'съществува';
$labels['filternotexists'] = 'не съществува';
$labels['filterunder'] = 'под';
$labels['filterover'] = 'над';
$labels['addrule'] = 'Добавяне на правило';
$labels['delrule'] = 'Изтриване на правило';
$labels['messagemoveto'] = 'Преместване на съобщението в';
$labels['messageredirect'] = 'Пренасочване на съобщението до';
$labels['messagereply'] = 'Отговор със съобщение';
$labels['messagedelete'] = 'Изтриване на съобщение';
$labels['messagediscard'] = 'Отхвърляне със съобщение';
$labels['messagesrules'] = 'За входящата поща:';
$labels['messagesactions'] = '...изпълнение на следните действия';
$labels['add'] = 'Добавяне';
$labels['del'] = 'Изтриване';
$labels['sender'] = 'Подател';
$labels['recipient'] = 'Получател';
$messages = array();
$messages['filterunknownerror'] = 'Неизвестна грешка на сървъра';
$messages['filterconnerror'] = 'Невъзможност за свързване с managesieve сървъра ';
$messages['filterdeleteerror'] = 'Невъзможност за изтриване на филтър. Сървър грешка';
$messages['filterdeleted'] = 'Филтърът е изтрит успешно';
$messages['filterdeleteconfirm'] = 'Наистина ли искате да изтриете избрания филтър?';
$messages['filtersaved'] = 'Филтърът е записан успешно';
$messages['filtersaveerror'] = 'Филтърът не може да бъде записан. Сървър грешка.';
$messages['ruledeleteconfirm'] = 'Сигурни ли сте, че искате да изтриете избраното правило?';
$messages['actiondeleteconfirm'] = 'Сигурни ли сте, че искате да изтриете избраното действие?';
$messages['forbiddenchars'] = 'Забранени символи в полето';
$messages['cannotbeempty'] = 'Полето не може да бъде празно';
?>
plugins/managesieve/localization/cs_CZ.inc
New file
@@ -0,0 +1,61 @@
<?php
/**
 * Czech translation for Roundcube managesieve plugin
 *
 * @version 1.0 (2009-09-29)
 * @author Daniel Kolar <kolar@g2n.cz>
 *
 */
$labels['filters'] = 'Filtry';
$labels['managefilters'] = 'Nastavení filtrů';
$labels['filtername'] = 'Název filtru';
$labels['newfilter'] = 'Nový filtr';
$labels['filteradd'] = 'Přidej filtr';
$labels['filterdel'] = 'Smaž filtr';
$labels['moveup'] = 'Posunout nahoru';
$labels['movedown'] = 'Posunout dolů';
$labels['filterallof'] = 'Odpovídají všechny pravidla';
$labels['filteranyof'] = 'Odpovídá kterékoliv pravidlo';
$labels['filterany'] = 'Všechny zprávy';
$labels['filtercontains'] = 'obsahuje';
$labels['filternotcontains'] = 'neobsahuje';
$labels['filteris'] = 'odpovídá';
$labels['filterisnot'] = 'neodpovídá';
$labels['filterexists'] = 'existuje';
$labels['filternotexists'] = 'neexistuje';
$labels['filterunder'] = 'pod';
$labels['filterover'] = 'nad';
$labels['addrule'] = 'Přidej pravidlo';
$labels['delrule'] = 'Smaž pravidlo';
$labels['messagemoveto'] = 'Přesuň zprávu do';
$labels['messageredirect'] = 'Přeposlat zprávu na';
$labels['messagereply'] = 'Odpovědět se zprávou';
$labels['messagedelete'] = 'Smazat zprávu';
$labels['messagediscard'] = 'Smazat se zprávou';
$labels['messagesrules'] = 'Pravidla pro příchozí zprávu:';
$labels['messagesactions'] = '...vykonej následující akce:';
$labels['add'] = 'Přidej';
$labels['del'] = 'Smaž';
$labels['sender'] = 'Odesílatel';
$labels['recipient'] = 'Příjemce';
$labels['vacationaddresses'] = 'Seznam příjemců, kterým nebude zpráva odeslána (oddělené čárkou):';
$labels['vacationdays'] = 'Počet dnů mezi automatickými odpověďmi:';
$labels['vacationreason'] = 'Zpráva (Důvod nepřítomnosti):';
$labels['rulestop'] = 'Zastavit pravidla';
$messages = array();
$messages['filterunknownerror'] = 'Neznámá chyba serveru';
$messages['filterconnerror'] = 'Nebylo možné se připojit k sieve serveru';
$messages['filterdeleteerror'] = 'Nebylo možné smazat filtr. Server nahlásil chybu';
$messages['filterdeleted'] = 'Filtr byl smazán';
$messages['filterdeleteconfirm'] = 'Opravdu chcete smazat vybraný filtr?';
$messages['filtersaved'] = 'Filtr byl uložen';
$messages['filtersaveerror'] = 'Nebylo možné uložit filtr. Server nahlásil chybu.';
$messages['ruledeleteconfirm'] = 'Jste si jisti, že chcete smazat vybrané pravidlo?';
$messages['actiondeleteconfirm'] = 'Jste si jisti, že chcete smazat vybranou akci?';
$messages['forbiddenchars'] = 'Zakázané znaky v poli';
$messages['cannotbeempty'] = 'Pole nemůže být prázdné';
?>
plugins/managesieve/localization/de_CH.inc
New file
@@ -0,0 +1,52 @@
<?php
$labels['filters'] = 'Filter';
$labels['managefilters'] = 'Verwalte eingehende Nachrichtenfilter';
$labels['filtername'] = 'Filtername';
$labels['newfilter'] = 'Neuer Filter';
$labels['filteradd'] = 'Filter hinzufügen';
$labels['filterdel'] = 'Filter löschen';
$labels['moveup'] = 'Nach oben';
$labels['movedown'] = 'Nach unten';
$labels['filterallof'] = 'UND (alle Regeln müssen zutreffen)';
$labels['filteranyof'] = 'ODER (eine der Regeln muss zutreffen';
$labels['filterany'] = 'Für alle Nachrichten';
$labels['filtercontains'] = 'enthält';
$labels['filternotcontains'] = 'enthält nicht';
$labels['filteris'] = 'ist gleich';
$labels['filterisnot'] = 'ist ungleich';
$labels['filterexists'] = 'ist vorhanden';
$labels['filternotexists'] = 'nicht vorhanden';
$labels['filterunder'] = 'unter';
$labels['filterover'] = 'über';
$labels['addrule'] = 'Regel hinzufügen';
$labels['delrule'] = 'Regel löschen';
$labels['messagemoveto'] = 'Verschiebe Nachricht nach';
$labels['messageredirect'] = 'Leite Nachricht um nach';
$labels['messagereply'] = 'Antworte mit Nachricht';
$labels['messagedelete'] = 'Nachricht löschen';
$labels['messagediscard'] = 'Discard with message';
$labels['messagesrules'] = 'Für eingehende Nachrichten:';
$labels['messagesactions'] = 'Führe folgende Aktionen aus:';
$labels['add'] = 'Hinzufügen';
$labels['del'] = 'Löschen';
$labels['sender'] = 'Absender';
$labels['recipient'] = 'Empfänger';
$labels['vacationaddresses'] = 'Zusätzliche Liste von Empfängern (Komma getrennt):';
$labels['vacationdays'] = 'Antwort wird erneut gesendet nach (in Tagen):';
$labels['vacationreason'] = 'Inhalt der Nachricht (Abwesenheitsgrund):';
$labels['rulestop'] = 'Regelauswertung anhalten';
$messages['filterunknownerror'] = 'Unbekannter Serverfehler';
$messages['filterconnerror'] = 'Kann nicht zum Sieve-Server verbinden';
$messages['filterdeleteerror'] = 'Fehler beim des löschen  Filters. Serverfehler';
$messages['filterdeleted'] = 'Filter erfolgreich gelöscht';
$messages['filterdeleteconfirm'] = 'Möchten Sie den Filter löschen ?';
$messages['filtersaved'] = 'Filter gespeichert';
$messages['filtersaveerror'] = 'Serverfehler, konnte den Filter nicht speichern.';
$messages['ruledeleteconfirm'] = 'Sicher, dass Sie die Regel löschen wollen?';
$messages['actiondeleteconfirm'] = 'Sicher, dass Sie die ausgewaehlte Aktion löschen wollen?';
$messages['forbiddenchars'] = 'Unerlaubte Zeichen im Feld';
$messages['cannotbeempty'] = 'Feld darf nicht leer sein';
?>
plugins/managesieve/localization/de_DE.inc
New file
@@ -0,0 +1,55 @@
<?php
$labels['filters'] = 'Filter';
$labels['managefilters'] = 'Verwalte eingehende Nachrichtenfilter';
$labels['filtername'] = 'Filtername';
$labels['newfilter'] = 'Neuer Filter';
$labels['filteradd'] = 'Filter hinzufügen';
$labels['filterdel'] = 'Filter löschen';
$labels['moveup'] = 'Nach oben';
$labels['movedown'] = 'Nach unten';
$labels['filterallof'] = 'trifft auf alle folgenden Regeln zu';
$labels['filteranyof'] = 'trifft auf eine der folgenden Regeln zu';
$labels['filterany'] = 'alle Nachrichten';
$labels['filtercontains'] = 'enthält';
$labels['filternotcontains'] = 'enthält nicht';
$labels['filteris'] = 'ist gleich';
$labels['filterisnot'] = 'ist ungleich';
$labels['filterexists'] = 'vorhanden';
$labels['filternotexists'] = 'nicht vorhanden';
$labels['filterunder'] = 'unter';
$labels['filterover'] = 'über';
$labels['addrule'] = 'Regel hinzufügen';
$labels['delrule'] = 'Regel löschen';
$labels['messagemoveto'] = 'Verschiebe Nachricht nach';
$labels['messageredirect'] = 'Leite Nachricht um an';
$labels['messagecopyto'] = 'Kopiere Nachricht nach';
$labels['messagesendcopy'] = 'Sende Kopie an';
$labels['messagereply'] = 'Antworte mit Nachricht';
$labels['messagedelete'] = 'Nachricht löschen';
$labels['messagediscard'] = 'Weise ab mit Nachricht';
$labels['messagesrules'] = 'Für eingehende Nachrichten:';
$labels['messagesactions'] = '...führe folgende Aktionen aus:';
$labels['add'] = 'Hinzufügen';
$labels['del'] = 'Löschen';
$labels['sender'] = 'Absender';
$labels['recipient'] = 'Empfänger';
$labels['vacationaddresses'] = 'Zusätzliche Liste von eMail-Empfängern (Komma getrennt):';
$labels['vacationdays'] = 'Wie oft sollen Nachrichten gesendet werden (in Tagen):';
$labels['vacationreason'] = 'Inhalt der Nachricht (Abwesenheitsgrund):';
$labels['rulestop'] = 'Regelauswertung anhalten';
$messages = array();
$messages['filterunknownerror'] = 'Unbekannter Serverfehler';
$messages['filterconnerror'] = 'Kann nicht zum Sieve-Server verbinden';
$messages['filterdeleteerror'] = 'Fehler beim Löschen des Filters. Serverfehler';
$messages['filterdeleted'] = 'Filter erfolgreich gelöscht';
$messages['filterdeleteconfirm'] = 'Möchten Sie den Filter löschen?';
$messages['filtersaved'] = 'Filter gespeichert';
$messages['filtersaveerror'] = 'Serverfehler, konnte den Filter nicht speichern.';
$messages['ruledeleteconfirm'] = 'Sicher, dass Sie die Regel löschen wollen?';
$messages['actiondeleteconfirm'] = 'Sicher, dass Sie die ausgewählte Aktion löschen wollen?';
$messages['forbiddenchars'] = 'Unerlaubte Zeichen im Feld';
$messages['cannotbeempty'] = 'Feld darf nicht leer sein';
?>
plugins/managesieve/localization/el_GR.inc
New file
@@ -0,0 +1,56 @@
<?php
// Antonis Kanouras (2009-10-22)
$labels = array();
$labels['filters'] = 'Φίλτρα';
$labels['managefilters'] = 'Διαχείριση φίλτρων εισερχόμενων';
$labels['filtername'] = 'Ονομασία φίλτρου';
$labels['newfilter'] = 'Δημιουργία φίλτρου';
$labels['filteradd'] = 'Προσθήκη φίλτρου';
$labels['filterdel'] = 'Διαγραφή φίλτρου';
$labels['moveup'] = 'Μετακίνηση πάνω';
$labels['movedown'] = 'Μετακίνηση κάτω';
$labels['filterallof'] = 'ταιριάζουν με όλους τους παρακάτω κανόνες';
$labels['filteranyof'] = 'ταιριάζουν με οποιονδήποτε από τους παρακάτω κανόνες';
$labels['filterany'] = 'όλα τα μηνύματα';
$labels['filtercontains'] = 'περιέχει';
$labels['filternotcontains'] = 'δεν περιέχει';
$labels['filteris'] = 'είναι ίσο με';
$labels['filterisnot'] = 'δεν είναι ίσο με';
$labels['filterexists'] = 'υπάρχει';
$labels['filternotexists'] = 'δεν υπάρχει';
$labels['filterunder'] = 'κάτω';
$labels['filterover'] = 'πάνω';
$labels['addrule'] = 'Προσθήκη κανόνα';
$labels['delrule'] = 'Διαγραφή κανόνα';
$labels['messagemoveto'] = 'Μετακίνηση μηνύματος στο';
$labels['messageredirect'] = 'Προώθηση μηνύματος στο';
$labels['messagereply'] = 'Απάντηση με μήνυμα';
$labels['messagedelete'] = 'Διαγραφή μηνύματος';
$labels['messagediscard'] = 'Απόρριψη με μήνυμα';
$labels['messagesrules'] = 'Για εισερχόμενα μηνύματα που:';
$labels['messagesactions'] = '...εκτέλεση των παρακάτω ενεργειών:';
$labels['add'] = 'Προσθήκη';
$labels['del'] = 'Διαγραφή';
$labels['sender'] = 'Αποστολέας';
$labels['recipient'] = 'Παραλήπτης';
$labels['vacationaddresses'] = 'Πρόσθετη λίστα email παραληπτών (διαχωρισμένη με κόμματα):';
$labels['vacationdays'] = 'Συχνότητα αποστολής μηνυμάτων (σε ημέρες):';
$labels['vacationreason'] = 'Σώμα μηνύματος (λόγος απουσίας):';
$labels['rulestop'] = 'Παύση επαλήθευσης κανόνων';
$messages = array();
$messages['filterunknownerror'] = 'Άγνωστο σφάλμα διακομιστή';
$messages['filterconnerror'] = 'Αδυναμία σύνδεσης στον διακομιστή managesieve';
$messages['filterdeleteerror'] = 'Αδυναμία διαγραφής φίλτρου. Προέκυψε σφάλμα στον διακομιστή';
$messages['filterdeleted'] = 'Το φίλτρο διαγράφηκε επιτυχώς';
$messages['filterconfirmdelete'] = 'Θέλετε όντως να διαγράψετε το επιλεγμένο φίλτρο;';
$messages['filtersaved'] = 'Το φίλτρο αποθηκεύτηκε επιτυχώς';
$messages['filtersaveerror'] = 'Αδυναμία αποθήκευσης φίλτρου. Προέκυψε σφάλμα στον διακομιστή';
$messages['ruledeleteconfirm'] = 'Θέλετε όντως να διαγράψετε τον επιλεγμένο κανόνα;';
$messages['actiondeleteconfirm'] = 'Θέλετε όντως να διαγράψετε την επιλεγμένη ενέργεια;';
$messages['forbiddenchars'] = 'Μη επιτρεπτοί χαρακτήρες στο πεδίο';
$messages['cannotbeempty'] = 'Το πεδίο δεν μπορεί να είναι κενό';
?>
plugins/managesieve/localization/en_GB.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels['filters'] = 'Filters';
$labels['managefilters'] = 'Manage incoming mail filters';
$labels['filtername'] = 'Filter name';
$labels['newfilter'] = 'New filter';
$labels['filteradd'] = 'Add filter';
$labels['filterdel'] = 'Delete filter';
$labels['moveup'] = 'Move up';
$labels['movedown'] = 'Move down';
$labels['filterallof'] = 'matching all of the following rules';
$labels['filteranyof'] = 'matching any of the following rules';
$labels['filterany'] = 'all messages';
$labels['filtercontains'] = 'contains';
$labels['filternotcontains'] = 'not contains';
$labels['filteris'] = 'is equal to';
$labels['filterisnot'] = 'is not equal to';
$labels['filterexists'] = 'exists';
$labels['filternotexists'] = 'not exists';
$labels['filterunder'] = 'under';
$labels['filterover'] = 'over';
$labels['addrule'] = 'Add rule';
$labels['delrule'] = 'Delete rule';
$labels['messagemoveto'] = 'Move message to';
$labels['messageredirect'] = 'Redirect message to';
$labels['messagereply'] = 'Reply with message';
$labels['messagedelete'] = 'Delete message';
$labels['messagediscard'] = 'Discard with message';
$labels['messagesrules'] = 'For incoming mail:';
$labels['messagesactions'] = '...execute the following actions:';
$labels['add'] = 'Add';
$labels['del'] = 'Delete';
$labels['sender'] = 'Sender';
$labels['recipient'] = 'Recipient';
$labels['vacationaddresses'] = 'Additional list of recipient e-mails (comma separated):';
$labels['vacationdays'] = 'How often send messages (in days):';
$labels['vacationreason'] = 'Message body (vacation reason):';
$labels['rulestop'] = 'Stop evaluating rules';
$messages = array();
$messages['filterunknownerror'] = 'Unknown server error';
$messages['filterconnerror'] = 'Unable to connect to managesieve server';
$messages['filterdeleteerror'] = 'Unable to delete filter. Server error occured';
$messages['filterdeleted'] = 'Filter deleted successfully';
$messages['filterdeleteconfirm'] = 'Do you really want to delete selected filter?';
$messages['filtersaved'] = 'Filter saved successfully';
$messages['filtersaveerror'] = 'Unable to save filter. Server error occured.';
$messages['ruledeleteconfirm'] = 'Are you sure, you want to delete selected rule?';
$messages['actiondeleteconfirm'] = 'Are you sure, you want to delete selected action?';
$messages['forbiddenchars'] = 'Forbidden characters in field';
$messages['cannotbeempty'] = 'Field cannot be empty';
?>
plugins/managesieve/localization/en_US.inc
New file
@@ -0,0 +1,92 @@
<?php
$labels['filters'] = 'Filters';
$labels['managefilters'] = 'Manage incoming mail filters';
$labels['filtername'] = 'Filter name';
$labels['newfilter'] = 'New filter';
$labels['filteradd'] = 'Add filter';
$labels['filterdel'] = 'Delete filter';
$labels['moveup'] = 'Move up';
$labels['movedown'] = 'Move down';
$labels['filterallof'] = 'matching all of the following rules';
$labels['filteranyof'] = 'matching any of the following rules';
$labels['filterany'] = 'all messages';
$labels['filtercontains'] = 'contains';
$labels['filternotcontains'] = 'not contains';
$labels['filteris'] = 'is equal to';
$labels['filterisnot'] = 'is not equal to';
$labels['filterexists'] = 'exists';
$labels['filternotexists'] = 'not exists';
$labels['filterunder'] = 'under';
$labels['filterover'] = 'over';
$labels['addrule'] = 'Add rule';
$labels['delrule'] = 'Delete rule';
$labels['messagemoveto'] = 'Move message to';
$labels['messageredirect'] = 'Redirect message to';
$labels['messagecopyto'] = 'Copy message to';
$labels['messagesendcopy'] = 'Send message copy to';
$labels['messagereply'] = 'Reply with message';
$labels['messagedelete'] = 'Delete message';
$labels['messagediscard'] = 'Discard with message';
$labels['messagesrules'] = 'For incoming mail:';
$labels['messagesactions'] = '...execute the following actions:';
$labels['add'] = 'Add';
$labels['del'] = 'Delete';
$labels['sender'] = 'Sender';
$labels['recipient'] = 'Recipient';
$labels['vacationaddresses'] = 'Additional list of recipient e-mails (comma separated):';
$labels['vacationdays'] = 'How often send messages (in days):';
$labels['vacationreason'] = 'Message body (vacation reason):';
$labels['rulestop'] = 'Stop evaluating rules';
$labels['filterset'] = 'Filters set';
$labels['filtersetadd'] = 'Add filters set';
$labels['filtersetdel'] = 'Delete current filters set';
$labels['filtersetact'] = 'Activate current filters set';
$labels['filtersetdeact'] = 'Deactivate current filters set';
$labels['filtersetget'] = 'Download filters set in text format';
$labels['filterdef'] = 'Filter definition';
$labels['filtersetname'] = 'Filters set name';
$labels['newfilterset'] = 'New filters set';
$labels['active'] = 'active';
$labels['none'] = 'none';
$labels['fromset'] = 'from set';
$labels['fromfile'] = 'from file';
$labels['filterdisabled'] = 'Filter disabled';
$labels['countisgreaterthan'] = 'count is greater than';
$labels['countisgreaterthanequal'] = 'count is greater than or equal to';
$labels['countislessthan'] = 'count is less than';
$labels['countislessthanequal'] = 'count is less than or equal to';
$labels['countequals'] = 'count is equal to';
$labels['countnotequals'] = 'count does not equal';
$labels['valueisgreaterthan'] = 'value is greater than';
$labels['valueisgreaterthanequal'] = 'value is greater than or equal to';
$labels['valueislessthan'] = 'value is less than';
$labels['valueislessthanequal'] = 'value is less than or equal to';
$labels['valueequals'] = 'value is equal to';
$labels['valuenotequals'] = 'value does not equal';
$messages = array();
$messages['filterunknownerror'] = 'Unknown server error';
$messages['filterconnerror'] = 'Unable to connect to managesieve server';
$messages['filterdeleteerror'] = 'Unable to delete filter. Server error occured';
$messages['filterdeleted'] = 'Filter deleted successfully';
$messages['filtersaved'] = 'Filter saved successfully';
$messages['filtersaveerror'] = 'Unable to save filter. Server error occured';
$messages['filterdeleteconfirm'] = 'Do you really want to delete selected filter?';
$messages['ruledeleteconfirm'] = 'Are you sure, you want to delete selected rule?';
$messages['actiondeleteconfirm'] = 'Are you sure, you want to delete selected action?';
$messages['forbiddenchars'] = 'Forbidden characters in field';
$messages['cannotbeempty'] = 'Field cannot be empty';
$messages['setactivateerror'] = 'Unable to activate selected filters set. Server error occured';
$messages['setdeactivateerror'] = 'Unable to deactivate selected filters set. Server error occured';
$messages['setdeleteerror'] = 'Unable to delete selected filters set. Server error occured';
$messages['setactivated'] = 'Filters set activated successfully';
$messages['setdeactivated'] = 'Filters set deactivated successfully';
$messages['setdeleted'] = 'Filters set deleted successfully';
$messages['setdeleteconfirm'] = 'Are you sure, you want to delete selected filters set?';
$messages['setcreateerror'] = 'Unable to create filters set. Server error occured';
$messages['setcreated'] = 'Filters set created successfully';
$messages['emptyname'] = 'Unable to create filters set. Empty set name';
$messages['nametoolong'] = 'Unable to create filters set. Name too long'
?>
plugins/managesieve/localization/es_AR.inc
New file
@@ -0,0 +1,81 @@
<?php
$labels = array();
$labels['filters'] = 'Filtros';
$labels['managefilters'] = 'Administrar filtros de correo entrante';
$labels['filtername'] = 'Nombre del filtro';
$labels['newfilter'] = 'Nuevo filtro';
$labels['filteradd'] = 'Agregar filtro';
$labels['filterdel'] = 'Eliminar filtro';
$labels['moveup'] = 'Mover arriba';
$labels['movedown'] = 'Mover abajo';
$labels['filterallof'] = 'coinidir con todas las reglas siguientes';
$labels['filteranyof'] = 'coincidir con alguna de las reglas siguientes';
$labels['filterany'] = 'todos los mensajes';
$labels['filtercontains'] = 'contiene';
$labels['filternotcontains'] = 'no contiene';
$labels['filteris'] = 'es igual a';
$labels['filterisnot'] = 'no es igual a';
$labels['filterexists'] = 'existe';
$labels['filternotexists'] = 'no existe';
$labels['filterunder'] = 'bajo';
$labels['filterover'] = 'sobre';
$labels['addrule'] = 'Agregar regla';
$labels['delrule'] = 'Eliminar regla';
$labels['messagemoveto'] = 'Mover mensaje a';
$labels['messageredirect'] = 'Redirigir mensaje a';
$labels['messagecopyto'] = 'Copiar mensaje a';
$labels['messagesendcopy'] = 'Enviar copia del mensaje a';
$labels['messagereply'] = 'Responder con un mensaje';
$labels['messagedelete'] = 'Eliminar mensaje';
$labels['messagediscard'] = 'Descartar con un mensaje';
$labels['messagesrules'] = 'Para el correo entrante:';
$labels['messagesactions'] = '... ejecutar las siguientes acciones:';
$labels['add'] = 'Agregar';
$labels['del'] = 'Eliminar';
$labels['sender'] = 'Remitente';
$labels['recipient'] = 'Destinatario';
$labels['vacationaddresses'] = 'Lista de direcciones de correo de destinatarios adicionales (separados por comas):';
$labels['vacationdays'] = 'Cada cuanto enviar mensajes (en días):';
$labels['vacationreason'] = 'Cuerpo del mensaje (razón de vacaciones):';
$labels['rulestop'] = 'Parar de evaluar reglas';
$labels['filterset'] = 'Conjunto de filtros';
$labels['filtersetadd'] = 'Agregar conjunto de filtros';
$labels['filtersetdel'] = 'Eliminar conjunto de filtros';
$labels['filtersetact'] = 'Activar conjunto de filtros';
$labels['filtersetdeact'] = 'Deactivar conjunto de filtros';
$labels['filtersetget'] = 'Descargar conjunto de filtros en archivo de texto';
$labels['filterdef'] = 'Definicion del conjunto de filtros';
$labels['filtersetname'] = 'Nombre del conjunto de filtros';
$labels['newfilterset'] = 'Nuevo conjunto de filtros';
$labels['active'] = 'Activar';
$labels['none'] = 'none';
$labels['fromset'] = 'desde conjunto';
$labels['fromfile'] = 'desde archivo';
$labels['filterdisabled'] = 'Filtro deshabilitado';
$messages = array();
$messages['filterunknownerror'] = 'Error desconocido de servidor';
$messages['filterconnerror'] = 'Imposible conectar con el servidor managesieve';
$messages['filterdeleteerror'] = 'Imposible borrar filtro. Ha ocurrido un error en el servidor';
$messages['filterdeleted'] = 'Filtro borrado satisfactoriamente';
$messages['filterdeleteconfirm'] = '¿Realmente desea borrar el filtro seleccionado?';
$messages['filtersaved'] = 'Filtro guardado satisfactoriamente';
$messages['filtersaveerror'] = 'Imposible guardar ell filtro. Ha ocurrido un error en el servidor';
$messages['ruledeleteconfirm'] = '¿Está seguro de que desea borrar la regla seleccionada?';
$messages['actiondeleteconfirm'] = '¿Está seguro de que desea borrar la acción seleccionada?';
$messages['forbiddenchars'] = 'Caracteres prohibidos en el campo';
$messages['cannotbeempty'] = 'El campo no puede estar vacío';
$messages['setactivateerror'] = 'Imposible activar el conjunto de filtros. Error en el servidor.';
$messages['setdeactivateerror'] = 'Imposible desactivar el conjunto de filtros. Error en el servidor.';
$messages['setdeleteerror'] = 'Imposible eliminar el conjunto de filtros. Error en el servidor.';
$messages['setactivated'] = 'Conjunto de filtros activados correctamente';
$messages['setdeactivated'] = 'Conjunto de filtros desactivados correctamente';
$messages['setdeleted'] = 'Conjunto de filtros eliminados correctamente';
$messages['setdeleteconfirm'] = '¿Esta seguro, que quiere eliminar el conjunto de filtros seleccionado?';
$messages['setcreateerror'] = 'Imposible crear el conjunto de filtros. Error en el servidor.';
$messages['setcreated'] = 'Conjunto de filtros creados correctamente';
$messages['emptyname'] = 'Imposible crear el conjunto de filtros. Nombre del conjunto de filtros vacio';
$messages['nametoolong'] = 'Imposible crear el conjunto de filtros. Nombre del conjunto de filtros muy largo';
?>
plugins/managesieve/localization/es_ES.inc
New file
@@ -0,0 +1,81 @@
<?php
$labels = array();
$labels['filters'] = 'Filtros';
$labels['managefilters'] = 'Administrar filtros de correo entrante';
$labels['filtername'] = 'Nombre del filtro';
$labels['newfilter'] = 'Nuevo filtro';
$labels['filteradd'] = 'Agregar filtro';
$labels['filterdel'] = 'Eliminar filtro';
$labels['moveup'] = 'Mover arriba';
$labels['movedown'] = 'Mover abajo';
$labels['filterallof'] = 'coinidir con todas las reglas siguientes';
$labels['filteranyof'] = 'coincidir con alguna de las reglas siguientes';
$labels['filterany'] = 'todos los mensajes';
$labels['filtercontains'] = 'contiene';
$labels['filternotcontains'] = 'no contiene';
$labels['filteris'] = 'es igual a';
$labels['filterisnot'] = 'no es igual a';
$labels['filterexists'] = 'existe';
$labels['filternotexists'] = 'no existe';
$labels['filterunder'] = 'bajo';
$labels['filterover'] = 'sobre';
$labels['addrule'] = 'Agregar regla';
$labels['delrule'] = 'Eliminar regla';
$labels['messagemoveto'] = 'Mover mensaje a';
$labels['messageredirect'] = 'Redirigir mensaje a';
$labels['messagecopyto'] = 'Copiar mensaje a';
$labels['messagesendcopy'] = 'Enviar copia del mensaje a';
$labels['messagereply'] = 'Responder con un mensaje';
$labels['messagedelete'] = 'Eliminar mensaje';
$labels['messagediscard'] = 'Descartar con un mensaje';
$labels['messagesrules'] = 'Para el correo entrante:';
$labels['messagesactions'] = '... ejecutar las siguientes acciones:';
$labels['add'] = 'Agregar';
$labels['del'] = 'Eliminar';
$labels['sender'] = 'Remitente';
$labels['recipient'] = 'Destinatario';
$labels['vacationaddresses'] = 'Lista de direcciones de correo de destinatarios adicionales (separados por comas):';
$labels['vacationdays'] = 'Cada cuanto enviar mensajes (en días):';
$labels['vacationreason'] = 'Cuerpo del mensaje (razón de vacaciones):';
$labels['rulestop'] = 'Parar de evaluar reglas';
$labels['filterset'] = 'Conjunto de filtros';
$labels['filtersetadd'] = 'Agregar conjunto de filtros';
$labels['filtersetdel'] = 'Eliminar conjunto de filtros actual';
$labels['filtersetact'] = 'Activar conjunto de filtros actual';
$labels['filtersetdeact'] = 'Desactivar conjunto de filtros actual';
$labels['filtersetget'] = 'Descargar conjunto de filtros en formato de texto';
$labels['filterdef'] = 'Definición de filtros';
$labels['filtersetname'] = 'Nombre del conjunto de filtros';
$labels['newfilterset'] = 'Nuevo conjunto de filtros';
$labels['active'] = 'activo';
$labels['none'] = 'ninguno';
$labels['fromset'] = 'de conjunto ';
$labels['fromfile'] = 'de archivo';
$labels['filterdisabled'] = 'Filtro desactivado';
$messages = array();
$messages['filterunknownerror'] = 'Error desconocido de servidor';
$messages['filterconnerror'] = 'Imposible conectar con el servidor managesieve';
$messages['filterdeleteerror'] = 'Imposible borrar filtro. Ha ocurrido un error en el servidor';
$messages['filterdeleted'] = 'Filtro borrado satisfactoriamente';
$messages['filtersaved'] = 'Filtro guardado satisfactoriamente';
$messages['filtersaveerror'] = 'Imposible guardar el filtro. Ha ocurrido un error en el servidor';
$messages['filterdeleteconfirm'] = '¿Realmente desea borrar el filtro seleccionado?';
$messages['ruledeleteconfirm'] = '¿Está seguro de que desea borrar la regla seleccionada?';
$messages['actiondeleteconfirm'] = '¿Está seguro de que desea borrar la acción seleccionada?';
$messages['forbiddenchars'] = 'Caracteres prohibidos en el campo';
$messages['cannotbeempty'] = 'El campo no puede estar vacío';
$messages['setactivateerror'] = 'Imposible activar el conjunto de filtros seleccionado. Ha ocurrido un error en el servidor';
$messages['setdeactivateerror'] = 'Imposible desactivar el conjunto de filtros seleccionado. Ha ocurrido un error en el servidor';
$messages['setdeleteerror'] = 'Imposible borrar el conjunto de filtros seleccionado. Ha ocurrido un error en el servidor';
$messages['setactivated'] = 'Conjunto de filtros activado satisfactoriamente';
$messages['setdeactivated'] = 'Conjunto de filtros desactivado satisfactoriamente';
$messages['setdeleted'] = 'Conjunto de filtros borrado satisfactoriamente';
$messages['setdeleteconfirm'] = '¿Está seguro de que desea borrar el conjunto de filtros seleccionado?';
$messages['setcreateerror'] = 'Imposible crear el conjunto de filtros. Ha ocurrido un error en el servidor';
$messages['setcreated'] = 'Conjunto de filtros creado satisfactoriamente';
$messages['emptyname'] = 'Imposible crear el conjunto de filtros. Sin nombre';
$messages['nametoolong'] = 'Imposible crear el conjunto de filtros. Nombre demasiado largo'
?>
plugins/managesieve/localization/et_EE.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels['filters'] = 'Filtrid';
$labels['managefilters'] = 'Halda sisenevate kirjade filtreid';
$labels['filtername'] = 'Filtri nimi';
$labels['newfilter'] = 'Uus filter';
$labels['filteradd'] = 'Lisa filter';
$labels['filterdel'] = 'Kustuta filter';
$labels['moveup'] = 'Liiguta üles';
$labels['movedown'] = 'Liiguta alla';
$labels['filterallof'] = 'vastab kõikidele järgnevatele reeglitele';
$labels['filteranyof'] = 'vastab mõnele järgnevatest reeglitest';
$labels['filterany'] = 'kõik kirjad';
$labels['filtercontains'] = 'sisaldab';
$labels['filternotcontains'] = 'ei sisalda';
$labels['filteris'] = 'on võrdne kui';
$labels['filterisnot'] = 'ei ole võrdne kui';
$labels['filterexists'] = 'on olemas';
$labels['filternotexists'] = 'pole olemas';
$labels['filterunder'] = 'alt';
$labels['filterover'] = 'üle';
$labels['addrule'] = 'Lisa reegel';
$labels['delrule'] = 'Kustuta reegel';
$labels['messagemoveto'] = 'Liiguta kiri';
$labels['messageredirect'] = 'Suuna kiri ümber';
$labels['messagereply'] = 'Vasta kirjaga';
$labels['messagedelete'] = 'Kustuta kiri';
$labels['messagediscard'] = 'Viska ära teatega';
$labels['messagesrules'] = 'Siseneva kirja puhul, mis:';
$labels['messagesactions'] = '...käivita järgnevad tegevused:';
$labels['add'] = 'Lisa';
$labels['del'] = 'Kustuta';
$labels['sender'] = 'Saatja';
$labels['recipient'] = 'Saaja';
$labels['vacationaddresses'] = 'Lisanimekiri saaja e-posti aadressidest (komadega eraldatud):';
$labels['vacationdays'] = 'Kui tihti kirju saata (päevades):';
$labels['vacationreason'] = 'Kirja sisu (puhkuse põhjus):';
$labels['rulestop'] = 'Peata reeglite otsimine';
$messages = array();
$messages['filterunknownerror'] = 'Tundmatu serveri tõrge';
$messages['filterconnerror'] = 'Managesieve serveriga ühendumine nurjus';
$messages['filterdeleteerror'] = 'Filtri kustutamine nurjus. Ilmnes serveri tõrge.';
$messages['filterdeleted'] = 'Filter edukalt kustutatud';
$messages['filterdeleteconfirm'] = 'Soovid valitud filtri kustutada?';
$messages['filtersaved'] = 'Filter edukalt salvestatud';
$messages['filtersaveerror'] = 'Filtri salvestamine nurjus. Ilmnes serveri tõrge.';
$messages['ruledeleteconfirm'] = 'Soovid valitud reegli kustutada?';
$messages['actiondeleteconfirm'] = 'Soovid valitud tegevuse kustutada?';
$messages['forbiddenchars'] = 'Väljal on lubamatu märk';
$messages['cannotbeempty'] = 'Väli ei või tühi olla';
?>
plugins/managesieve/localization/fi_FI.inc
New file
@@ -0,0 +1,68 @@
<?php
$labels = array();
$labels['filters'] = 'Suodattimet';
$labels['managefilters'] = 'Muokkaa saapuvan sähköpostin suodattimia';
$labels['filtername'] = 'Suodattimen nimi';
$labels['newfilter'] = 'Uusi suodatin';
$labels['filteradd'] = 'Lisää suodatin';
$labels['filterdel'] = 'Poista suodatin';
$labels['moveup'] = 'Siirrä ylös';
$labels['movedown'] = 'Siirrä alas';
$labels['filterallof'] = 'Täsmää kaikkien sääntöjen mukaan';
$labels['filteranyof'] = 'Täsmää minkä tahansa sääntöjen mukaan';
$labels['filterany'] = 'Kaikki viestit';
$labels['filtercontains'] = 'Sisältää';
$labels['filternotcontains'] = 'Ei Sisällä';
$labels['filteris'] = 'on samanlainen kuin';
$labels['filterisnot'] = 'ei ole samanlainen kuin';
$labels['filterexists'] = 'on olemassa';
$labels['filternotexists'] = 'ei ole olemassa';
$labels['filterunder'] = 'alla';
$labels['filterover'] = 'yli';
$labels['addrule'] = 'Lisää sääntö';
$labels['delrule'] = 'Poista sääntö';
$labels['messagemoveto'] = 'Siirrä viesti';
$labels['messageredirect'] = 'Uudelleen ohjaa viesti';
$labels['messagereply'] = 'Vastaa viestin kanssa';
$labels['messagedelete'] = 'Poista viesti';
$labels['messagediscard'] = 'Hylkää viesti';
$labels['messagesrules'] = 'Saapuva sähköposti';
$labels['messagesactions'] = 'Suorita seuraavat tapahtumat';
$labels['add'] = 'Lisää';
$labels['del'] = 'Poista';
$labels['sender'] = 'Lähettäjä';
$labels['recipient'] = 'Vastaanottaja';
$labels['vacationaddresses'] = 'Lähetä viesti myös seuraaviin osotteisiin (erottele pilkulla):';
$labels['vacationdays'] = 'Kuinka monen päivän välein lähetetään uusi vastaus:';
$labels['vacationreason'] = 'Viesti:';
$labels['rulestop'] = 'Viimeinen sääntö';
$labels['filterset'] = 'Suodatinlista';
$labels['filtersetadd'] = 'Lisää suodatinlista';
$labels['filtersetdel'] = 'Poista valittu suodatinlista';
$labels['filtersetact'] = 'Aktivoi valittu suodatinlista';
$labels['filtersetget'] = 'Lataa valittu suodatinlista tekstimuodossa';
$labels['filterdef'] = 'Suodatinmääritykset';
$labels['filtersetname'] = 'Suodatinlistan nimi';
$labels['newfilterset'] = 'Uusi suodatinlista';
$labels['active'] = 'aktiivinen';
$labels['none'] = 'ei mitään';
$labels['fromset'] = 'listasta';
$labels['fromfile'] = 'tiedostosta';
$labels['filterdisabled'] = 'Suodatin on poistettu käytöstä';
$messages = array();
$messages['filterunknownerror'] = 'Tuntematon palvelin virhe';
$messages['filterconnerror'] = 'Yhdistäminen palvelimeen epäonnistui';
$messages['filterdeleteerror'] = 'Suodattimen poistaminen epäonnistui. Palvelin virhe';
$messages['filterdeleted'] = 'Suodatin poistettu';
$messages['filterdeleteconfirm'] = 'Haluatko varmasti poistaa valitut suodattimet?';
$messages['filtersaved'] = 'Suodatin tallennettu';
$messages['filtersaveerror'] = 'Suodattimen tallennus epäonnistui. Palvelin virhe';
$messages['ruledeleteconfirm'] = 'Haluatko poistaa valitut säännöt?';
$messages['actiondeleteconfirm'] = 'Haluatko poistaa valitut tapahtumat?';
$messages['forbiddenchars'] = 'Sisältää kiellettyjä kirjaimia';
$messages['cannotbeempty'] = 'Kenttä ei voi olla tyhjä';
?>
plugins/managesieve/localization/fr_FR.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels['filters'] = 'Filtres';
$labels['managefilters'] = 'Gestion des filtres sur les mails entrants';
$labels['filtername'] = 'Nom du filtre';
$labels['newfilter'] = 'Nouveau filtre';
$labels['filteradd'] = 'Ajouter un filtre';
$labels['filterdel'] = 'Supprimer un filtre';
$labels['moveup'] = 'Monter';
$labels['movedown'] = 'Descendre';
$labels['filterallof'] = 'valident toutes les conditions suivantes';
$labels['filteranyof'] = 'valident au moins une des conditions suivantes';
$labels['filterany'] = 'tous les messages';
$labels['filtercontains'] = 'contient';
$labels['filternotcontains'] = 'ne contient pas';
$labels['filteris'] = 'est ';
$labels['filterisnot'] = 'n\'est pas';
$labels['filterexists'] = 'existe';
$labels['filternotexists'] = 'n\'existe pas';
$labels['filterunder'] = 'est plus petit que';
$labels['filterover'] = 'est plus grand que';
$labels['addrule'] = 'Ajouter une règle';
$labels['delrule'] = 'Supprimer une règle';
$labels['messagemoveto'] = 'Déplacer le message vers';
$labels['messageredirect'] = 'Transférer le message à';
$labels['messagereply'] = 'Répondre avec le message';
$labels['messagedelete'] = 'Supprimer le message';
$labels['messagediscard'] = 'Rejeter avec le message';
$labels['messagesrules'] = 'Pour les mails entrants:';
$labels['messagesactions'] = '...exécuter les actions suivantes:';
$labels['add'] = 'Ajouter';
$labels['del'] = 'Supprimer';
$labels['sender'] = 'Expéditeur';
$labels['recipient'] = 'Destinataire';
$labels['vacationaddresses'] = 'Liste des destinataires (séparés par une virgule) :';
$labels['vacationdays'] = 'Ne pas renvoyer un message avant (jours) :';
$labels['vacationreason'] = 'Corps du message (raison de l\'absence) :';
$labels['rulestop'] = 'Arrêter d\'évaluer les prochaines règles';
$messages = array();
$messages['filterunknownerror'] = 'Erreur du serveur inconnue';
$messages['filterconnerror'] = 'Connexion au serveur Managesieve impossible';
$messages['filterdeleteerror'] = 'Suppression du filtre impossible. Le serveur à produit une erreur';
$messages['filterdeleted'] = 'Le filtre a bien été supprimé';
$messages['filterdeleteconfirm'] = 'Voulez-vous vraiment supprimer le filtre sélectionné?';
$messages['filtersaved'] = 'Le filtre a bien été enregistré';
$messages['filtersaveerror'] = 'Enregistrement du filtre impossibe. Le serveur à produit une erreur';
$messages['ruledeleteconfirm'] = 'Voulez-vous vraiment supprimer la règle sélectionnée?';
$messages['actiondeleteconfirm'] = 'Voulez-vous vraiment supprimer l\'action sélectionnée?';
$messages['forbiddenchars'] = 'Caractères interdits dans le champ';
$messages['cannotbeempty'] = 'Le champ ne peut pas être vide';
?>
plugins/managesieve/localization/hu_HU.inc
New file
@@ -0,0 +1,54 @@
<?php
$labels = array();
$labels['filters'] = 'Üzenetszűrők';
$labels['managefilters'] = 'Bejövő üzenetek szűrői';
$labels['filtername'] = 'Szűrő neve';
$labels['newfilter'] = 'Új szűrő';
$labels['filteradd'] = 'Szűrő hozzáadása';
$labels['filterdel'] = 'Szűrő törlése';
$labels['moveup'] = 'Mozgatás felfelé';
$labels['movedown'] = 'Mozgatás lefelé';
$labels['filterallof'] = 'A következők mind illeszkedjenek';
$labels['filteranyof'] = 'A következők bármelyike illeszkedjen';
$labels['filterany'] = 'Minden üzenet illeszkedjen';
$labels['filtercontains'] = 'tartalmazza';
$labels['filternotcontains'] = 'nem tartalmazza';
$labels['filteris'] = 'megegyezik';
$labels['filterisnot'] = 'nem egyezik meg';
$labels['filterexists'] = 'létezik';
$labels['filternotexists'] = 'nem létezik';
$labels['filterunder'] = 'alatta';
$labels['filterover'] = 'felette';
$labels['addrule'] = 'Szabály hozzáadása';
$labels['delrule'] = 'Szabály törlése';
$labels['messagemoveto'] = 'Üzenet áthelyezése ide:';
$labels['messageredirect'] = 'Üzenet továbbítása ide:';
$labels['messagereply'] = 'Válaszüzenet küldése (autoreply)';
$labels['messagedelete'] = 'Üzenet törlése';
$labels['messagediscard'] = 'Válaszüzenet küldése, a levél törlése';
$labels['messagesrules'] = 'Az adott tulajdonságú beérkezett üzenetekre:';
$labels['messagesactions'] = '... a következő műveletek végrehajtása:';
$labels['add'] = 'Hozzáadás';
$labels['del'] = 'Törlés';
$labels['sender'] = 'Feladó';
$labels['recipient'] = 'Címzett';
$labels['vacationaddresses'] = 'További címzettek (vesszővel elválasztva):';
$labels['vacationdays'] = 'Válaszüzenet küldése ennyi naponként:';
$labels['vacationreason'] = 'Levél szövege (automatikus válasz):';
$labels['rulestop'] = 'Műveletek végrehajtásának befejezése';
$messages = array();
$messages['filterunknownerror'] = 'Ismeretlen szerverhiba';
$messages['filterconnerror'] = 'Nem tudok a szűrőszerverhez kapcsolódni';
$messages['filterdeleteerror'] = 'A szűrőt nem lehet törölni, szerverhiba történt';
$messages['filterdeleted'] = 'A szűrő törlése sikeres';
$messages['filterdeleteconfirm'] = 'Biztosan törli ezt a szűrőt?';
$messages['filtersaved'] = 'A szűrő mentése sikeres';
$messages['filtersaveerror'] = 'A szűrő mentése sikertelen, szerverhiba történt';
$messages['ruledeleteconfirm'] = 'Biztosan törli ezt a szabályt?';
$messages['actiondeleteconfirm'] = 'Biztosan törli ezt a műveletet?';
$messages['forbiddenchars'] = 'Érvénytelen karakter a mezőben';
$messages['cannotbeempty'] = 'A mező nem lehet üres';
?>
plugins/managesieve/localization/it_IT.inc
New file
@@ -0,0 +1,79 @@
<?php
$labels = array();
$labels['filters'] = 'Filtri';
$labels['managefilters'] = 'Gestione dei filtri per la posta in arrivo';
$labels['filtername'] = 'Nome del filtro';
$labels['newfilter'] = 'Nuovo filtro';
$labels['filteradd'] = 'Aggiungi filtro';
$labels['filterdel'] = 'Elimina filtro';
$labels['moveup'] = 'Sposta sopra';
$labels['movedown'] = 'Sposta sotto';
$labels['filterallof'] = 'che soddisfa tutte le regole seguenti';
$labels['filteranyof'] = 'che soddisfa una qualsiasi delle regole seguenti';
$labels['filterany'] = 'tutti i messaggi';
$labels['filtercontains'] = 'contiene';
$labels['filternotcontains'] = 'non contiene';
$labels['filteris'] = 'è uguale a';
$labels['filterisnot'] = 'è diverso da';
$labels['filterexists'] = 'esiste';
$labels['filternotexists'] = 'non esiste';
$labels['filterunder'] = 'sotto';
$labels['filterover'] = 'sopra';
$labels['addrule'] = 'Aggiungi regola';
$labels['delrule'] = 'Elimina regola';
$labels['messagemoveto'] = 'Sposta il messaggio in';
$labels['messageredirect'] = 'Inoltra il messaggio a';
$labels['messagereply'] = 'Rispondi con il messaggio';
$labels['messagedelete'] = 'Elimina il messaggio';
$labels['messagediscard'] = 'Rifiuta con messaggio';
$labels['messagesrules'] = 'Per la posta in arrivo';
$labels['messagesactions'] = '...esegui le seguenti azioni:';
$labels['add'] = 'Aggiungi';
$labels['del'] = 'Elimina';
$labels['sender'] = 'Mittente';
$labels['recipient'] = 'Destinatario';
$labels['vacationaddresses'] = 'Lista di indirizzi e-mail di destinatari addizionali (separati da virgola):';
$labels['vacationdays'] = 'Ogni quanti giorni ribadire il messaggio allo stesso mittente';
$labels['vacationreason'] = 'Corpo del messaggio (dettagli relativi all\'assenza):';
$labels['rulestop'] = 'Non valutare le regole successive';
$labels['filterset'] = 'Gruppi di filtri';
$labels['filtersetadd'] = 'Aggiungi gruppo';
$labels['filtersetdel'] = 'Cancella gruppo selezionato';
$labels['filtersetact'] = 'Attiva gruppo selezionato';
$labels['filtersetdeact'] = 'Disattiva gruppo selezionato';
$labels['filtersetget'] = 'Scarica filtri come testo';
$labels['filterdef'] = 'Definizione del filtro';
$labels['filtersetname'] = 'Nome del Gruppo di filtri';
$labels['newfilterset'] = 'Nuovo gruppo di filri';
$labels['active'] = 'attivo';
$labels['none'] = 'nessuno';
$labels['fromset'] = 'dal set';
$labels['fromfile'] = 'dal file';
$labels['filterdisabled'] = 'Filtro disabilitato';
$messages = array();
$messages['filterunknownerror'] = 'Errore sconosciuto del server';
$messages['filterconnerror'] = 'Collegamento al server managesieve fallito';
$messages['filterdeleteerror'] = 'Eliminazione del filtro fallita. Si è verificato un errore nel server';
$messages['filterdeleted'] = 'Filtro eliminato con successo';
$messages['filterdeleteconfirm'] = 'Vuoi veramente eliminare il filtro selezionato?';
$messages['filtersaved'] = 'Filtro salvato con successo';
$messages['filtersaveerror'] = 'Salvataggio del filtro fallito. Si è verificato un errore nel server';
$messages['ruledeleteconfirm'] = 'Sei sicuro di voler eliminare la regola selezionata?';
$messages['actiondeleteconfirm'] = 'Sei sicuro di voler eliminare l\'azione selezionata?';
$messages['forbiddenchars'] = 'Caratteri non consentiti nel campo';
$messages['cannotbeempty'] = 'Il campo non può essere vuoto';
$messages['setactivateerror'] = 'Impossibile attivare il filtro. Errore del server';
$messages['setdeactivateerror'] = 'Impossibile disattivare il filtro. Errore del server';
$messages['setdeleteerror'] = 'Impossibile cancellare il filtro. Errore del server';
$messages['setactivated'] = 'Filtro attivato';
$messages['setdeactivated'] = 'Filtro disattivato';
$messages['setdeleted'] = 'Filtro cancellato';
$messages['setdeleteconfirm'] = 'Sei sicuro di voler cancellare il gruppo di filtri';
$messages['setcreateerror'] = 'Impossibile creare il gruppo. Errore del server';
$messages['setcreated'] = 'Gruppo di filtri creato';
$messages['emptyname'] = 'Impossibile creare il gruppo: Inserire un nome';
$messages['nametoolong'] = 'Impossibile creare il gruppo: Nome troppo lungo';
?>
plugins/managesieve/localization/ja_JP.inc
New file
@@ -0,0 +1,82 @@
<?php
//  EN-Revision: 3891
$labels['filters'] = 'フィルター';
$labels['managefilters'] = '受信メールのフィルターの管理';
$labels['filtername'] = 'フィルターの名前';
$labels['newfilter'] = '新規フィルター';
$labels['filteradd'] = 'フィルターの追加';
$labels['filterdel'] = 'フィルターの削除';
$labels['moveup'] = '上に移動';
$labels['movedown'] = '下に移動';
$labels['filterallof'] = '次のルールすべてに一致';
$labels['filteranyof'] = '次のルールのどれかに一致';
$labels['filterany'] = '全メッセージ';
$labels['filtercontains'] = '含む';
$labels['filternotcontains'] = '含まない';
$labels['filteris'] = '次と一致する';
$labels['filterisnot'] = '次と一致しない';
$labels['filterexists'] = '存在する';
$labels['filternotexists'] = '存在しない';
$labels['filterunder'] = 'より上';
$labels['filterover'] = 'より下';
$labels['addrule'] = 'ルールの追加';
$labels['delrule'] = 'ルールの削除';
$labels['messagemoveto'] = '次にメッセージを移動する';
$labels['messageredirect'] = '次のメールアドレスに転送 (リダイレクト)';
$labels['messagecopyto'] = '次にメッセージをコピーする';
$labels['messagesendcopy'] = '次にメッセージのコピーを送信する';
$labels['messagereply'] = 'メッセージに返信する';
$labels['messagedelete'] = 'メッセージを削除する';
$labels['messagediscard'] = 'メッセージを破棄する';
$labels['messagesrules'] = '受信メールへの処理:';
$labels['messagesactions'] = '…次の操作の実行:';
$labels['add'] = '追加';
$labels['del'] = '削除';
$labels['sender'] = '送信者';
$labels['recipient'] = '受信者';
$labels['vacationaddresses'] = '電子メール受信者の一覧を追加する (カンマ区切り):';
$labels['vacationdays'] = 'どれ位頻繁にメッセージの送信をするか (1 日あたり):';
$labels['vacationreason'] = 'メッセージ本文 (vacation reason):';
$labels['rulestop'] = 'ルール評価の停止';
$labels['filterset'] = 'フィルター セット';
$labels['filtersetadd'] = 'フィルター セットの追加';
$labels['filtersetdel'] = '現在のルールセット の削除';
$labels['filtersetact'] = '現在のフィルター セットを有効にする';
$labels['filtersetdeact'] = '現在のフィルター セットを無効にする';
$labels['filtersetget'] = 'テキスト形式でフィルター セットをダウンロードする';
$labels['filterdef'] = 'フィルターの定義';
$labels['filtersetname'] = 'フィルター セットの名前';
$labels['newfilterset'] = '新規フィルター セット';
$labels['active'] = '有効';
$labels['none'] = 'なし';
$labels['fromset'] = 'セットから';
$labels['fromfile'] = 'ファイルから';
$labels['filterdisabled'] = 'フィルターを無効にしました。';
$messages = array();
$messages['filterunknownerror'] = '不明なサーバーのエラーです';
$messages['filterconnerror'] = 'managesieve サーバーに接続できません。';
$messages['filterdeleteerror'] = 'フィルターを削除できませんでした。サーバーでエラーが発生しました。';
$messages['filterdeleted'] = 'フィルターの削除に成功しました。';
$messages['filtersaved'] = 'フィルターの保存に成功しました。';
$messages['filtersaveerror'] = 'フィルターの保存に失敗しました。サーバーでエラーが発生しました。';
$messages['filterdeleteconfirm'] = '本当に選択したフィルターを削除しますか?';
$messages['ruledeleteconfirm'] = '本当に選択したルールを削除しますか?';
$messages['actiondeleteconfirm'] = '本当に選択した操作を削除しますか?';
$messages['forbiddenchars'] = '項目に禁止文字があります。';
$messages['cannotbeempty'] = '空にできませんでした';
$messages['setactivateerror'] = '選択したフィルター セットの有効化に失敗しました。サーバーでエラーが発生しました。';
$messages['setdeactivateerror'] = '選択したフィルター セットの無効化に失敗しました。サーバーでエラーが発生しました。';
$messages['setdeleteerror'] = '選択したフィルター セットの削除に失敗しました。サーバーでエラーが発生しました。';
$messages['setactivated'] = 'フィルターセットの有効化に成功しました。';
$messages['setdeactivated'] = 'フィルターセットの無効化に成功しました。';
$messages['setdeleted'] = 'フィルターセットの削除に成功しました。';
$messages['setdeleteconfirm'] = '本当に選択したフィルター セットを削除しますか?';
$messages['setcreateerror'] = 'フィルター セットの作成に失敗しました。サーバーでエラーが発生しました。';
$messages['setcreated'] = 'フィルター セットの作成に成功しました。';
$messages['emptyname'] = 'フィルター セットの作成に失敗しました。名前が空です。';
$messages['nametoolong'] = 'フィルター セットの作成に失敗しました。名前が長すぎます。';
?>
plugins/managesieve/localization/nb_NO.inc
New file
@@ -0,0 +1,54 @@
<?php
$labels = array();
$labels['filters'] = 'Filtre';
$labels['managefilters'] = 'Rediger filter for innkommende e-post';
$labels['filtername'] = 'Filternavn';
$labels['newfilter'] = 'Nytt filter';
$labels['filteradd'] = 'Legg til filter';
$labels['filterdel'] = 'Slett filter';
$labels['moveup'] = 'Flytt opp';
$labels['movedown'] = 'Flytt ned';
$labels['filterallof'] = 'som treffer alle følgende regler';
$labels['filteranyof'] = 'som treffer en av følgende regler';
$labels['filterany'] = 'og alle meldinger';
$labels['filtercontains'] = 'inneholder';
$labels['filternotcontains'] = 'ikke innehold';
$labels['filteris'] = 'er';
$labels['filterisnot'] = 'ikke er';
$labels['filterexists'] = 'eksisterer';
$labels['filternotexists'] = 'ikke eksisterer';
$labels['filterunder'] = 'under';
$labels['filterover'] = 'over';
$labels['addrule'] = 'Legg til regel';
$labels['delrule'] = 'Slett regel';
$labels['messagemoveto'] = 'Flytt meldingen til';
$labels['messageredirect'] = 'Videresend meldingen til';
$labels['messagereply'] = 'Svar med melding';
$labels['messagedelete'] = 'Slett melding';
$labels['messagediscard'] = 'Avvis med melding';
$labels['messagesrules'] = 'For innkommende e-post';
$labels['messagesactions'] = '...gjør følgende';
$labels['add'] = 'Legg til';
$labels['del'] = 'Slett';
$labels['sender'] = 'Avsender';
$labels['recipient'] = 'Mottaker';
$labels['vacationaddresses'] = 'Liste med mottakeradresser (adskilt med komma):';
$labels['vacationdays'] = 'Periode mellom meldinger (i dager):';
$labels['vacationreason'] = 'Innhold (begrunnelse for fravær)';
$labels['rulestop'] = 'Stopp evaluering av regler';
$messages = array();
$messages['filterunknownerror'] = 'Ukjent problem med tjener';
$messages['filterconnerror'] = 'Kunne ikke koble til MANAGESIEVE-tjener';
$messages['filterdeleteerror'] = 'Kunne ikke slette filter. Det dukket opp en feil på tjeneren.';
$messages['filterdeleted'] = 'Filteret er blitt slettet';
$messages['filterconfirmdelete'] = 'Er du sikker på at du vil slette følgende filter?';
$messages['filtersaved'] = 'Filter er blitt lagret';
$messages['filtersaveerror'] = 'Kunne ikke lagre filteret. Det dukket opp en feil på tjeneren.';
$messages['ruledeleteconfirm'] = 'Er du sikker på at du vil slette valgte regel?';
$messages['actiondeleteconfirm'] = 'Er du sikker på at du vil slette valgte hendelse?';
$messages['forbiddenchars'] = 'Ugyldige tegn i felt';
$messages['cannotbeempty'] = 'Feltet kan ikke stå tomt';
?>
plugins/managesieve/localization/nl_NL.inc
New file
@@ -0,0 +1,49 @@
<?php
$labels['filters'] = 'Filters';
$labels['managefilters'] = 'Beheer inkomende mail filters';
$labels['filtername'] = 'Filternaam';
$labels['newfilter'] = 'Nieuw filter';
$labels['filteradd'] = 'Filter toevoegen';
$labels['filterdel'] = 'Filter verwijderen';
$labels['moveup'] = 'Omhoog';
$labels['movedown'] = 'Omlaag';
$labels['filterallof'] = 'die voldoen aan alle volgende regels';
$labels['filteranyof'] = 'die voldoen aan een van de volgende regels';
$labels['filterany'] = 'alle berichten';
$labels['filtercontains'] = 'bevat';
$labels['filternotcontains'] = 'bevat niet';
$labels['filteris'] = 'is gelijk aan';
$labels['filterisnot'] = 'is niet gelijk aan';
$labels['filterexists'] = 'bestaat';
$labels['filternotexists'] = 'bestaat niet';
$labels['filterunder'] = 'onder';
$labels['filterover'] = 'over';
$labels['addrule'] = 'Regel toevoegen';
$labels['delrule'] = 'Regel verwijderen';
$labels['messagemoveto'] = 'Verplaats bericht naar';
$labels['messageredirect'] = 'Redirect bericht naar';
$labels['messagereply'] = 'Beantwoord met bericht';
$labels['messagedelete'] = 'Verwijder bericht';
$labels['messagediscard'] = 'Wijs af met bericht';
$labels['messagesrules'] = 'Voor binnenkomende e-mail';
$labels['messagesactions'] = '...voer de volgende acties uit';
$labels['add'] = 'Toevoegen';
$labels['del'] = 'Verwijderen';
$labels['sender'] = 'Afzender';
$labels['recipient'] = 'Ontvanger';
$messages = array();
$messages['filterunknownerror'] = 'Onbekende fout';
$messages['filterconnerror'] = 'Kan geen verbinding maken met de managesieve server';
$messages['filterdeleteerror'] = 'Kan filter niet verwijderen. Er is een fout opgetreden';
$messages['filterdeleted'] = 'Filter succesvol verwijderd';
$messages['filterdeleteconfirm'] = 'Weet je zeker dat je het geselecteerde filter wilt verwijderen?';
$messages['filtersaved'] = 'Filter succesvol opgeslagen';
$messages['filtersaveerror'] = 'Kan filter niet opslaan. Er is een fout opgetreden.';
$messages['ruledeleteconfirm'] = 'Weet je zeker dat je de geselecteerde regel wilt verwijderen?';
$messages['actiondeleteconfirm'] = 'Weet je zeker dat je de geselecteerde actie wilt verwijderen?';
$messages['forbiddenchars'] = 'Verboden karakters in het veld';
$messages['cannotbeempty'] = 'Veld mag niet leeg zijn';
?>
plugins/managesieve/localization/pl_PL.inc
New file
@@ -0,0 +1,93 @@
<?php
$labels = array();
$labels['filters'] = 'Filtry';
$labels['managefilters'] = 'Zarządzaj filtrami wiadomości przychodzących';
$labels['filtername'] = 'Nazwa filtru';
$labels['newfilter'] = 'Nowy filtr';
$labels['filteradd'] = 'Dodaj filtr';
$labels['filterdel'] = 'Usuń filtr';
$labels['moveup'] = 'Przenieś wyżej';
$labels['movedown'] = 'Przenieś niżej';
$labels['filterallof'] = 'spełniające wszystkie poniższe kryteria';
$labels['filteranyof'] = 'spełniające dowolne z poniższych kryteriów';
$labels['filterany'] = 'wszystkich';
$labels['filtercontains'] = 'zawiera';
$labels['filternotcontains'] = 'nie zawiera';
$labels['filteris'] = 'jest równe';
$labels['filterisnot'] = 'nie jest równe';
$labels['filterexists'] = 'istnieje';
$labels['filternotexists'] = 'nie istnieje';
$labels['filterunder'] = 'poniżej';
$labels['filterover'] = 'ponad';
$labels['addrule'] = 'Dodaj regułę';
$labels['delrule'] = 'Usuń regułę';
$labels['messagemoveto'] = 'Przenieś wiadomość do';
$labels['messageredirect'] = 'Przekaż wiadomość na konto';
$labels['messagereply'] = 'Odpowiedz wiadomością o treści';
$labels['messagecopyto'] = 'Skopiuj wiadomość do';
$labels['messagesendcopy'] = 'Wyślij kopię do';
$labels['messagedelete'] = 'Usuń wiadomość';
$labels['messagediscard'] = 'Odrzuć z komunikatem';
$labels['messagesrules'] = 'W stosunku do przychodzących wiadomości:';
$labels['messagesactions'] = '...wykonaj następujące czynności:';
$labels['add'] = 'Dodaj';
$labels['del'] = 'Usuń';
$labels['sender'] = 'Nadawca';
$labels['recipient'] = 'Odbiorca';
$labels['rulestop'] = 'Przerwij przetwarzanie reguł';
$labels['vacationdays'] = 'Częstotliwość wysyłania wiadomości (w dniach):';
$labels['vacationaddresses'] = 'Lista dodatkowych adresów odbiorców (oddzielonych przecinkami):';
$labels['vacationreason'] = 'Treść (przyczyna nieobecności):';
$labels['filterset'] = 'Zbiór filtrów';
$labels['filtersetadd'] = 'Dodaj zbiór filtrów';
$labels['filtersetdel'] = 'Usuń bierzący zbiór filtrów';
$labels['filtersetact'] = 'Aktywuj bierzący zbiór filtrów';
$labels['filtersetdeact'] = 'Deaktywuj bierzący zbiór filtrów';
$labels['filtersetget'] = 'Pobierz bierzący zbiór filtrów w formacie tekstowym';
$labels['filterdef'] = 'Definicja filtra';
$labels['filtersetname'] = 'Nazwa zbioru';
$labels['newfilterset'] = 'Nowy zbiór filtrów';
$labels['active'] = 'aktywny';
$labels['none'] = 'brak';
$labels['fromset'] = 'ze zbioru';
$labels['fromfile'] = 'z pliku';
$labels['filterdisabled'] = 'Filtr wyłączony';
$labels['countisgreaterthan'] = 'ilość jest większa od';
$labels['countisgreaterthanequal'] = 'ilość jest równa lub większa od';
$labels['countislessthan'] = 'ilość jest mniejsza od';
$labels['countislessthanequal'] = 'ilość jest równa lub mniejsza od';
$labels['countequals'] = 'ilość jest równa';
$labels['countnotequals'] = 'ilość jest różna od';
$labels['valueisgreaterthan'] = 'wartość jest większa od';
$labels['valueisgreaterthanequal'] = 'wartość jest równa lub większa od';
$labels['valueislessthan'] = 'wartość jest mniejsza od';
$labels['valueislessthanequal'] = 'wartość jest równa lub mniejsza od';
$labels['valueequals'] = 'wartość jest równa';
$labels['valuenotequals'] = 'wartość jest różna od';
$messages = array();
$messages['filterunknownerror'] = 'Nieznany błąd serwera';
$messages['filterconnerror'] = 'Nie można nawiązać połączenia z serwerem managesieve';
$messages['filterdeleteerror'] = 'Nie można usunąć filtra. Wystąpił błąd serwera';
$messages['filterdeleted'] = 'Filtr został usunięty pomyślnie';
$messages['filterdeleteconfirm'] = 'Czy na pewno chcesz usunąć wybrany filtr?';
$messages['filtersaved'] = 'Filtr został zapisany pomyślnie';
$messages['filtersaveerror'] = 'Nie można zapisać filtra. Wystąpił błąd serwera.';
$messages['ruledeleteconfirm'] = 'Czy na pewno chcesz usunąć wybraną regułę?';
$messages['actiondeleteconfirm'] = 'Czy na pewno usunąć wybraną akcję?';
$messages['forbiddenchars'] = 'Pole zawiera niedozwolone znaki';
$messages['cannotbeempty'] = 'Pole nie może być puste';
$messages['setactivateerror'] = 'Nie można aktywować wybranego zbioru filtrów. Błąd serwera';
$messages['setdeactivateerror'] = 'Nie można deaktywować wybranego zbioru filtrów. Błąd serwera';
$messages['setdeleteerror'] = 'Nie można usunąć wybranego zbioru filtrów. Błąd serwera';
$messages['setactivated'] = 'Zbiór filtrów został aktywowany pomyślnie';
$messages['setdeactivated'] = 'Zbiór filtrów został deaktywowany pomyślnie';
$messages['setdeleted'] = 'Zbiór filtrów został usunięty pomyślnie';
$messages['setdeleteconfirm'] = 'Czy na pewno chcesz usunąć wybrany zbiór filtrów?';
$messages['setcreateerror'] = 'Nie można utworzyć zbioru filtrów. Błąd serwera';
$messages['setcreated'] = 'Zbiór filtrów został utworzony pomyślnie';
$messages['emptyname'] = 'Nie można utworzyć zbioru filtrów. Pusta nazwa zbioru';
$messages['nametoolong'] = 'Nie można utworzyć zbioru filtrów. Nazwa zbyt długa'
?>
plugins/managesieve/localization/pt_BR.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels['filters'] = 'Filtros';
$labels['managefilters'] = 'Gerenciar filtros de entrada de e-mail';
$labels['filtername'] = 'Nome do filtro';
$labels['newfilter'] = 'Novo filtro';
$labels['filteradd'] = 'Adicionar filtro';
$labels['filterdel'] = 'Excluir filtro';
$labels['moveup'] = 'Mover para cima';
$labels['movedown'] = 'Mover para baixo';
$labels['filterallof'] = 'casando todas as seguintes regras';
$labels['filteranyof'] = 'casando qualquer das seguintes regras';
$labels['filterany'] = 'todas as mensagens';
$labels['filtercontains'] = 'contem';
$labels['filternotcontains'] = 'não contem';
$labels['filteris'] = 'é igual a';
$labels['filterisnot'] = 'não é igual a';
$labels['filterexists'] = 'existe';
$labels['filternotexists'] = 'não existe';
$labels['filterunder'] = 'inferior a';
$labels['filterover'] = 'superior a';
$labels['addrule'] = 'Adicionar regra';
$labels['delrule'] = 'Excluir regra';
$labels['messagemoveto'] = 'Mover mensagem para';
$labels['messageredirect'] = 'Redirecionar mensagem para';
$labels['messagereply'] = 'Responder com mensagem';
$labels['messagedelete'] = 'Excluir mensagem';
$labels['messagediscard'] = 'Descartar com mensagem';
$labels['messagesrules'] = 'Para e-mails recebidos:';
$labels['messagesactions'] = '...execute as seguintes ações:';
$labels['add'] = 'Adicionar';
$labels['del'] = 'Excluir';
$labels['sender'] = 'Remetente';
$labels['recipient'] = 'Destinatário';
$labels['vacationaddresses'] = 'Lista adicional de e-mails de remetente (separado por vírgula):';
$labels['vacationdays'] = 'Enviar mensagens com que frequência (em dias):';
$labels['vacationreason'] = 'Corpo da mensagem (motivo de férias):';
$labels['rulestop'] = 'Parar de avaliar regras';
$messages = array();
$messages['filterunknownerror'] = 'Erro desconhecido de servidor';
$messages['filterconnerror'] = 'Não foi possível conectar ao servidor managesieve';
$messages['filterdeleteerror'] = 'Não foi possível excluir filtro. Occorreu um erro de servidor';
$messages['filterdeleted'] = 'Filtro excluído com sucesso';
$messages['filterdeleteconfirm'] = 'Deseja realmente excluir o filtro selecionado?';
$messages['filtersaved'] = 'Filtro gravado com sucesso';
$messages['filtersaveerror'] = 'Não foi possível gravar filtro. Occoreu um erro de servidor.';
$messages['ruledeleteconfirm'] = 'Deseja realmente excluir a regra selecionada?';
$messages['actiondeleteconfirm'] = 'Deseja realmente excluir a ação selecionada?';
$messages['forbiddenchars'] = 'Caracteres não permitidos no campo';
$messages['cannotbeempty'] = 'Campo não pode ficar em branco';
?>
plugins/managesieve/localization/pt_PT.inc
New file
@@ -0,0 +1,80 @@
<?php
$labels['filters'] = 'Filtros';
$labels['managefilters'] = 'Gerir filtros de recepção de mails';
$labels['filtername'] = 'Nome do filtro';
$labels['newfilter'] = 'Novo filtro';
$labels['filteradd'] = 'Adicionar filtro';
$labels['filterdel'] = 'Eliminar filtro';
$labels['moveup'] = 'Mover para cima';
$labels['movedown'] = 'Mover para baixo';
$labels['filterallof'] = 'corresponder a todas as seguintes regras';
$labels['filteranyof'] = 'corresponder a qualquer das seguintes regras';
$labels['filterany'] = 'todas as mensagens';
$labels['filtercontains'] = 'contém';
$labels['filternotcontains'] = 'não contém';
$labels['filteris'] = 'é igual a';
$labels['filterisnot'] = 'não é igual a';
$labels['filterexists'] = 'existe';
$labels['filternotexists'] = 'não existe';
$labels['filterunder'] = 'inferior a';
$labels['filterover'] = 'superior a';
$labels['addrule'] = 'Adicionar regra';
$labels['delrule'] = 'Eliminar regra';
$labels['messagemoveto'] = 'Mover mensagem para';
$labels['messageredirect'] = 'Redireccionar mensagem para';
$labels['messagecopyto'] = 'Copiar mensagem para';
$labels['messagesendcopy'] = 'Enviar cópia da mensagem para';
$labels['messagereply'] = 'Responder com mensagem';
$labels['messagedelete'] = 'Eliminar mensagem';
$labels['messagediscard'] = 'Descartar com mensagem';
$labels['messagesrules'] = 'Para mensagens recebidas:';
$labels['messagesactions'] = '...executar as seguintes ações:';
$labels['add'] = 'Adicionar';
$labels['del'] = 'Eliminar';
$labels['sender'] = 'Remetente';
$labels['recipient'] = 'Destinatário';
$labels['vacationaddresses'] = 'Lista complementar de destinatário de e-mails (separado por vírgula):';
$labels['vacationdays'] = 'Enviar mensagens com que frequência (em dias):';
$labels['vacationreason'] = 'Corpo da mensagem (motivo de férias):';
$labels['rulestop'] = 'Parar execução de regras';
$labels['filterset'] = 'Conjunto de filtros';
$labels['filtersetadd'] = 'Adicionar conjunto de filtros';
$labels['filtersetdel'] = 'Eliminar conjunto de filtros actual';
$labels['filtersetact'] = 'Activar conjunto de filtros actual';
$labels['filtersetdeact'] = 'Desactivar conjunto de filtros actual';
$labels['filtersetget'] = 'Importar conjunto de filtros no formato de texto';
$labels['filterdef'] = 'Definições para filtros';
$labels['filtersetname'] = 'Nome conjunto de filtros';
$labels['newfilterset'] = 'Novo conjunto de filtros';
$labels['active'] = 'activo';
$labels['none'] = 'nenhum';
$labels['fromset'] = 'do conjunto';
$labels['fromfile'] = 'do ficheiro';
$labels['filterdisabled'] = 'Filtro inactivo';
$messages = array();
$messages['filterunknownerror'] = 'Ocorreu um erro desconhecido no servidor.';
$messages['filterconnerror'] = 'Não foi possível ligar ao servidor ManageSieve.';
$messages['filterdeleteerror'] = 'Não foi possível eliminar o filtro. Ocorreu um erro no servidor.';
$messages['filterdeleted'] = 'Filtro eliminado com sucesso.';
$messages['filterdeleteconfirm'] = 'Deseja realmente eliminar o filtro seleccionado?';
$messages['filtersaved'] = 'Filtro guardado com sucesso.';
$messages['filtersaveerror'] = 'Não foi possível guardar o filtro. Occoreu um erro no servidor.';
$messages['ruledeleteconfirm'] = 'Deseja realmente eliminar a regra seleccionada?';
$messages['actiondeleteconfirm'] = 'Deseja realmente eliminar a acção seleccionada?';
$messages['forbiddenchars'] = 'Caracteres não permitidos no campo';
$messages['cannotbeempty'] = 'Campo não pode ficar em branco';
$messages['setactivateerror'] = 'Não foi possível activar os filtros seleccionados. Occoreu um erro no servidor.';
$messages['setdeactivateerror'] = 'Não foi possível desactivar os filtros seleccionados. Occoreu um erro no servidor.';
$messages['setdeleteerror'] = 'Não foi possível eliminar os filtros seleccionados. Occoreu um erro no servidor.';
$messages['setactivated'] = 'Filtros activados com sucesso.';
$messages['setdeactivated'] = 'Filtros desactivados com sucesso.';
$messages['setdeleted'] = 'Filtros eliminados com sucesso.';
$messages['setdeleteconfirm'] = 'Tem a certeza que quer eliminar os filtros seleccionados?';
$messages['setcreateerror'] = 'Não foi possível criar o filtro. Ocorreu um erro no servidor.';
$messages['setcreated'] = 'Filtros criados com sucesso';
$messages['emptyname'] = 'Não foi possível criar o filtro. Tem de indicar um nome para o filtro.';
$messages['nametoolong'] = 'Não foi possível criar o filtro. O nome do filtro é demasiado grande.';
?>
plugins/managesieve/localization/ru_RU.inc
New file
@@ -0,0 +1,74 @@
<?php
$labels['filters'] = 'Фильтры';
$labels['managefilters'] = 'Управление фильтрами для входящей почты';
$labels['filtername'] = 'Название фильтра';
$labels['newfilter'] = 'Новый фильтр';
$labels['filteradd'] = 'Добавить фильтр';
$labels['filterdel'] = 'Удалить фильтр';
$labels['moveup'] = 'Сдвинуть вверх';
$labels['movedown'] = 'Сдвинуть вниз';
$labels['filterallof'] = 'соответствует всем указанным правилам';
$labels['filteranyof'] = 'соответствует любому из указанных правил';
$labels['filterany'] = 'все сообщения';
$labels['filtercontains'] = 'содержит';
$labels['filternotcontains'] = 'не содержит';
$labels['filteris'] = 'соответствует';
$labels['filterisnot'] = 'не соответствует';
$labels['filterexists'] = 'существует';
$labels['filternotexists'] = 'не существует';
$labels['filterunder'] = 'под';
$labels['filterover'] = 'на';
$labels['addrule'] = 'Добавить правило';
$labels['delrule'] = 'Удалить правило';
$labels['messagemoveto'] = 'Переместить сообщение в';
$labels['messageredirect'] = 'Перенаправить сообщение на ';
$labels['messagereply'] = 'Ответить с сообщением';
$labels['messagedelete'] = 'Удалить сообщение';
$labels['messagediscard'] = 'Отбросить с сообщением';
$labels['messagesrules'] = 'Для входящей почты:';
$labels['messagesactions'] = '...выполнить следующие действия:';
$labels['add'] = 'Добавить';
$labels['del'] = 'Удалить';
$labels['sender'] = 'Отправитель';
$labels['recipient'] = 'Получатель';
$labels['vacationaddresses'] = 'Список дополнительных адресов получателя (разделённых запятыми):';
$labels['vacationdays'] = 'Как часто отправлять сообщения (в днях):';
$labels['vacationreason'] = 'Текст сообщения (причина отсутствия):';
$labels['rulestop'] = 'Закончить выполнение';
$labels['filterset'] = 'Набор фильтров';
$labels['filtersetadd'] = 'Добавить набор фильтров';
$labels['filtersetdel'] = 'Удалить текущий набор фильтров';
$labels['filtersetact'] = 'Активировать текущий набор фильтров';
$labels['filtersetget'] = 'Скачать набор фильтров в виде текста';
$labels['filterdef'] = 'Описание фильтра';
$labels['filtersetname'] = 'Название набора фильтров';
$labels['newfilterset'] = 'Новый набор фильтров';
$labels['active'] = 'активный';
$labels['none'] = 'пустой';
$labels['fromset'] = 'из набора';
$labels['fromfile'] = 'из файла';
$labels['filterdisabled'] = 'Отключить фильтр';
$messages = array();
$messages['filterunknownerror'] = 'Неизвестная ошибка сервера';
$messages['filterconnerror'] = 'Невозможно подсоединится к серверу фильтров';
$messages['filterdeleteerror'] = 'Невозможно удалить фильтр. Ошибка сервера';
$messages['filterdeleted'] = 'Фильтр успешно удалён';
$messages['filterdeleteconfirm'] = 'Вы действительно хотите удалить фильтр?';
$messages['filtersaved'] = 'Фильтр успешно сохранён';
$messages['filtersaveerror'] = 'Невозможно сохранить фильтр. Ошибка сервера';
$messages['ruledeleteconfirm'] = 'Вы уверенны, что хотите удалить это правило?';
$messages['actiondeleteconfirm'] = 'Вы уверенны, что хотите удалить это действие?';
$messages['forbiddenchars'] = 'Недопустимые символы в поле';
$messages['cannotbeempty'] = 'Поле не может быть пустым';
$messages['setactivateerror'] = 'Невозможно активировать выбранный набор фильтров. Ошибка сервера';
$messages['setdeleteerror'] = 'Невозможно удалить выбранный набор фильтров. Ошибка сервера';
$messages['setactivated'] = 'Набор фильтров успешно активирован';
$messages['setdeleted'] = 'Набор фильтров успешно удалён';
$messages['setdeleteconfirm'] = 'Вы уверены в том, что хотите удалить выбранный набор фильтров?';
$messages['setcreateerror'] = 'Невозможно создать набор фильтров. Ошибка сервера';
$messages['setcreated'] = 'Набор фильтров успешно создан';
$messages['emptyname'] = 'Невозможно создать набор фильтров. Название не задано';
$messages['nametoolong'] = 'Невозможно создать набор фильтров. Название слишком длинное'
?>
plugins/managesieve/localization/sk_SK.inc
New file
@@ -0,0 +1,85 @@
<?php
/*
 * Slovak translation for roundcube/managesieve plugin
 *
 * @version 1.0
 * (c) 27.06.2010 by Juraj LUTTER <juraj@lutter.sk>
 */
$labels['filters'] = 'Filtre';
$labels['managefilters'] = 'Správa filtrov príchádzajúcej pošty';
$labels['filtername'] = 'Názov filtra';
$labels['newfilter'] = 'Nový filter';
$labels['filteradd'] = 'Pridaj filter';
$labels['filterdel'] = 'Zmaž filter';
$labels['moveup'] = 'Presuň vyššie';
$labels['movedown'] = 'Presuň nižšie';
$labels['filterallof'] = 'vyhovujúcu VŠETKÝM nasledujúcim pravidlám';
$labels['filteranyof'] = 'vyhovujúcu ĽUBOVOĽNÉMU z nasledujúcich pravidiel';
$labels['filterany'] = 'všetky správy';
$labels['filtercontains'] = 'obsahuje';
$labels['filternotcontains'] = 'neobsahuje';
$labels['filteris'] = 'je';
$labels['filterisnot'] = 'nie je';
$labels['filterexists'] = 'existuje';
$labels['filternotexists'] = 'neexistuje';
$labels['filterunder'] = 'pod';
$labels['filterover'] = 'nad';
$labels['addrule'] = 'Pridaj pravidlo';
$labels['delrule'] = 'Zmaž pravidlo';
$labels['messagemoveto'] = 'Presuň správu do';
$labels['messageredirect'] = 'Presmeruj správu na';
$labels['messagereply'] = 'Pošli automatickú odpoveď';
$labels['messagedelete'] = 'Zmaž správu';
$labels['messagediscard'] = 'Zmaž a pošli správu na';
$labels['messagesrules'] = 'Pre prichádzajúcu poštu';
$labels['messagesactions'] = 'vykonaj nasledovné akcie';
$labels['add'] = 'Pridaj';
$labels['del'] = 'Zmaž';
$labels['sender'] = 'Odosielateľ';
$labels['recipient'] = 'Adresát';
$labels['vacationaddresses'] = 'Dodatoční príjemcovia správy (oddelení čiarkami):';
$labels['vacationdays'] = 'Počet dní medzi odoslaním správy:';
$labels['vacationreason'] = 'Dôvod neprítomnosti:';
$labels['rulestop'] = 'Koniec pravidiel';
$labels['filterset'] = 'Sada filtrov';
$labels['filtersetadd'] = 'Pridaj sadu filtrov';
$labels['filtersetdel'] = 'Zmaž túto sadu filtrov';
$labels['filtersetact'] = 'Aktivuj túto sadu filtrov';
$labels['filtersetdeact'] = 'Deaktivuj túto sadu filtrov';
$labels['filtersetget'] = 'Stiahni definíciu filtrov v textovom súbore';
$labels['filterdef'] = 'Definícia filtra';
$labels['filtersetname'] = 'Názov sady filtrov';
$labels['newfilterset'] = 'Nová sada filtrov';
$labels['active'] = 'aktívna';
$labels['none'] = 'žiadne';
$labels['fromset'] = 'zo sady';
$labels['fromfile'] = 'zo súboru';
$labels['filterdisabled'] = 'Filter zakázaný';
$messages = array();
$messages['filterunknownerror'] = 'Neznáma chyba serveru';
$messages['filterconnerror'] = 'Nepodarilo sa pripojiť k managesieve serveru';
$messages['filterdeleteerror'] = 'Nepodarilo sa zmazať filter, server ohlásil chybu';
$messages['filterdeleted'] = 'Filter bol zmazaný';
$messages['filtersaved'] = 'Filter bol uložený';
$messages['filtersaveerror'] = 'Nepodarilo sa uložiť filter, server ohlásil chybu';
$messages['filterdeleteconfirm'] = 'Naozaj si prajete zmazať tento filter?';
$messages['ruledeleteconfirm'] = 'Naozaj si prajete zamzať toto pravidlo?';
$messages['actiondeleteconfirm'] = 'Naozaj si prajete zmazať túto akciu?';
$messages['forbiddenchars'] = 'Pole obsahuje nepovolené znaky';
$messages['cannotbeempty'] = 'Pole nemôže byť prázdne';
$messages['setactivateerror'] = 'Nepodarilo sa aktivovať zvolenú sadu filtrov, server ohlásil chybu';
$messages['setdeactivateerror'] = 'Nepodarilo sa deaktivovať zvolenú sadu filtrov, server ohlásil chybu';
$messages['setdeleteerror'] = 'Nepodarilo sa zmazať zvolenú sadu filtrov, server ohlásil chybu';
$messages['setactivated'] = 'Sada filtrov bola aktivovaná';
$messages['setdeactivated'] = 'Sada filtrov bola deaktivovaná';
$messages['setdeleted'] = 'Sada filtrov bola zmazaná';
$messages['setdeleteconfirm'] = 'Naozaj si prajete zmazať túto sadu filtrov?';
$messages['setcreateerror'] = 'Nepodarilo sa vytvoriť sadu filtrov, server ohlásil chybu';
$messages['setcreated'] = 'Sada filtrov bola vytvorená';
$messages['emptyname'] = 'Názov sady filtrov nemôže byť prázdny';
$messages['nametoolong'] = 'Názov sady filtrov je príliš dlhý'
?>
plugins/managesieve/localization/sl_SI.inc
New file
@@ -0,0 +1,53 @@
<?php
$labels['filters'] = 'Pravila';
$labels['managefilters'] = 'Uredi sporočilna pravila';
$labels['filtername'] = 'Ime pravila';
$labels['newfilter'] = 'Novo pravilo';
$labels['filteradd'] = 'Dodaj pravilo';
$labels['filterdel'] = 'Izbriši pravilo';
$labels['moveup'] = 'Pomakni se više';
$labels['movedown'] = 'Pomakni se niže';
$labels['filterallof'] = 'izpolnjeni morajo biti vsi pogoji';
$labels['filteranyof'] = 'izpolnjen mora biti vsaj eden od navedenih pogojev';
$labels['filterany'] = 'pogoj velja za vsa sporočila';
$labels['filtercontains'] = 'vsebuje';
$labels['filternotcontains'] = 'ne vsebuje';
$labels['filteris'] = 'je enak/a';
$labels['filterisnot'] = 'ni enak/a';
$labels['filterexists'] = 'obstaja';
$labels['filternotexists'] = 'ne obstaja';
$labels['filterunder'] = 'pod';
$labels['filterover'] = 'nad';
$labels['addrule'] = 'Dodaj pravilo';
$labels['delrule'] = 'Izbriši pravilo';
$labels['messagemoveto'] = 'Premakni sporočilo v';
$labels['messageredirect'] = 'Preusmeri sporočilo v';
$labels['messagereply'] = 'Odgovori s sporočilom';
$labels['messagedelete'] = 'Izbriši sporočilo';
$labels['messagediscard'] = 'Zavrži s sporočilom';
$labels['messagesrules'] = 'Določi pravila za dohodno pošto:';
$labels['messagesactions'] = '...izvrši naslednja dejanja:';
$labels['add'] = 'Dodaj';
$labels['del'] = 'Izbriši';
$labels['sender'] = 'Pošiljatelj';
$labels['recipient'] = 'Prejemnik';
$labels['vacationaddresses'] = 'Dodaten seznam naslovov prejemnikov (ločenih z vejico):';
$labels['vacationdays'] = 'Kako pogosto naj bodo sporočila poslana (v dnevih):';
$labels['vacationreason'] = 'Vsebina sporočila (vzrok za odsotnost):';
$labels['rulestop'] = 'Prekini z izvajanjem pravil';
$messages = array();
$messages['filterunknownerror'] = 'Prišlo je do neznane napake.';
$messages['filterconnerror'] = 'Povezave s strežnikom (managesieve) ni bilo mogoče vzpostaviti';
$messages['filterdeleteerror'] = 'Pravila ni bilo mogoče izbrisati. Prišlo je do napake.';
$messages['filterdeleted'] = 'Pravilo je bilo uspešno izbrisano.';
$messages['filterdeleteconfirm'] = 'Ste prepričani, da želite izbrisati izbrano pravilo?';
$messages['filtersaved'] = 'Pravilo je bilo uspešno shranjeno';
$messages['filtersaveerror'] = 'Pravilo ni bilo shranjeno. Prišlo je do napake.';
$messages['ruledeleteconfirm'] = 'Ste prepričani, da želite izbrisati izbrano pravilo?';
$messages['actiondeleteconfirm'] = 'Ste prepričani, da želite izbrisati izbrano dejanje?';
$messages['forbiddenchars'] = 'V polju so neveljavni znaki';
$messages['cannotbeempty'] = 'Polje ne sme biti prazno';
?>
plugins/managesieve/localization/sv_SE.inc
New file
@@ -0,0 +1,54 @@
<?php
$labels = array();
$labels['filters'] = 'Filter';
$labels['managefilters'] = 'Administrera filter';
$labels['filtername'] = 'Filternamn';
$labels['newfilter'] = 'Nytt filter';
$labels['filteradd'] = 'Lägg till filter';
$labels['filterdel'] = 'Ta bort filter';
$labels['moveup'] = 'Flytta upp filter';
$labels['movedown'] = 'Flytta ner filter';
$labels['filterallof'] = 'Filtrera på alla följande regler';
$labels['filteranyof'] = 'Filtrera på någon av följande regler';
$labels['filterany'] = 'Filtrera alla meddelanden';
$labels['filtercontains'] = 'innehåller';
$labels['filternotcontains'] = 'inte innehåller';
$labels['filteris'] = 'är lika med';
$labels['filterisnot'] = 'är inte lika med';
$labels['filterexists'] = 'finns';
$labels['filternotexists'] = 'inte finns';
$labels['filterunder'] = 'under';
$labels['filterover'] = 'över';
$labels['addrule'] = 'Lägg till regel';
$labels['delrule'] = 'Ta bort regel';
$labels['messagemoveto'] = 'Flytta meddelande till';
$labels['messageredirect'] = 'Ändra mottagare till';
$labels['messagereply'] = 'Besvara meddelande';
$labels['messagedelete'] = 'Ta bort meddelande';
$labels['messagediscard'] = 'Avböj med felmeddelande';
$labels['messagesrules'] = 'För inkommande meddelande';
$labels['messagesactions'] = 'Utför följande åtgärd';
$labels['add'] = 'Lägg till';
$labels['del'] = 'Ta bort';
$labels['sender'] = 'Avsändare';
$labels['recipient'] = 'Mottagare';
$labels['vacationaddresses'] = 'Ytterligare mottagaradresser (avdelade med kommatecken)';
$labels['vacationdays'] = 'Antal dagar mellan auto-svar';
$labels['vacationreason'] = 'Meddelande i auto-svar';
$labels['rulestop'] = 'Avsluta filtrering';
$messages = array();
$messages['filterunknownerror'] = 'Okänt serverfel';
$messages['filterconnerror'] = 'Anslutning till serverns filtertjänst misslyckades';
$messages['filterdeleteerror'] = 'Filtret kunde inte tas bort på grund av serverfel';
$messages['filterdeleted'] = 'Filtret är borttaget';
$messages['filterdeleteconfirm'] = 'Vill du ta bort det markerade filtret?';
$messages['filtersaved'] = 'Filtret har sparats';
$messages['filtersaveerror'] = 'Filtret kunde inte sparas på grund av serverfel';
$messages['ruledeleteconfirm'] = 'Vill du ta bort filterregeln?';
$messages['actiondeleteconfirm'] = 'Vill du ta bort filteråtgärden?';
$messages['forbiddenchars'] = 'Otillåtet tecken i fältet';
$messages['cannotbeempty'] = 'Fältet kan inte lämnas tomt';
?>
plugins/managesieve/localization/uk_UA.inc
New file
@@ -0,0 +1,76 @@
<?php
$labels = array();
$labels['filters'] = 'Фільтри';
$labels['managefilters'] = 'Керування фільтрами вхідної пошти';
$labels['filtername'] = 'Назва фільтру';
$labels['newfilter'] = 'Новий фільтр';
$labels['filteradd'] = 'Додати фільтр';
$labels['filterdel'] = 'Видалити фільтр';
$labels['moveup'] = 'Пересунути вгору';
$labels['movedown'] = 'Пересунути вниз';
$labels['filterallof'] = 'задовольняє усім наступним умовам';
$labels['filteranyof'] = 'задовольняє будь-якій з умов';
$labels['filterany'] = 'всі повідомлення';
$labels['filtercontains'] = 'містить';
$labels['filternotcontains'] = 'не містить';
$labels['filteris'] = 'ідентичний до';
$labels['filterisnot'] = 'не ідентичний до';
$labels['filterexists'] = 'існує';
$labels['filternotexists'] = 'не існує';
$labels['filterunder'] = 'менше, ніж';
$labels['filterover'] = 'більше, ніж';
$labels['addrule'] = 'Додати правило';
$labels['delrule'] = 'Видалити правило';
$labels['messagemoveto'] = 'Пересунути повідомлення до';
$labels['messageredirect'] = 'Перенаправити повідомлення до';
$labels['messagereply'] = 'Автовідповідач';
$labels['messagedelete'] = 'Видалити повідомлення';
$labels['messagediscard'] = 'Відхилити з повідомленням';
$labels['messagesrules'] = 'Для вхідної пошти';
$labels['messagesactions'] = '... виконати дію:';
$labels['add'] = 'Додати';
$labels['del'] = 'Видалити';
$labels['sender'] = 'Відправник';
$labels['recipient'] = 'Отримувач';
$labels['vacationaddresses'] = 'Додатковий список адрес отримувачів (розділених комою)';
$labels['vacationdays'] = 'Як часто повторювати (у днях):';
$labels['vacationreason'] = 'Текст повідомлення:';
$labels['rulestop'] = 'Зупинити перевірку правил';
$labels['filterset'] = 'Набір фільтрів';
$labels['filtersetadd'] = 'Додати набір фільтрів';
$labels['filtersetdel'] = 'Видалити поточний набір';
$labels['filtersetact'] = 'Активувати поточний набір';
$labels['filtersetget'] = 'Зберегти набір у текстовому форматі';
$labels['filterdef'] = 'Параметри фільтру';
$labels['filtersetname'] = 'Назва набору фільтрів';
$labels['newfilterset'] = 'Новий набір фільтрів';
$labels['active'] = 'активний';
$labels['none'] = 'нічого';
$labels['fromset'] = 'з набору';
$labels['fromfile'] = 'з файлу';
$labels['filterdisabled'] = 'Фільтр вимкнено';
$messages = array();
$messages['filterunknownerror'] = 'Невідома помилка сервера';
$messages['filterconnerror'] = 'Неможливо з\'єднатися з сервером';
$messages['filterdeleteerror'] = 'Неможливо видалити фільтр. Помилка сервера';
$messages['filterdeleted'] = 'Фільтр успішно видалено';
$messages['filtersaved'] = 'Фільтр успішно збережено';
$messages['filtersaveerror'] = 'Неможливо зберегти фільтр. Помилка сервера';
$messages['filterdeleteconfirm'] = 'Ви дійсно хочете видалити обраний фільтр?';
$messages['ruledeleteconfirm'] = 'Ви дійсно хочете видалити обране правило?';
$messages['actiondeleteconfirm'] = 'Ви дійсно хочете видалити обрану дію?';
$messages['forbiddenchars'] = 'Введено заборонений символ';
$messages['cannotbeempty'] = 'Поле не може бути пустим';
$messages['setactivateerror'] = 'Неможливо активувати обраний набір. Помилка сервера';
$messages['setdeleteerror'] = 'Неможливо видалити обраний набір. Помилка сервера';
$messages['setactivated'] = 'Набір фільтрів активовано успішно';
$messages['setdeleted'] = 'Набір фільтрів видалено успішно';
$messages['setdeleteconfirm'] = 'Ви впевнені, що хочете видалити обраний набір?';
$messages['setcreateerror'] = 'Не вдалося створити набір. Помилка сервера';
$messages['setcreated'] = 'Набір фільтрів створено успішно';
$messages['emptyname'] = 'Не вдалося створити набір. Введіть назву набору';
$messages['nametoolong'] = 'Не вдалося створити набір. Занадто довга назва'
?>
plugins/managesieve/localization/zh_CN.inc
New file
@@ -0,0 +1,49 @@
<?php
$labels['filters'] = '过滤器';
$labels['managefilters'] = '管理邮件过滤器';
$labels['filtername'] = '过滤器名称';
$labels['newfilter'] = '新建过滤器';
$labels['filteradd'] = '添加过滤器';
$labels['filterdel'] = '删除过滤器';
$labels['moveup'] = '上移';
$labels['movedown'] = '下移';
$labels['filterallof'] = '匹配所有规则';
$labels['filteranyof'] = '匹配任意一条规则';
$labels['filterany'] = '所有邮件';
$labels['filtercontains'] = '包含';
$labels['filternotcontains'] = '不包含';
$labels['filteris'] = '等于';
$labels['filterisnot'] = '不等于';
$labels['filterexists'] = '存在';
$labels['filternotexists'] = '不存在';
$labels['filterunder'] = '小于';
$labels['filterover'] = '大于';
$labels['addrule'] = '添加规则';
$labels['delrule'] = '删除规则';
$labels['messagemoveto'] = '将邮件移动到';
$labels['messageredirect'] = '将邮件转发到';
$labels['messagereply'] = '回复以下信息';
$labels['messagedelete'] = '删除邮件';
$labels['messagediscard'] = '丢弃邮件并回复以下信息';
$labels['messagesrules'] = '对收取的邮件应用规则:';
$labels['messagesactions'] = '...执行以下动作:';
$labels['add'] = '添加';
$labels['del'] = '删除';
$labels['sender'] = '发件人';
$labels['recipient'] = '收件人';
$messages = array();
$messages['filterunknownerror'] = '未知的服务器错误';
$messages['filterconnerror'] = '无法连接到 managesieve 服务器';
$messages['filterdeleteerror'] = '无法删除过滤器。服务器错误';
$messages['filterdeleted'] = '过滤器已成功删除';
$messages['filterdeleteconfirm'] = '您确定要删除所选择的过滤器吗?';
$messages['filtersaved'] = '过滤器已成功保存。';
$messages['filtersaveerror'] = '无法保存过滤器。服务器错误';
$messages['ruledeleteconfirm'] = '您确定要删除所选择的规则吗?';
$messages['actiondeleteconfirm'] = '您确定要删除所选择的动作吗?';
$messages['forbiddenchars'] = '内容中包含禁用的字符';
$messages['cannotbeempty'] = '内容不能为空';
?>
plugins/managesieve/localization/zh_TW.inc
New file
@@ -0,0 +1,78 @@
<?php
$labels['filters'] = '篩選器';
$labels['managefilters'] = '設定篩選器';
$labels['filtername'] = '篩選器名稱';
$labels['newfilter'] = '建立新篩選器';
$labels['filteradd'] = '增加篩選器';
$labels['filterdel'] = '刪除篩選器';
$labels['moveup'] = '上移';
$labels['movedown'] = '下移';
$labels['filterallof'] = '符合所有規則';
$labels['filteranyof'] = '符合任一條規則';
$labels['filterany'] = '所有信件';
$labels['filtercontains'] = '包含';
$labels['filternotcontains'] = '不包含';
$labels['filteris'] = '等於';
$labels['filterisnot'] = '不等於';
$labels['filterexists'] = '存在';
$labels['filternotexists'] = '不存在';
$labels['filterunder'] = '小於';
$labels['filterover'] = '大於';
$labels['addrule'] = '新增規則';
$labels['delrule'] = '刪除規則';
$labels['messagemoveto'] = '將信件移至';
$labels['messageredirect'] = '將信件轉寄至';
$labels['messagereply'] = '以下列內容回覆';
$labels['messagedelete'] = '刪除信件';
$labels['messagediscard'] = '刪除信件並以下列內容回覆';
$labels['messagesrules'] = '對新收到的信件:';
$labels['messagesactions'] = '執行下列動作:';
$labels['add'] = '新增';
$labels['del'] = '刪除';
$labels['sender'] = '寄件者';
$labels['recipient'] = '收件者';
$labels['vacationaddresses'] = '其他收件者(用半形逗號隔開):';
$labels['vacationdays'] = '多久回覆一次(單位:天):';
$labels['vacationreason'] = '信件內容(休假原因):';
$labels['rulestop'] = '停止評估規則';
$labels['filterset'] = '篩選器集合';
$labels['filtersetadd'] = '加入篩選器集合';
$labels['filtersetdel'] = '刪除目前的篩選器集合';
$labels['filtersetact'] = '啟用目前的篩選器集合';
$labels['filtersetdeact'] = '停用目前的篩選器集合';
$labels['filtersetget'] = '以純文字下載篩選器集合';
$labels['filterdef'] = '篩選器定義';
$labels['filtersetname'] = '篩選器集合名稱';
$labels['newfilterset'] = '建立篩選器集合';
$labels['active'] = '啟用';
$labels['none'] = '無';
$labels['fromset'] = '從集合';
$labels['fromfile'] = '重檔案';
$labels['filterdisabled'] = '篩選器已停用';
$messages = array();
$messages['filterunknownerror'] = '未知的伺服器錯誤';
$messages['filterconnerror'] = '無法與伺服器連線';
$messages['filterdeleteerror'] = '無法刪除篩選器。發生伺服器錯誤';
$messages['filterdeleted'] = '成功刪除篩選器';
$messages['filterconfirmdelete'] = '您確定要刪除選擇的篩選器嗎?';
$messages['filtersaved'] = '成功儲存篩選器。';
$messages['filtersaveerror'] = '無法儲存篩選器。發生伺服器錯誤';
$messages['ruledeleteconfirm'] = '您確定要刪除選的規則嗎?';
$messages['actiondeleteconfirm'] = '您確定要刪除選擇的動作嗎?';
$messages['forbiddenchars'] = '內容包含禁用字元';
$messages['cannotbeempty'] = '內容不能為空白';
$messages['setactivateerror'] = '無法啟用選擇的篩選器集合。 伺服器發生錯誤';
$messages['setdeactivateerror'] = '無法停用選擇的篩選器集合。 伺服器發生錯誤';
$messages['setdeleteerror'] = '無法刪除選擇的篩選器集合。 伺服器發生錯誤';
$messages['setactivated'] = '篩選器集合成功啟用';
$messages['setdeactivated'] = '篩選器集合成功停用';
$messages['setdeleted'] = '篩選器集合成功刪除';
$messages['setdeleteconfirm'] = '你確定要刪除選擇的篩選器集合嗎?';
$messages['setcreateerror'] = '無法建立篩選器集合。 伺服器發生錯誤';
$messages['setcreated'] = '篩選器集合成功建立';
$messages['emptyname'] = '無法建立篩選器集合。 集合名稱空白';
$messages['nametoolong'] = '無法建立篩選器集合。 名稱太長'
?>
plugins/managesieve/managesieve.js
New file
@@ -0,0 +1,511 @@
/* Sieve Filters (tab) */
if (window.rcmail) {
  rcmail.addEventListener('init', function(evt) {
    var tab = $('<span>').attr('id', 'settingstabpluginmanagesieve').addClass('tablink');
    var button = $('<a>').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);
    rcmail.register_command('plugin.managesieve-set', function() { rcmail.managesieve_set() }, true);
    rcmail.register_command('plugin.managesieve-setadd', function() { rcmail.managesieve_setadd() }, true);
    rcmail.register_command('plugin.managesieve-setdel', function() { rcmail.managesieve_setdel() }, true);
    rcmail.register_command('plugin.managesieve-setact', function() { rcmail.managesieve_setact() }, true);
    rcmail.register_command('plugin.managesieve-setget', function() { rcmail.managesieve_setget() }, 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', 'plugin.managesieve-setadd', !rcmail.env.sieveconnerror);
      }
      // Create layer for form tips
      if (!rcmail.env.framed) {
        rcmail.env.ms_tip_layer = $('<div id="managesieve-tip" class="popupmenu"></div>');
        rcmail.env.ms_tip_layer.appendTo(document.body);
      }
      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();
        rcmail.enable_command('plugin.managesieve-set', true);
        rcmail.enable_command('plugin.managesieve-setact', 'plugin.managesieve-setget', rcmail.gui_objects.filtersetslist.length);
        rcmail.enable_command('plugin.managesieve-setdel', rcmail.gui_objects.filtersetslist.length > 1);
        $('#'+rcmail.buttons['plugin.managesieve-setact'][0].id).attr('title', rcmail.gettext('managesieve.filterset'
          + (rcmail.gui_objects.filtersetslist.value == rcmail.env.active_set ? 'deact' : 'act')));
      }
    }
    if (rcmail.gui_objects.sieveform && rcmail.env.rule_disabled)
      $('#disabled').attr('checked', true);
  });
};
/*********************************************************/
/*********     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.filterdeleteconfirm')))
    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 i, rows = this.filters_list.rows;
  for (i=0; i<rows.length; i++)
    if (rows[i] != null && rows[i].uid == id)
      return i;
}
rcube_webmail.prototype.managesieve_updatelist = function(action, name, id, disabled)
{
  this.set_busy(true);
  switch (action) {
    case 'delete':
      this.filters_list.remove_row(this.managesieve_rowid(id));
      this.filters_list.clear_selection();
      this.enable_command('plugin.managesieve-del', 'plugin.managesieve-up', 'plugin.managesieve-down', false);
      this.show_contentframe(false);
      // re-numbering filters
      var i, rows = this.filters_list.rows;
      for (i=0; i<rows.length; i++) {
        if (rows[i] != null && rows[i].uid > id)
          rows[i].uid = rows[i].uid-1;
      }
      break;
    case 'down':
      var from, fromstatus, status, rows = this.filters_list.rows;
      // we need only to replace filter names...
      for (var i=0; i<rows.length; i++) {
        if (rows[i]==null) { // removed row
          continue;
        }
        else if (rows[i].uid == id) {
          from = rows[i].obj;
          fromstatus = $(from).hasClass('disabled');
        }
        else if (rows[i].uid == id+1) {
          name = rows[i].obj.cells[0].innerHTML;
          status = $(rows[i].obj).hasClass('disabled');
          rows[i].obj.cells[0].innerHTML = from.cells[0].innerHTML;
          from.cells[0].innerHTML = name;
          $(from)[status?'addClass':'removeClass']('disabled');
          $(rows[i].obj)[fromstatus?'addClass':'removeClass']('disabled');
          this.filters_list.highlight_row(i);
          break;
        }
      }
      // ... and disable/enable Down button
      this.filters_listbuttons();
      break;
    case 'up':
      var from, status, fromstatus, rows = this.filters_list.rows;
      // we need only to replace filter names...
      for (var i=0; i<rows.length; i++) {
        if (rows[i] == null) { // removed row
          continue;
        }
        else if (rows[i].uid == id-1) {
          from = rows[i].obj;
          fromstatus = $(from).hasClass('disabled');
          this.filters_list.highlight_row(i);
        }
        else if (rows[i].uid == id) {
          name = rows[i].obj.cells[0].innerHTML;
          status = $(rows[i].obj).hasClass('disabled');
          rows[i].obj.cells[0].innerHTML = from.cells[0].innerHTML;
          from.cells[0].innerHTML = name;
          $(from)[status?'addClass':'removeClass']('disabled');
          $(rows[i].obj)[fromstatus?'addClass':'removeClass']('disabled');
          break;
        }
      }
      // ... and disable/enable Up button
      this.filters_listbuttons();
      break;
    case 'update':
      var rows = parent.rcmail.filters_list.rows;
      for (var i=0; i<rows.length; i++)
        if (rows[i] && rows[i].uid == id) {
          rows[i].obj.cells[0].innerHTML = name;
          if (disabled)
            $(rows[i].obj).addClass('disabled');
          else
            $(rows[i].obj).removeClass('disabled');
          break;
        }
      break;
    case 'add':
      var row, new_row, td, list = parent.rcmail.filters_list;
      if (!list)
        break;
      for (var i=0; i<list.rows.length; i++)
        if (list.rows[i] != null && String(list.rows[i].obj.id).match(/^rcmrow/))
          row = list.rows[i].obj;
      if (row) {
        new_row = parent.document.createElement('tr');
        new_row.id = 'rcmrow'+id;
        td = parent.document.createElement('td');
        new_row.appendChild(td);
        list.insert_row(new_row, false);
        if (disabled)
          $(new_row).addClass('disabled');
          if (row.cells[0].className)
            td.className = row.cells[0].className;
           td.innerHTML = name;
        list.highlight_row(id);
        parent.rcmail.enable_command('plugin.managesieve-del', 'plugin.managesieve-up', true);
      }
      else // refresh whole page
        parent.rcmail.goto_url('plugin.managesieve');
      break;
  }
  this.set_busy(false);
};
rcube_webmail.prototype.managesieve_select = function(list)
{
  var id = list.get_single_selection();
  if (id != null)
    this.load_managesieveframe(list.rows[id].uid);
};
rcube_webmail.prototype.managesieve_save = function()
{
  if (parent.rcmail && parent.rcmail.filters_list && this.gui_objects.sieveform.name != 'filtersetform') {
    var id = parent.rcmail.filters_list.get_single_selection();
    if (id != null)
      this.gui_objects.sieveform.elements['_fid'].value = parent.rcmail.filters_list.rows[id].uid;
  }
  this.gui_objects.sieveform.submit();
};
// load filter frame
rcube_webmail.prototype.load_managesieveframe = function(id)
{
  if (typeof(id) != 'undefined' && id != null) {
    this.enable_command('plugin.managesieve-del', true);
    this.filters_listbuttons();
  }
  else
    this.enable_command('plugin.managesieve-up', 'plugin.managesieve-down', 'plugin.managesieve-del', false);
  if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    target = window.frames[this.env.contentframe];
    var msgid = this.set_busy(true, 'loading');
    target.location.href = this.env.comm_path+'&_action=plugin.managesieve&_framed=1&_fid='+id+'&_unlock='+msgid;
  }
};
// enable/disable Up/Down buttons
rcube_webmail.prototype.filters_listbuttons = function()
{
  var id = this.filters_list.get_single_selection(),
    rows = this.filters_list.rows;
  for (var i=0; i<rows.length; i++) {
    if (rows[i] == null) { // removed row
    }
    else if (i == id) {
      this.enable_command('plugin.managesieve-up', false);
      break;
    }
    else {
      this.enable_command('plugin.managesieve-up', true);
      break;
    }
  }
  for (var i=rows.length-1; i>0; 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'),
      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'),
      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; i<div.childNodes.length; i++) {
    if (div.childNodes[i].id == (div.id == 'rules' ? 'rulerow' : 'actionrow')  + after)
      break;
  }
  if (div.childNodes[i+1])
    div.insertBefore(row, div.childNodes[i+1]);
  else
    div.appendChild(row);
};
// update Delete buttons status
rcube_webmail.prototype.managesieve_formbuttons = function(div)
{
  var i, button, buttons = [];
  // count and get buttons
  for (i=0; i<div.childNodes.length; i++) {
    if (div.id == 'rules' && div.childNodes[i].id) {
      if (/rulerow/.test(div.childNodes[i].id))
        buttons.push('ruledel' + div.childNodes[i].id.replace(/rulerow/, ''));
    }
    else if (div.childNodes[i].id) {
      if (/actionrow/.test(div.childNodes[i].id))
        buttons.push( 'actiondel' + div.childNodes[i].id.replace(/actionrow/, ''));
    }
  }
  for (i=0; i<buttons.length; i++) {
    button = document.getElementById(buttons[i]);
    if (i>0 || buttons.length>1) {
      $(button).removeClass('disabled');
      button.removeAttribute('disabled');
    }
    else {
      $(button).addClass('disabled');
      button.setAttribute('disabled', true);
    }
  }
};
// Set change
rcube_webmail.prototype.managesieve_set = function()
{
  var script = $(this.gui_objects.filtersetslist).val();
  location.href = this.env.comm_path+'&_action=plugin.managesieve&_set='+script;
};
// Script download
rcube_webmail.prototype.managesieve_setget = function()
{
  var script = $(this.gui_objects.filtersetslist).val();
  location.href = this.env.comm_path+'&_action=plugin.managesieve&_act=setget&_set='+script;
};
// Set activate
rcube_webmail.prototype.managesieve_setact = function()
{
  if (!this.gui_objects.filtersetslist)
    return false;
  var script = this.gui_objects.filtersetslist.value,
    action = (script == rcmail.env.active_set ? 'deact' : 'setact');
  this.http_post('plugin.managesieve', '_act='+action+'&_set='+script);
};
// Set activate flag in sets list after set activation
rcube_webmail.prototype.managesieve_reset = function()
{
  if (!this.gui_objects.filtersetslist)
    return false;
  var list = this.gui_objects.filtersetslist,
    opts = list.getElementsByTagName('option'),
    label = ' (' + this.get_label('managesieve.active') + ')',
    regx = new RegExp(RegExp.escape(label)+'$');
  for (var x=0; x<opts.length; x++) {
    if (opts[x].value != rcmail.env.active_set && opts[x].innerHTML.match(regx))
      opts[x].innerHTML = opts[x].innerHTML.replace(regx, '');
    else if (opts[x].value == rcmail.env.active_set)
      opts[x].innerHTML = opts[x].innerHTML + label;
  }
  // change title of setact button
  $('#'+rcmail.buttons['plugin.managesieve-setact'][0].id).attr('title', rcmail.gettext('managesieve.filterset'
    + (list.value == rcmail.env.active_set ? 'deact' : 'act')));
};
// Set delete
rcube_webmail.prototype.managesieve_setdel = function()
{
  if (!this.gui_objects.filtersetslist)
    return false;
  if (!confirm(this.get_label('managesieve.setdeleteconfirm')))
    return false;
  var script = this.gui_objects.filtersetslist.value;
  this.http_post('plugin.managesieve', '_act=setdel&_set='+script);
};
// Set add
rcube_webmail.prototype.managesieve_setadd = function()
{
  this.filters_list.clear_selection();
  this.enable_command('plugin.managesieve-up', 'plugin.managesieve-down', 'plugin.managesieve-del', false);
  if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
    target = window.frames[this.env.contentframe];
    var msgid = this.set_busy(true, 'loading');
    target.location.href = this.env.comm_path+'&_action=plugin.managesieve&_framed=1&_newset=1&_unlock='+msgid;
  }
};
rcube_webmail.prototype.managesieve_reload = function(set)
{
  this.env.reload_set = set;
  window.setTimeout(function() {
    location.href = rcmail.env.comm_path + '&_action=plugin.managesieve'
      + (rcmail.env.reload_set ? '&_set=' + rcmail.env.reload_set : '')
  }, 500);
};
// Register onmouse(leave/enter) events for tips on specified form element
rcube_webmail.prototype.managesieve_tip_register = function(tips)
{
  for (var n in tips) {
    $('#'+tips[n][0])
      .bind('mouseenter', {str: tips[n][1]},
        function(e) {
          var offset = $(this).offset(),
            tip = rcmail.env.framed ? parent.rcmail.env.ms_tip_layer : rcmail.env.ms_tip_layer,
            left = offset.left,
            top = offset.top - 12;
          if (rcmail.env.framed) {
            offset = $(parent.document.getElementById('filter-box')).offset();
            top  += offset.top;
            left += offset.left;
          }
          tip.html(e.data.str)
          top -= tip.height();
          tip.css({left: left, top: top}).show();
        })
      .bind('mouseleave',
        function(e) {
          var tip = parent.rcmail && parent.rcmail.env.ms_tip_layer ?
            parent.rcmail.env.ms_tip_layer : rcmail.env.ms_tip_layer;
          tip.hide();
      });
  }
};
plugins/managesieve/managesieve.php
New file
@@ -0,0 +1,1140 @@
<?php
/**
 * Managesieve (Sieve Filters)
 *
 * Plugin that adds a possibility to manage Sieve filters in Thunderbird's style.
 * It's clickable interface which operates on text scripts and communicates
 * with server using managesieve protocol. Adds Filters tab in Settings.
 *
 * @version 2.10
 * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
 *
 * Configuration (see config.inc.php.dist)
 *
 * $Id$
 */
class managesieve extends rcube_plugin
{
    public $task = 'settings';
    private $rc;
    private $sieve;
    private $errors;
    private $form;
    private $tips = array();
    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_actions'));
        $this->register_action('plugin.managesieve-save', array($this, 'managesieve_save'));
        // include main js script
        $this->include_script('managesieve.js');
    }
    function managesieve_start()
    {
        $this->rc = rcmail::get_instance();
        $this->load_config();
        // register UI objects
        $this->rc->output->add_handlers(array(
            'filterslist'    => array($this, 'filters_list'),
            'filtersetslist' => array($this, 'filtersets_list'),
            'filterframe'    => array($this, 'filter_frame'),
            'filterform'     => array($this, 'filter_form'),
            'filtersetform'  => array($this, 'filterset_form'),
        ));
        require_once($this->home . '/lib/Net/Sieve.php');
        require_once($this->home . '/lib/rcube_sieve.php');
        $host = rcube_parse_host($this->rc->config->get('managesieve_host', 'localhost'));
        $port = $this->rc->config->get('managesieve_port', 2000);
        $host = idn_to_ascii($host);
        // try to connect to managesieve server and to fetch the script
        $this->sieve = new rcube_sieve($_SESSION['username'],
            $this->rc->decrypt($_SESSION['password']), $host, $port,
            $this->rc->config->get('managesieve_auth_type'),
            $this->rc->config->get('managesieve_usetls', false),
            $this->rc->config->get('managesieve_disabled_extensions'),
            $this->rc->config->get('managesieve_debug', false),
            $this->rc->config->get('managesieve_auth_cid'),
            $this->rc->config->get('managesieve_auth_pw')
        );
        if (!($error = $this->sieve->error())) {
            $list = $this->sieve->get_scripts();
            $active = $this->sieve->get_active();
            $_SESSION['managesieve_active'] = $active;
            if (!empty($_GET['_set'])) {
                $script_name = get_input_value('_set', RCUBE_INPUT_GET);
            }
            else if (!empty($_SESSION['managesieve_current'])) {
                $script_name = $_SESSION['managesieve_current'];
            }
            else {
                // get active script
                if ($active) {
                    $script_name = $active;
                }
                else if ($list) {
                    $script_name = $list[0];
                }
                // create a new (initial) script
                else {
                    // if script not exists build default script contents
                    $script_file = $this->rc->config->get('managesieve_default');
                    $script_name = 'roundcube';
                    if ($script_file && is_readable($script_file))
                        $content = file_get_contents($script_file);
                // add script and set it active
                if ($this->sieve->save_script($script_name, $content))
                    if ($this->sieve->activate($script_name))
                        $_SESSION['managesieve_active'] = $script_name;
                }
            }
            if ($script_name)
                $this->sieve->load($script_name);
            $error = $this->sieve->error();
        }
        // finally set script objects
        if ($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;
            }
            raise_error(array('code' => 403, 'type' => 'php',
                'file' => __FILE__, 'line' => __LINE__,
                'message' => "Unable to connect to managesieve on $host:$port"), true, false);
            // to disable 'Add filter' button set env variable
            $this->rc->output->set_env('filterconnerror', true);
            $this->script = array();
        }
        else {
            $this->script = $this->sieve->script->as_array();
            $this->exts = $this->sieve->get_extensions();
            $this->rc->output->set_env('active_set', $_SESSION['managesieve_active']);
            $_SESSION['managesieve_current'] = $this->sieve->current;
        }
        return $error;
    }
    function managesieve_actions()
    {
        // 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');
                }
            }
            else if ($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 === true) {
//                      $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');
                    }
                }
            }
            else if ($action == 'delete' && !$error) {
                if (isset($this->script[$fid])) {
                    if ($this->sieve->script->delete_rule($fid))
                        $result = $this->sieve->save();
                    if ($result === true) {
                        $this->rc->output->show_message('managesieve.filterdeleted', 'confirmation');
                        $this->rc->output->command('managesieve_updatelist', 'delete', '', $fid);
                    } else {
                        $this->rc->output->show_message('managesieve.filterdeleteerror', 'error');
                    }
                }
            }
            else if ($action == 'setact' && !$error) {
                $script_name = get_input_value('_set', RCUBE_INPUT_GPC);
                $result = $this->sieve->activate($script_name);
                if ($result === true) {
                    $this->rc->output->set_env('active_set', $script_name);
                    $this->rc->output->show_message('managesieve.setactivated', 'confirmation');
                    $this->rc->output->command('managesieve_reset');
                    $_SESSION['managesieve_active'] = $script_name;
                } else {
                    $this->rc->output->show_message('managesieve.setactivateerror', 'error');
                }
            }
            else if ($action == 'deact' && !$error) {
                $result = $this->sieve->deactivate();
                if ($result === true) {
                    $this->rc->output->set_env('active_set', '');
                    $this->rc->output->show_message('managesieve.setdeactivated', 'confirmation');
                    $this->rc->output->command('managesieve_reset');
                    $_SESSION['managesieve_active'] = '';
                } else {
                    $this->rc->output->show_message('managesieve.setdeactivateerror', 'error');
                }
            }
            else if ($action == 'setdel' && !$error) {
                $script_name = get_input_value('_set', RCUBE_INPUT_GPC);
                $result = $this->sieve->remove($script_name);
                if ($result === true) {
                    $this->rc->output->show_message('managesieve.setdeleted', 'confirmation');
                    $this->rc->output->command('managesieve_reload');
                    $this->rc->session->remove('managesieve_current');
                } else {
                    $this->rc->output->show_message('managesieve.setdeleteerror', 'error');
                }
            }
            else if ($action == 'setget') {
                $script_name = get_input_value('_set', RCUBE_INPUT_GPC);
                $script = $this->sieve->get_script($script_name);
                if (PEAR::isError($script))
                    exit;
                $browser = new rcube_browser;
                // send download headers
                header("Content-Type: application/octet-stream");
                header("Content-Length: ".strlen($script));
                if ($browser->ie)
                    header("Content-Type: application/force-download");
                if ($browser->ie && $browser->ver < 7)
                    $filename = rawurlencode(abbreviate_string($script_name, 55));
                else if ($browser->ie)
                    $filename = rawurlencode($script_name);
                else
                    $filename = addcslashes($script_name, '\\"');
                header("Content-Disposition: attachment; filename=\"$filename.txt\"");
                echo $script;
                exit;
            }
            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();
        // filters set add action
        if (!empty($_POST['_newset'])) {
            $name = get_input_value('_name', RCUBE_INPUT_POST);
            $copy = get_input_value('_copy', RCUBE_INPUT_POST);
            $from = get_input_value('_from', RCUBE_INPUT_POST);
            if (!$name)
                $error = 'managesieve.emptyname';
            else if (mb_strlen($name)>128)
                $error = 'managesieve.nametoolong';
            else if ($from == 'file') {
                // from file
                if (is_uploaded_file($_FILES['_file']['tmp_name'])) {
                    $file = file_get_contents($_FILES['_file']['tmp_name']);
                    $file = preg_replace('/\r/', '', $file);
                    // for security don't save script directly
                    // check syntax before, like this...
                    $this->sieve->load_script($file);
                    if (!$this->sieve->save($name)) {
                        $error = 'managesieve.setcreateerror';
                    }
                }
                else {  // upload failed
                    $err = $_FILES['_file']['error'];
                    $error = true;
                    if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
                        $msg = rcube_label(array('name' => 'filesizeerror',
                            'vars' => array('size' =>
                                show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
                    }
                    else {
                        $error = 'fileuploaderror';
                    }
                }
            }
            else if (!$this->sieve->copy($name, $from == 'set' ? $copy : '')) {
                $error = 'managesieve.setcreateerror';
            }
            if (!$error) {
                $this->rc->output->show_message('managesieve.setcreated', 'confirmation');
                $this->rc->output->command('parent.managesieve_reload', $name);
            } else if ($msg) {
                $this->rc->output->command('display_message', $msg, 'error');
            } else {
                $this->rc->output->show_message($error, 'error');
            }
        }
        // filter add/edit action
        else if (isset($_POST['_name'])) {
            $name = trim(get_input_value('_name', RCUBE_INPUT_POST, true));
            $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['disabled'] = $_POST['_disabled'] ? true : false;
            $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], true);
                    $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 if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target))
                                $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars');
                        }
                    }
                    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 ($sizetarget == '')
                                $this->errors['tests'][$i]['sizetarget'] = $this->gettext('cannotbeempty');
                            else if (!preg_match('/^[0-9]+(K|M|G)*$/i', $sizetarget))
                                $this->errors['tests'][$i]['sizetarget'] = $this->gettext('forbiddenchars');
                            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');
                                else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target))
                                    $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars');
                            }
                            break;
                        }
                    $i++;
                }
            }
            $i = 0;
            // actions
            foreach($act_types as $idx => $type) {
                $type   = $this->strip_value($type);
                $target = $this->strip_value($act_targets[$idx]);
                switch ($type) {
                case 'fileinto':
                case 'fileinto_copy':
                    $mailbox = $this->strip_value($mailboxes[$idx]);
                    $this->form['actions'][$i]['target'] = $mailbox;
                    if ($type == 'fileinto_copy') {
                        $type = 'fileinto';
                        $this->form['actions'][$i]['copy'] = true;
                    }
                    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':
                case 'redirect_copy':
                    $this->form['actions'][$i]['target'] = $target;
                    if ($this->form['actions'][$i]['target'] == '')
                        $this->errors['actions'][$i]['target'] = $this->gettext('cannotbeempty');
                    else if (!check_email($this->form['actions'][$i]['target']))
                        $this->errors['actions'][$i]['target'] = $this->gettext('noemailwarning');
                    if ($type == 'redirect_copy') {
                        $type = 'redirect';
                        $this->form['actions'][$i]['copy'] = true;
                    }
                    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(!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;
                }
                $this->form['actions'][$i]['type'] = $type;
                $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, %d);",
                            isset($new) ? 'add' : 'update', Q($this->form['name']),
                            $fid, $this->form['disabled']),
                        'foot');
                }
                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'])) {
            if (isset($_GET['_newset']) || isset($_POST['_newset'])) {
                $this->rc->output->send('managesieve.setedit');
            }
            else {
                $this->rc->output->send('managesieve.filteredit');
            }
        } 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,
                'class' => $filter['disabled'] ? 'disabled' : '',
            );
        // 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.filterdeleteconfirm');
        return $out;
    }
    // return the filters list as <SELECT>
    function filtersets_list($attrib)
    {
        // add id to message list table if not specified
        if (!strlen($attrib['id']))
            $attrib['id'] = 'rcmfiltersetslist';
        $list = $this->sieve->get_scripts();
        $active = $this->sieve->get_active();
        $select = new html_select(array('name' => '_set', 'id' => $attrib['id'],
            'onchange' => 'rcmail.managesieve_set()'));
        if ($list) {
            asort($list, SORT_LOCALE_STRING);
            foreach ($list as $set)
                $select->add($set . ($set == $active ? ' ('.$this->gettext('active').')' : ''), $set);
        }
        $out = $select->show($this->sieve->current);
        // set client env
        $this->rc->output->add_gui_object('filtersetslist', $attrib['id']);
        $this->rc->output->add_label(
            'managesieve.setdeleteconfirm',
            'managesieve.active',
            'managesieve.filtersetact',
            'managesieve.filtersetdeact'
        );
        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 filterset_form($attrib)
    {
        if (!$attrib['id'])
            $attrib['id'] = 'rcmfiltersetform';
        $out = '<form name="filtersetform" action="./" method="post" enctype="multipart/form-data">'."\n";
        $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' => '_newset', 'value' => 1));
        $out .= $hiddenfields->show();
        $name     = get_input_value('_name', RCUBE_INPUT_POST);
        $copy     = get_input_value('_copy', RCUBE_INPUT_POST);
        $selected = get_input_value('_from', RCUBE_INPUT_POST);
        // filter set name input
        $input_name = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30,
            'class' => ($this->errors['name'] ? 'error' : '')));
        $out .= sprintf('<label for="%s"><b>%s:</b></label> %s<br /><br />',
            '_name', Q($this->gettext('filtersetname')), $input_name->show($name));
        $out .="\n<fieldset class=\"itemlist\"><legend>" . $this->gettext('filters') . ":</legend>\n";
        $out .= '<input type="radio" id="from_none" name="_from" value="none"'
            .(!$selected || $selected=='none' ? ' checked="checked"' : '').'></input>';
        $out .= sprintf('<label for="%s">%s</label> ', 'from_none', Q($this->gettext('none')));
        // filters set list
        $list   = $this->sieve->get_scripts();
        $active = $this->sieve->get_active();
        $select = new html_select(array('name' => '_copy', 'id' => '_copy'));
        if (is_array($list)) {
            asort($list, SORT_LOCALE_STRING);
            foreach ($list as $set)
                $select->add($set . ($set == $active ? ' ('.$this->gettext('active').')' : ''), $set);
            $out .= '<br /><input type="radio" id="from_set" name="_from" value="set"'
                .($selected=='set' ? ' checked="checked"' : '').'></input>';
            $out .= sprintf('<label for="%s">%s:</label> ', 'from_set', Q($this->gettext('fromset')));
            $out .= $select->show($copy);
        }
        // script upload box
        $upload = new html_inputfield(array('name' => '_file', 'id' => '_file', 'size' => 30,
            'type' => 'file', 'class' => ($this->errors['name'] ? 'error' : '')));
        $out .= '<br /><input type="radio" id="from_file" name="_from" value="file"'
            .($selected=='file' ? ' checked="checked"' : '').'></input>';
        $out .= sprintf('<label for="%s">%s:</label> ', 'from_file', Q($this->gettext('fromfile')));
        $out .= $upload->show();
        $out .= '</fieldset>';
        $this->rc->output->add_gui_object('sieveform', 'filtersetform');
        return $out;
    }
    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 = '<form name="filterform" action="./" method="post">'."\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 ($this->errors['name'])
            $this->add_tip($field_id, $this->errors['name'], true);
        if (isset($scr))
            $input_name = $input_name->show($scr['name']);
        else
            $input_name = $input_name->show();
        $out .= sprintf("\n<label for=\"%s\"><b>%s:</b></label> %s<br /><br />\n",
            $field_id, Q($this->gettext('filtername')), $input_name);
        $out .= '<fieldset><legend>' . Q($this->gettext('messagesrules')) . "</legend>\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<label for=\"%s\">%s</label>&nbsp;\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<label for=\"%s\">%s</label>\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<label for=\"%s\">%s</label>\n",
            $input_join, $field_id, Q($this->gettext('filterany')));
        $rows_num = isset($scr) ? sizeof($scr['tests']) : 1;
        $out .= '<div id="rules"'.($any ? ' style="display: none"' : '').'>';
        for ($x=0; $x<$rows_num; $x++)
            $out .= $this->rule_div($fid, $x);
        $out .= "</div>\n";
        $out .= "</fieldset>\n";
        // actions
        $out .= '<fieldset><legend>' . Q($this->gettext('messagesactions')) . "</legend>\n";
        $rows_num = isset($scr) ? sizeof($scr['actions']) : 1;
        $out .= '<div id="actions">';
        for ($x=0; $x<$rows_num; $x++)
            $out .= $this->action_div($fid, $x);
        $out .= "</div>\n";
        $out .= "</fieldset>\n";
        $this->print_tips();
        if ($scr['disabled']) {
            $this->rc->output->set_env('rule_disabled', true);
        }
        $this->rc->output->add_label(
            'managesieve.ruledeleteconfirm',
            '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 ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : '';
        $out .= '<table><tr><td class="rowactions">';
        // 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']);
        else if ((isset($rule['test']) && $rule['test'] == 'exists')
            && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers))
            $out .= $select_header->show($rule['arg']);
        else if (isset($rule['test']) && $rule['test'] == 'size')
            $out .= $select_header->show('size');
        else if (isset($rule['test']) && $rule['test'] != 'true')
            $out .= $select_header->show('...');
        else
            $out .= $select_header->show();
        $out .= '</td><td class="rowtargets">';
        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'];
        else if ((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 .= '<div id="custom_header' .$id. '" style="display:' .(isset($custom) ? 'inline' : 'none'). '">
            <input type="text" name="_custom_header[]" id="custom_header_i'.$id.'" '
            . $this->error_class($id, 'test', 'header', 'custom_header_i')
            .' value="' .Q($custom). '" size="20" />&nbsp;</div>' . "\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');
        if (in_array('relational', $this->exts)) {
            $select_op->add(Q($this->gettext('countisgreaterthan')), 'count-gt');
            $select_op->add(Q($this->gettext('countisgreaterthanequal')), 'count-ge');
            $select_op->add(Q($this->gettext('countislessthan')), 'count-lt');
            $select_op->add(Q($this->gettext('countislessthanequal')), 'count-le');
            $select_op->add(Q($this->gettext('countequals')), 'count-eq');
            $select_op->add(Q($this->gettext('countnotequals')), 'count-ne');
            $select_op->add(Q($this->gettext('valueisgreaterthan')), 'value-gt');
            $select_op->add(Q($this->gettext('valueisgreaterthanequal')), 'value-ge');
            $select_op->add(Q($this->gettext('valueislessthan')), 'value-lt');
            $select_op->add(Q($this->gettext('valueislessthanequal')), 'value-le');
            $select_op->add(Q($this->gettext('valueequals')), 'value-eq');
            $select_op->add(Q($this->gettext('valuenotequals')), 'value-ne');
        }
        // target input (TODO: lists)
        if ($rule['test'] == 'header') {
            $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['type']);
            $target = $rule['arg2'];
        }
        else if ($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 .= '<input type="text" name="_rule_target[]" id="rule_target' .$id. '"
            value="' .Q($target). '" size="20" ' . $this->error_class($id, 'test', 'target', 'rule_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 .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">';
        $out .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : '');
        $out .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" '
            . $this->error_class($id, 'test', 'sizetarget', 'rule_size_i') .' />
            <input type="radio" name="_rule_size_item['.$id.']" value=""'
                . (!$sizeitem ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('B').'
            <input type="radio" name="_rule_size_item['.$id.']" value="K"'
                . ($sizeitem=='K' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('KB').'
            <input type="radio" name="_rule_size_item['.$id.']" value="M"'
                . ($sizeitem=='M' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('MB').'
            <input type="radio" name="_rule_size_item['.$id.']" value="G"'
                . ($sizeitem=='G' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('GB');
        $out .= '</div>';
        $out .= '</td>';
        // add/del buttons
        $out .= '<td class="rowbuttons">';
        $out .= '<input type="button" id="ruleadd' . $id .'" value="'. Q($this->gettext('add')). '"
            onclick="rcmail.managesieve_ruleadd(' . $id .')" class="button" /> ';
        $out .= '<input type="button" id="ruledel' . $id .'" value="'. Q($this->gettext('del')). '"
            onclick="rcmail.managesieve_ruledel(' . $id .')" class="button' . ($rows_num<2 ? ' disabled' : '') .'"'
            . ($rows_num<2 ? ' disabled="disabled"' : '') .' />';
        $out .= '</td></tr></table>';
        $out .= $div ? "</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 ? '<div class="actionrow" id="actionrow' .$id .'">'."\n" : '';
        $out .= '<table><tr><td class="rowactions">';
        // 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');
        if (in_array('fileinto', $this->exts) && in_array('copy', $this->exts))
            $select_action->add(Q($this->gettext('messagecopyto')), 'fileinto_copy');
        $select_action->add(Q($this->gettext('messageredirect')), 'redirect');
        if (in_array('copy', $this->exts))
            $select_action->add(Q($this->gettext('messagesendcopy')), 'redirect_copy');
        if (in_array('reject', $this->exts))
            $select_action->add(Q($this->gettext('messagediscard')), 'reject');
        else if (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');
        $select_type = $action['type'];
        if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) {
            $select_type .= '_copy';
        }
        $out .= $select_action->show($select_type);
        $out .= '</td>';
        // actions target inputs
        $out .= '<td class="rowtargets">';
        // shared targets
        $out .= '<input type="text" name="_action_target[]" id="action_target' .$id. '" '
            .'value="' .($action['type']=='redirect' ? Q($action['target'], 'strict', false) : ''). '" size="40" '
            .'style="display:' .($action['type']=='redirect' ? 'inline' : 'none') .'" '
            . $this->error_class($id, 'action', 'target', 'action_target') .' />';
        $out .= '<textarea name="_action_target_area[]" id="action_target_area' .$id. '" '
            .'rows="3" cols="40" '. $this->error_class($id, 'action', 'targetarea', 'action_target_area')
            .'style="display:' .(in_array($action['type'], array('reject', 'ereject')) ? 'inline' : 'none') .'">'
            . (in_array($action['type'], array('reject', 'ereject')) ? Q($action['target'], 'strict', false) : '')
            . "</textarea>\n";
        // vacation
        $out .= '<div id="action_vacation' .$id.'" style="display:' .($action['type']=='vacation' ? 'inline' : 'none') .'">';
        $out .= '<span class="label">'. Q($this->gettext('vacationreason')) .'</span><br />'
            .'<textarea name="_action_reason[]" id="action_reason' .$id. '" '
            .'rows="3" cols="40" '. $this->error_class($id, 'action', 'reason', 'action_reason') . '>'
            . Q($action['reason'], 'strict', false) . "</textarea>\n";
        $out .= '<br /><span class="label">' .Q($this->gettext('vacationaddresses')) . '</span><br />'
            .'<input type="text" name="_action_addresses[]" id="action_addr'.$id.'" '
            .'value="' . (is_array($action['addresses']) ? Q(implode(', ', $action['addresses']), 'strict', false) : $action['addresses']) . '" size="40" '
            . $this->error_class($id, 'action', 'addresses', 'action_addr') .' />';
        $out .= '<br /><span class="label">' . Q($this->gettext('vacationdays')) . '</span><br />'
            .'<input type="text" name="_action_days[]" id="action_days'.$id.'" '
            .'value="' .Q($action['days'], 'strict', false) . '" size="2" '
            . $this->error_class($id, 'action', 'days', 'action_days') .' />';
        $out .= '</div>';
        // mailbox select
        $out .= '<select id="action_mailbox' .$id. '" name="_action_mailbox[]" style="display:'
            .(!isset($action) || $action['type']=='fileinto' ? 'inline' : 'none'). '">';
        $this->rc->imap_connect();
        $a_folders = $this->rc->imap->list_mailboxes();
        $delimiter = $this->rc->imap->get_hierarchy_delimiter();
        // set mbox encoding
        $mbox_encoding = $this->rc->config->get('managesieve_mbox_encoding', 'UTF7-IMAP');
        if ($action['type'] == 'fileinto')
            $mailbox = $action['target'];
        else
            $mailbox = '';
        foreach ($a_folders as $folder) {
            $utf7folder = $this->rc->imap->mod_mailbox($folder);
            $names = explode($delimiter, rcube_charset_convert($folder, 'UTF7-IMAP'));
            $name  = $names[sizeof($names)-1];
            if ($replace_delimiter = $this->rc->config->get('managesieve_replace_delimiter'))
                $utf7folder = str_replace($delimiter, $replace_delimiter, $utf7folder);
            // convert to Sieve implementation encoding
            $utf7folder = $this->mbox_encode($utf7folder, $mbox_encoding);
            if ($folder_class = rcmail_folder_classname($name))
                $foldername = $this->gettext($folder_class);
            else
                $foldername = $name;
            $out .= sprintf('<option value="%s"%s>%s%s</option>'."\n",
                htmlspecialchars($utf7folder),
                ($mailbox == $utf7folder ? ' selected="selected"' : ''),
                str_repeat('&nbsp;', 4 * (sizeof($names)-1)),
                Q(abbreviate_string($foldername, 40 - (2 * sizeof($names)-1))));
        }
        $out .= '</select>';
        $out .= '</td>';
        // add/del buttons
        $out .= '<td class="rowbuttons">';
        $out .= '<input type="button" id="actionadd' . $id .'" value="'. Q($this->gettext('add')). '"
            onclick="rcmail.managesieve_actionadd(' . $id .')" class="button" /> ';
        $out .= '<input type="button" id="actiondel' . $id .'" value="'. Q($this->gettext('del')). '"
            onclick="rcmail.managesieve_actiondel(' . $id .')" class="button' . ($rows_num<2 ? ' disabled' : '') .'"'
            . ($rows_num<2 ? ' disabled="disabled"' : '') .' />';
        $out .= '</td>';
        $out .= '</tr></table>';
        $out .= $div ? "</div>\n" : '';
        return $out;
    }
    private function genid()
    {
        $result = intval(rcube_timer());
        return $result;
    }
    private function strip_value($str, $allow_html=false)
    {
        if (!$allow_html)
            $str = strip_tags($str);
        return trim($str);
    }
    private function error_class($id, $type, $target, $elem_prefix='')
    {
        // TODO: tooltips
        if (($type == 'test' && ($str = $this->errors['tests'][$id][$target])) ||
            ($type == 'action' && ($str = $this->errors['actions'][$id][$target]))
        ) {
            $this->add_tip($elem_prefix.$id, $str, true);
            return ' class="error"';
        }
        return '';
    }
    private function mbox_encode($text, $encoding)
    {
        return rcube_charset_convert($text, 'UTF7-IMAP', $encoding);
    }
    private function add_tip($id, $str, $error=false)
    {
        if ($error)
            $str = html::span('sieve error', $str);
        $this->tips[] = array($id, $str);
    }
    private function print_tips()
    {
        if (empty($this->tips))
            return;
        $script = JS_OBJECT_NAME.'.managesieve_tip_register('.json_encode($this->tips).');';
        $this->rc->output->add_script($script, 'foot');
    }
}
plugins/managesieve/skins/default/managesieve.css
New file
@@ -0,0 +1,296 @@
/***** Roundcube|Filters styles *****/
#filterslist
{
  position: absolute;
  left: 20px;
  top: 120px;
  bottom: 20px;
  border: 1px solid #999999;
  overflow: auto;
  /* css hack for IE */
  height: expression((parseInt(document.documentElement.clientHeight)-140)+'px');
}
#filters-table
{
  width: 100%;
  table-layout: fixed;
  /* css hack for IE */
  width: expression(document.getElementById('filterslist').clientWidth);
}
#filters-table tbody td
{
  cursor: pointer;
}
#filters-table tbody tr.disabled td
{
  color: #999999;
}
#filtersbuttons
{
  position: absolute;
  left: 20px;
  top: 85px;
}
#filtersetsbuttons
{
  position: absolute;
  left: 230px;
  top: 85px;
}
#filtersbuttons a,
#filtersetsbuttons a
{
  display: block;
  float: left;
}
#filtersbuttons a.button,
#filtersbuttons a.buttonPas,
#filtersetsbuttons a.button,
#filtersetsbuttons a.buttonPas
{
  display: block;
  float: left;
  width: 32px;
  height: 32px;
  padding: 0;
  margin-right: 3px;
  overflow: hidden;
  background: url(managesieve_toolbar.png) 0 0 no-repeat transparent;
  opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#filtersbuttons a.buttonPas,
#filtersetsbuttons a.buttonPas
{
  filter: alpha(opacity=35);
  opacity: 0.35;
}
#filtersbuttons a.add {
  background-position: 0px 0px;
}
#filtersbuttons a.addsel {
  background-position: 0 -32px;
}
#filtersbuttons a.del {
  background-position: -32px 0px;
}
#filtersbuttons a.delsel {
  background-position: -32px -32px;
}
#filtersbuttons a.up {
  background-position: -64px 0px;
}
#filtersbuttons a.upsel {
  background-position: -64px -32px;
}
#filtersbuttons a.down {
  background-position: -96px 0px;
}
#filtersbuttons a.downsel {
  background-position: -96px -32px;
}
#filtersetsbuttons a.setadd {
  background-position: -128px 0px;
}
#filtersetsbuttons a.setaddsel {
  background-position: -128px -32px;
}
#filtersetsbuttons a.setdel {
  background-position: -160px 0px;
}
#filtersetsbuttons a.setdelsel {
  background-position: -160px -32px;
}
#filtersetsbuttons a.setset {
  background-position: -192px 0px;
}
#filtersetsbuttons a.setsetsel {
  background-position: -192px -32px;
}
#filtersetsbuttons a.setget {
  background-position: -224px 0px;
}
#filtersetsbuttons a.setgetsel {
  background-position: -224px -32px;
}
#filtersetselect
{
  position: absolute;
  left: 375px;
  top: 90px;
}
#filter-box
{
  position: absolute;
  top: 120px;
  right: 20px;
  bottom: 20px;
  border: 1px solid #999999;
  overflow: hidden;
  /* css hack for IE */
  width: expression((parseInt(document.documentElement.clientWidth)-40-parseInt(document.getElementById('filterslist').offsetWidth))+'px');
  height: expression((parseInt(document.documentElement.clientHeight)-140)+'px');
}
#filter-frame
{
  border: none;
}
body.iframe
{
  min-width: 740px;
  width: expression(Math.max(740, document.documentElement.clientWidth)+'px');
}
#filter-form
{
  min-width: 650px;
  white-space: nowrap;
  padding: 20px 10px 10px 10px;
}
legend, label
{
  color: #666666;
}
#rules, #actions
{
  margin-top: 5px;
  padding: 0;
  border-collapse: collapse;
}
div.rulerow, div.actionrow
{
  width: auto;
  padding: 2px;
  white-space: nowrap;
  border: 1px solid #F2F2F2;
}
div.rulerow:hover, div.actionrow:hover
{
  padding: 2px;
  white-space: nowrap;
  background: #F9F9F9;
  border: 1px solid silver;
}
div.rulerow table, div.actionrow table
{
  padding: 0px;
  width: 100%;
}
td.rowbuttons
{
  text-align: right;
  white-space: nowrap;
  width: 1%;
}
td.rowactions
{
  white-space: nowrap;
  width: 1%;
}
td.rowtargets
{
  white-space: nowrap;
  width: 98%;
  padding-left: 10px;
}
input.disabled, input.disabled:hover
{
  color: #999999;
}
input.error, textarea.error
{
  background-color: #FFFF88;
}
input.box,
input.radio
{
  border: 0;
}
span.label
{
  color: #666666;
  font-size: 10px;
  white-space: nowrap;
}
#footer
{
  padding-top: 5px;
  width: 100%;
}
#footer .footerleft
{
  padding-left: 2px;
  white-space: nowrap;
  float: left;
}
#footer .footerright
{
  padding-right: 2px;
  white-space: nowrap;
  text-align: right;
  float: right;
}
.itemlist
{
  line-height: 25px;
}
.itemlist input
{
  vertical-align: middle;
}
span.sieve.error
{
  color: red;
}
#managesieve-tip
{
  width: 200px;
}
plugins/managesieve/skins/default/managesieve_toolbar.png
plugins/managesieve/skins/default/templates/filteredit.html
New file
@@ -0,0 +1,117 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/managesieve.css" />
</head>
<body class="iframe">
<script type="text/javascript">
function header_select(id)
{
    var obj = document.getElementById('header'+id);
    if (obj.value == 'size')
    {
    document.getElementById('rule_size' + id).style.display = 'inline';
    document.getElementById('rule_op' + id).style.display = 'none';
    document.getElementById('rule_target' + id).style.display = 'none';
    document.getElementById('custom_header' + id).style.display = 'none';
    }
    else
    {
    if (obj.value != '...')
        document.getElementById('custom_header' + id).style.display = 'none';
    else
        document.getElementById('custom_header' + id).style.display = 'inline';
    document.getElementById('rule_size' + id).style.display = 'none';
    document.getElementById('rule_op' + id).style.display = 'inline';
    rule_op_select(id);
    }
}
function rule_op_select(id)
{
    var obj = document.getElementById('rule_op'+id);
    if (obj.value == 'exists' || obj.value == 'notexists')
    {
    document.getElementById('rule_target' + id).style.display = 'none';
    }
    else
    {
    document.getElementById('rule_target' + id).style.display = 'inline';
    }
}
function action_type_select(id)
{
    var obj = document.getElementById('action_type'+id);
    if (obj.value == 'fileinto' || obj.value == 'fileinto_copy')
    {
    document.getElementById('action_mailbox' + id).style.display = 'inline';
    document.getElementById('action_target' + id).style.display = 'none';
    document.getElementById('action_target_area' + id).style.display = 'none';
    document.getElementById('action_vacation' + id).style.display = 'none';
    }
    else if (obj.value == 'redirect' || obj.value == 'redirect_copy')
    {
    document.getElementById('action_target' + id).style.display = 'inline';
    document.getElementById('action_mailbox' + id).style.display = 'none';
    document.getElementById('action_target_area' + id).style.display = 'none';
    document.getElementById('action_vacation' + id).style.display = 'none';
    }
    else if (obj.value.match(/^reject|ereject$/))
    {
    document.getElementById('action_target_area' + id).style.display = 'inline';
    document.getElementById('action_vacation' + id).style.display = 'none';
    document.getElementById('action_target' + id).style.display = 'none';
    document.getElementById('action_mailbox' + id).style.display = 'none';
    }
    else if (obj.value == 'vacation')
    {
    document.getElementById('action_vacation' + id).style.display = 'inline';
        document.getElementById('action_target_area' + id).style.display = 'none';
    document.getElementById('action_target' + id).style.display = 'none';
    document.getElementById('action_mailbox' + id).style.display = 'none';
    }
    else // discard, keep, stop
    {
    document.getElementById('action_target_area' + id).style.display = 'none';
    document.getElementById('action_vacation' + id).style.display = 'none';
    document.getElementById('action_target' + id).style.display = 'none';
    document.getElementById('action_mailbox' + id).style.display = 'none';
    }
}
function rule_join_radio(value)
{
    document.getElementById('rules').style.display = (value=='any' ? 'none' : 'block');
}
</script>
<div id="filter-title" class="boxtitle"><roundcube:label name="managesieve.filterdef" /></div>
<div id="filter-form" class="boxcontent">
<roundcube:object name="filterform" />
<div id="footer">
<div class="footerleft">
<roundcube:button command="plugin.managesieve-save" type="input" class="button mainaction" label="save" />
</div>
<div class="footerright">
<label for="disabled"><roundcube:label name="managesieve.filterdisabled" /></label>
<input type="checkbox" id="disabled" name="_disabled" value="1" />
</div>
</div>
</form>
</div>
</body>
</html>
plugins/managesieve/skins/default/templates/managesieve.html
New file
@@ -0,0 +1,54 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/managesieve.css" />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#filterslist { width: <roundcube:exp expression="!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter-5 : 210" />px; }
#filter-box { left: <roundcube:exp expression="!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter+5 : 220" />px;
<roundcube:exp expression="browser:ie ? ('width:expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter+5 : 220).')+\\'px\\');') : ''" />
}
</style>
</head>
<body>
<roundcube:include file="/includes/taskbar.html" />
<roundcube:include file="/includes/header.html" />
<roundcube:include file="/includes/settingstabs.html" />
<div id="filtersbuttons">
<roundcube:button command="plugin.managesieve-add" type="link" class="buttonPas add" classSel="button addsel" classAct="button add" title="managesieve.filteradd" content=" " />
<roundcube:button command="plugin.managesieve-del" type="link" class="buttonPas del" classSel="button delsel" classAct="button del" title="managesieve.filterdel" content=" " />
<roundcube:button command="plugin.managesieve-up" type="link" class="buttonPas up" classSel="button upsel" classAct="button up" title="managesieve.moveup" content=" " />
<roundcube:button command="plugin.managesieve-down" type="link" class="buttonPas down" classSel="button downsel" classAct="button down" title="managesieve.movedown" content=" " />
</div>
<div id="filtersetsbuttons">
<roundcube:button command="plugin.managesieve-setadd" type="link" class="buttonPas setadd" classSel="button setaddsel" classAct="button setadd" title="managesieve.filtersetadd" content=" " />
<roundcube:button command="plugin.managesieve-setdel" type="link" class="buttonPas setdel" classSel="button setdelsel" classAct="button setdel" title="managesieve.filtersetdel" content=" " />
<roundcube:button command="plugin.managesieve-setact" type="link" class="buttonPas setset" classSel="button setsetsel" classAct="button setset" content=" " />
<roundcube:button command="plugin.managesieve-setget" type="link" class="buttonPas setget" classSel="button setgetsel" classAct="button setget" title="managesieve.filtersetget" content=" " />
</div>
<div id="filtersetselect">
<roundcube:label name="managesieve.filterset" />:
<roundcube:object name="filtersetslist" id="filtersets-select" />
</div>
<div id="filterslist">
<roundcube:object name="filterslist" id="filters-table" class="records-table" cellspacing="0" summary="Filters list" />
</div>
<script type="text/javascript">
  var sieveviewsplit = new rcube_splitter({id:'sieveviewsplitter', p1: 'filterslist', p2: 'filter-box', orientation: 'v', relative: true, start: 215});
  rcmail.add_onload('sieveviewsplit.init()');
</script>
<div id="filter-box">
<roundcube:object name="filterframe" id="filter-frame" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>
</body>
</html>
plugins/managesieve/skins/default/templates/setedit.html
New file
@@ -0,0 +1,24 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/managesieve.css" />
</head>
<body class="iframe">
<div id="filter-title" class="boxtitle"><roundcube:label name="managesieve.newfilterset" /></div>
<div id="filter-form" class="boxcontent">
<roundcube:object name="filtersetform" />
<p>
<roundcube:button command="plugin.managesieve-save" type="input" class="button mainaction" label="save" />
</p>
</form>
</div>
</body>
</html>
plugins/markasjunk/localization/cs_CZ.inc
New file
@@ -0,0 +1,24 @@
<?php
/*
+-----------------------------------------------------------------------+
| language/cs_CZ/labels.inc                                             |
|                                                                       |
| Language file of the Roundcube markasjunk plugin                      |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
| Licensed under the GNU GPL                                            |
|                                                                       |
+-----------------------------------------------------------------------+
| Author: Milan Kozak <hodza@hodza.net>                                 |
+-----------------------------------------------------------------------+
@version $Id: labels.inc 2993 2009-09-26 18:32:07Z alec $
*/
$labels = array();
$labels['buttontitle'] = 'Označit jako Spam';
$labels['reportedasjunk'] = 'Úspěšně nahlášeno jako Spam';
?>
plugins/markasjunk/localization/da_DK.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Marker som junk mail';
$labels['reportedasjunk'] = 'Successfuldt rapporteret som junk mail';
?>
plugins/markasjunk/localization/de_DE.inc
New file
@@ -0,0 +1,6 @@
<?php
// translation done by Ulli Heist - http://heist.hobby-site.org/
$labels = array();
$labels['buttontitle'] = 'als SPAM markieren';
$labels['reportedasjunk'] = 'Erfolgreich als SPAM gemeldet';
?>
plugins/markasjunk/localization/en_US.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Mark as Junk';
$labels['reportedasjunk'] = 'Successfully reported as Junk';
?>
plugins/markasjunk/localization/es_AR.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Marcar como SPAM';
$labels['reportedasjunk'] = 'Mensaje reportado como SPAM';
?>
plugins/markasjunk/localization/es_ES.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Marcar como SPAM';
$labels['reportedasjunk'] = 'Mensaje informado como SPAM';
?>
plugins/markasjunk/localization/et_EE.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Märgista Rämpsuks';
$labels['reportedasjunk'] = 'Edukalt Rämpsuks märgitud';
?>
plugins/markasjunk/localization/ja_JP.inc
New file
@@ -0,0 +1,9 @@
<?php
//  EN-Revision: 3891
$labels = array();
$labels['buttontitle'] = '迷惑メールとして設定';
$labels['reportedasjunk'] = '迷惑メールとして報告することに成功しました。';
?>
plugins/markasjunk/localization/pl_PL.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Oznacz jako SPAM';
$labels['reportedasjunk'] = 'Pomyślnie oznaczono jako SPAM';
?>
plugins/markasjunk/localization/ru_RU.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Переместить в "СПАМ"';
$labels['reportedasjunk'] = 'Перемещено в "СПАМ"';
?>
plugins/markasjunk/localization/sk_SK.inc
New file
@@ -0,0 +1,15 @@
<?php
/**
 * Slovak translation for Roundcube markasjunk plugin
 *
 * @version 1.0 (2010-10-18)
 * @author panda <admin@whistler.sk>
 *
 */
$labels = array();
$labels['buttontitle'] = 'Označiť ako Spam';
$labels['reportedasjunk'] = 'Úspešne nahlásené ako Spam';
?>
plugins/markasjunk/localization/sv_SE.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = 'Märk som skräp';
$labels['reportedasjunk'] = 'Framgångsrikt rapporterat som skräp';
?>
plugins/markasjunk/localization/zh_TW.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['buttontitle'] = '標示為垃圾信';
$labels['reportedasjunk'] = '成功回報垃圾信';
?>
plugins/markasjunk/markasjunk.js
New file
@@ -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(','),
      lock = rcmail.set_busy(true, 'loading');
    rcmail.http_post('plugin.markasjunk', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), lock);
}
// 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);
      });
  })
}
plugins/markasjunk/markasjunk.php
New file
@@ -0,0 +1,56 @@
<?php
/**
 * Mark as Junk
 *
 * Sample plugin that adds a new button to the mailbox toolbar
 * to mark the selected messages as Junk and move them to the Junk folder
 *
 * @version @package_version@
 * @author Thomas Bruederli
 */
class markasjunk extends rcube_plugin
{
  public $task = 'mail';
  function init()
  {
    $rcmail = rcmail::get_instance();
    $this->register_action('plugin.markasjunk', array($this, 'request_action'));
    if ($rcmail->action == '' || $rcmail->action == 'show') {
      $skin_path = $this->local_skin_path();
      $this->include_script('markasjunk.js');
      $this->add_texts('localization', true);
      $this->add_button(array(
        'command' => 'plugin.markasjunk',
        'imagepas' => $skin_path.'/junk_pas.png',
        'imageact' => $skin_path.'/junk_act.png',
    'title' => 'markasjunk.buttontitle'), 'toolbar');
    }
  }
  function request_action()
  {
    $this->add_texts('localization');
    $GLOBALS['IMAP_FLAGS']['JUNK'] = 'Junk';
    $GLOBALS['IMAP_FLAGS']['NONJUNK'] = 'NonJunk';
    $uids = get_input_value('_uid', RCUBE_INPUT_POST);
    $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
    $rcmail = rcmail::get_instance();
    $rcmail->imap->unset_flag($uids, 'NONJUNK');
    $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();
  }
}
plugins/markasjunk/package.xml
New file
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
    http://pear.php.net/dtd/tasks-1.0.xsd
    http://pear.php.net/dtd/package-2.0
    http://pear.php.net/dtd/package-2.0.xsd">
    <name>markasjunk</name>
    <channel>pear.roundcube.net</channel>
    <summary>Mark messages as Junk</summary>
    <description>Adds a new button to the mailbox toolbar to mark the selected messages as Junk and move them to the configured Junk folder.</description>
    <lead>
        <name>Thomas Bruederli</name>
        <user>thomasb</user>
        <email>roundcube@gmail.com</email>
        <active>yes</active>
    </lead>
    <date>2010-03-29</date>
    <time>13:20:00</time>
    <version>
        <release>1.0</release>
        <api>1.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license>
    <notes>-</notes>
    <contents>
        <dir baseinstalldir="/" name="/">
            <file name="markasjunk.php" role="php">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="markasjunk.js" role="data">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="localization/cs_CZ.inc" role="data"></file>
            <file name="localization/da_DK.inc" role="data"></file>
            <file name="localization/de_DE.inc" role="data"></file>
            <file name="localization/en_US.inc" role="data"></file>
            <file name="localization/es_AR.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/ja_JP.inc" role="data"></file>
            <file name="localization/pl_PL.inc" role="data"></file>
            <file name="localization/ru_RU.inc" role="data"></file>
            <file name="localization/sv_SE.inc" role="data"></file>
            <file name="localization/zh_TW.inc" role="data"></file>
            <file name="skins/default/junk_act.png" role="data"></file>
            <file name="skins/default/junk_pas.png" role="data"></file>
        </dir>
        <!-- / -->
    </contents>
    <dependencies>
        <required>
            <php>
                <min>5.2.1</min>
            </php>
            <pearinstaller>
                <min>1.7.0</min>
            </pearinstaller>
        </required>
    </dependencies>
    <phprelease/>
</package>
plugins/markasjunk/skins/default/junk_act.png
plugins/markasjunk/skins/default/junk_pas.png
plugins/new_user_dialog/localization/cs_CZ.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['identitydialogtitle'] = 'Prosím doplňte své jméno a e-mail';
$labels['identitydialoghint'] = 'Tento dialog se objeví pouze při prvním přihlášení.';
?>
plugins/new_user_dialog/localization/de_CH.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['identitydialogtitle'] = 'Bitte vervollständigen Sie Ihre Absender-Informationen';
$labels['identitydialoghint'] = 'Dieser Dialog erscheint nur einmal beim ersten Login.';
?>
plugins/new_user_dialog/localization/de_DE.inc
New file
@@ -0,0 +1,7 @@
<?php
$labels = array();
$labels['identitydialogtitle'] = 'Bitte vervollständigen Sie Ihre Absender-Informationen';
$labels['identitydialoghint'] = 'Dieser Dialog erscheint nur einmal beim ersten Login.';
?>
Diff truncated after the above file
plugins/new_user_dialog/localization/en_US.inc plugins/new_user_dialog/localization/es_ES.inc plugins/new_user_dialog/localization/et_EE.inc plugins/new_user_dialog/localization/it_IT.inc plugins/new_user_dialog/localization/ja_JP.inc plugins/new_user_dialog/localization/nl_NL.inc plugins/new_user_dialog/localization/pl_PL.inc plugins/new_user_dialog/localization/pt_BR.inc plugins/new_user_dialog/localization/pt_PT.inc plugins/new_user_dialog/localization/ru_RU.inc plugins/new_user_dialog/localization/sl_SI.inc plugins/new_user_dialog/localization/sv_SE.inc plugins/new_user_dialog/localization/zh_TW.inc plugins/new_user_dialog/new_user_dialog.php plugins/new_user_dialog/newuserdialog.css plugins/new_user_dialog/package.xml plugins/new_user_identity/new_user_identity.php plugins/password/README plugins/password/config.inc.php.dist plugins/password/drivers/chgsaslpasswd.c plugins/password/drivers/chgvirtualminpasswd.c plugins/password/drivers/chpass-wrapper.py plugins/password/drivers/chpasswd.php plugins/password/drivers/cpanel.php plugins/password/drivers/directadmin.php plugins/password/drivers/hmail.php plugins/password/drivers/ldap.php plugins/password/drivers/ldap_simple.php plugins/password/drivers/pam.php plugins/password/drivers/poppassd.php plugins/password/drivers/sasl.php plugins/password/drivers/sql.php plugins/password/drivers/virtualmin.php plugins/password/drivers/vpopmaild.php plugins/password/drivers/ximss.php plugins/password/drivers/xmail.php plugins/password/localization/az_AZ.inc plugins/password/localization/bg_BG.inc plugins/password/localization/ca_ES.inc plugins/password/localization/cs_CZ.inc plugins/password/localization/da_DK.inc plugins/password/localization/de_CH.inc plugins/password/localization/de_DE.inc plugins/password/localization/en_US.inc plugins/password/localization/es_AR.inc plugins/password/localization/es_ES.inc plugins/password/localization/et_EE.inc plugins/password/localization/fi_FI.inc plugins/password/localization/fr_FR.inc plugins/password/localization/hu_HU.inc plugins/password/localization/it_IT.inc plugins/password/localization/ja_JP.inc plugins/password/localization/lt_LT.inc plugins/password/localization/lv_LV.inc plugins/password/localization/nl_NL.inc plugins/password/localization/pl_PL.inc plugins/password/localization/pt_BR.inc plugins/password/localization/pt_PT.inc plugins/password/localization/ru_RU.inc plugins/password/localization/sl_SI.inc plugins/password/localization/sv_SE.inc plugins/password/localization/tr_TR.inc plugins/password/localization/zh_TW.inc plugins/password/package.xml plugins/password/password.js plugins/password/password.php plugins/show_additional_headers/show_additional_headers.php plugins/squirrelmail_usercopy/config.inc.php.dist plugins/squirrelmail_usercopy/squirrelmail_usercopy.php plugins/subscriptions_option/localization/cs_CZ.inc plugins/subscriptions_option/localization/de_CH.inc plugins/subscriptions_option/localization/de_DE.inc plugins/subscriptions_option/localization/en_US.inc plugins/subscriptions_option/localization/es_ES.inc plugins/subscriptions_option/localization/et_EE.inc plugins/subscriptions_option/localization/ja_JP.inc plugins/subscriptions_option/localization/pl_PL.inc plugins/subscriptions_option/localization/ru_RU.inc plugins/subscriptions_option/localization/sv_SE.inc plugins/subscriptions_option/localization/zh_TW.inc plugins/subscriptions_option/subscriptions_option.php plugins/userinfo/localization/cs_CZ.inc plugins/userinfo/localization/da_DK.inc plugins/userinfo/localization/de_CH.inc plugins/userinfo/localization/en_US.inc plugins/userinfo/localization/es_ES.inc plugins/userinfo/localization/et_EE.inc plugins/userinfo/localization/ja_JP.inc plugins/userinfo/localization/pl_PL.inc plugins/userinfo/localization/pt_PT.inc plugins/userinfo/localization/ru_RU.inc plugins/userinfo/localization/sv_SE.inc plugins/userinfo/localization/zh_TW.inc plugins/userinfo/userinfo.js plugins/userinfo/userinfo.php plugins/vcard_attachments/localization/cs_CZ.inc plugins/vcard_attachments/localization/de_CH.inc plugins/vcard_attachments/localization/de_DE.inc plugins/vcard_attachments/localization/en_US.inc plugins/vcard_attachments/localization/es_ES.inc plugins/vcard_attachments/localization/et_EE.inc plugins/vcard_attachments/localization/ja_JP.inc plugins/vcard_attachments/localization/pl_PL.inc plugins/vcard_attachments/localization/ru_RU.inc plugins/vcard_attachments/localization/sv_SE.inc plugins/vcard_attachments/localization/zh_TW.inc plugins/vcard_attachments/package.xml plugins/vcard_attachments/skins/default/vcard.png plugins/vcard_attachments/skins/default/vcard_add_contact.png plugins/vcard_attachments/vcard_attachments.php plugins/vcard_attachments/vcardattach.js plugins/virtuser_file/virtuser_file.php plugins/virtuser_query/virtuser_query.php