Compare commits

...
276 Commits
Author SHA1 Message Date
smizrahi 9f96905c3a feat(core): Add URL encryption for GDPR compliancy. Remove log 2024-01-02 13:48:54 +01:00
smizrahi 10d789fb1f feat(core): Add URL encryption for GDPR compliancy 2023-12-22 11:33:40 +01:00
smizrahi d505b874f7 Merge branch 'master' into feature-gdpr 2023-12-20 18:01:40 +01:00
Hivert Quentin cbeaa4cc11 chore(ytnef): missing symbols with centos 2023-12-20 17:02:32 +01:00
Hivert Quentin 427f7f4b4f feature(contact): Show 100 first results for global address book. Fix commit #17eca6f 2023-12-20 15:36:37 +01:00
Hivert Quentin d68bbc836c chore(js/css): update generated files 2023-12-19 01:00:44 -05:00
smizrahi 51b6c0d9a3 fix(ui): Fix text/placeholder overlaps when Chrome autofill applies 2023-12-18 22:42:13 +01:00
Hivert Quentin 5f2e4889f3 chore(js/css): update generated files 2023-12-18 08:27:23 -05:00
smizrahi 37379ba416 fix(preferences): Fix error 500 when changing password 2023-12-18 14:24:50 +01:00
smizrahi 17eca6f96a feature(contact): Show 100 first results for global address book 2023-12-18 08:34:53 +01:00
Hivert Quentin 26c7462eb0 fix(contacts): Custom contacts categories in sogo.conf wasn't working 2023-12-14 18:11:10 +01:00
smizrahi 0d471efe97 Update changelog 2023-12-12 13:00:46 +01:00
Hivert Quentin 8375c9352e chore(js/css): update generated files 2023-12-12 06:10:11 -05:00
smizrahi f85c53190c fix(addressbook): Fix exception when using multiple address book source and searching. Fixes #5906. 2023-12-12 12:08:25 +01:00
Hivert Quentin 3cd860838c chore(js/css): update generated files 2023-12-12 01:00:56 -05:00
Hivert Quentin 377cff935f chore(release): 5.9.1 2023-12-11 15:49:23 +01:00
Hivert Quentin 06a6ed7768 i18n(cy): Update Welsh translations 2023-12-11 14:32:48 +01:00
Hivert Quentin 0e50d37531 i18n(uk): Update Ukrainian translations 2023-12-11 14:32:47 +01:00
Hivert Quentin 8d1fc1e306 i18n(tr_TR): Update TurkishTurkey translations 2023-12-11 14:32:47 +01:00
Hivert Quentin 10369aa654 i18n(sv): Update Swedish translations 2023-12-11 14:32:46 +01:00
Hivert Quentin 057a1ba105 i18n(es_ES): Update SpanishSpain translations 2023-12-11 14:32:46 +01:00
Hivert Quentin 527fded19c i18n(es_AR): Update SpanishArgentina translations 2023-12-11 14:32:45 +01:00
Hivert Quentin ca21bf4ce1 i18n(sl_SI): Update Slovenian translations 2023-12-11 14:32:45 +01:00
Hivert Quentin cd7e06c2ed i18n(sk): Update Slovak translations 2023-12-11 14:32:44 +01:00
Hivert Quentin 9126cae27d i18n(sr@latin): Update SerbianLatin translations 2023-12-11 14:32:43 +01:00
Hivert Quentin ed444fdc22 i18n(sr): Update Serbian translations 2023-12-11 14:32:43 +01:00
Hivert Quentin 3809ec3904 i18n(ru): Update Russian translations 2023-12-11 14:32:41 +01:00
Hivert Quentin d9c716c1b6 i18n(ro_RO): Update Romanian translations 2023-12-11 14:32:41 +01:00
Hivert Quentin e4f07c466e i18n(pt): Update Portuguese translations 2023-12-11 14:32:40 +01:00
Hivert Quentin 6d270fe7a4 i18n(nn_NO): Update NorwegianNynorsk translations 2023-12-11 14:32:40 +01:00
Hivert Quentin 3d5f352111 i18n(nb_NO): Update NorwegianBokmal translations 2023-12-11 14:32:39 +01:00
Hivert Quentin fe7369e427 i18n(sr_ME@latin): Update Montenegrin translations 2023-12-11 14:32:39 +01:00
Hivert Quentin d6c9ecdeb0 i18n(mk_MK): Update Macedonian translations 2023-12-11 14:32:39 +01:00
Hivert Quentin a9ecaed5fa i18n(lv): Update Latvian translations 2023-12-11 14:32:38 +01:00
Hivert Quentin 21741954dd i18n(kk): Update Kazakh translations 2023-12-11 14:32:38 +01:00
Hivert Quentin 41fb85e6a3 i18n(ja): Update Japanese translations 2023-12-11 14:32:38 +01:00
Hivert Quentin 53036b323f i18n(it): Update Italian translations 2023-12-11 14:32:36 +01:00
Hivert Quentin a16d29b70b i18n(id_ID): Update Indonesian translations 2023-12-11 14:32:35 +01:00
Hivert Quentin 2de6241220 i18n(is): Update Icelandic translations 2023-12-11 14:32:34 +01:00
Hivert Quentin 0b22fa6cd5 i18n(hu): Update Hungarian translations 2023-12-11 14:32:34 +01:00
Hivert Quentin 694c21de99 i18n(he): Update Hebrew translations 2023-12-11 14:32:34 +01:00
Hivert Quentin 5aa7ebf2ae i18n(gl): Update Galician translations 2023-12-11 14:32:33 +01:00
Hivert Quentin dba2332864 i18n(fi): Update Finnish translations 2023-12-11 14:32:33 +01:00
Hivert Quentin 402c3d0a1f i18n(nl): Update Dutch translations 2023-12-11 14:32:33 +01:00
Hivert Quentin 76911fd445 i18n(da_DK): Update Danish translations 2023-12-11 14:32:32 +01:00
Hivert Quentin 029a82143f i18n(cs): Update Czech translations 2023-12-11 14:32:32 +01:00
Hivert Quentin 715d25d40a i18n(hr_HR): Update Croatian translations 2023-12-11 14:32:31 +01:00
Hivert Quentin fc2a82c3b0 i18n(zh_TW): Update ChineseTaiwan translations 2023-12-11 14:32:31 +01:00
Hivert Quentin cf87022348 i18n(zh_CN): Update ChineseChina translations 2023-12-11 14:32:31 +01:00
Hivert Quentin 3040543c79 i18n(ca): Update Catalan translations 2023-12-11 14:32:31 +01:00
Hivert Quentin 3815bc1c5e i18n(bg_BG): Update Bulgarian translations 2023-12-11 14:32:31 +01:00
Hivert Quentin 2afbcbeaed i18n(bs_BA): Update Bosnian translations 2023-12-11 14:32:31 +01:00
Hivert Quentin 33f3a1b716 i18n(eu): Update Basque translations 2023-12-11 14:32:31 +01:00
Hivert Quentin 28d883dac1 i18n(ar): Update Arabic translations 2023-12-11 14:32:31 +01:00
Hivert Quentin c75f210864 i18n(pl): Update Polish translations 2023-12-11 14:32:31 +01:00
Hivert Quentin cfab85624b i18n(pt_BR): Update BrazilianPortuguese translations 2023-12-11 14:32:30 +01:00
Hivert Quentin 6e9e7f98ef i18n(lt): Update Lithuanian translations 2023-12-11 14:32:30 +01:00
Hivert Quentin 8a4bc2a716 i18n(de): Update German translations 2023-12-11 14:32:30 +01:00
Hivert Quentin fd5e28ec77 chore(js/css): update theme default 2023-12-11 14:06:44 +01:00
Hivert Quentin 1961f1a914 chore(js/css): update generated files 2023-12-11 14:06:44 +01:00
Hivert Quentin 0704949056 chore(npm): Update CKEditor to version 4.22.1 2023-12-11 14:06:44 +01:00
WoodySlum 85870d6b16 Merge pull request #350 from kovacs-andras/master
docs(guides): update documentation url
2023-12-11 14:03:27 +01:00
Andras Kovacs 669dcaa242 update documentation url 2023-12-05 14:38:11 +01:00
Hivert Quentin 21571ab1e0 fix(calendar): If content is NSData, properly convert it to NSString 2023-12-05 10:58:48 +01:00
smizrahi 7d66699219 feat(password): Add specials characters to POLICY_MIN_SPECIAL_SYMBOLS 2023-12-04 16:43:16 +01:00
smizrahi cd50ee0176 feat(core): Add mobile provisioning download for Apple's devices. Fix nil port when using standard http configuration (port 80 and 443) 2023-12-04 10:36:41 +01:00
WoodySlum da24c0d0a7 Merge pull request #348 from marvin-j97/patch-1
style(login): Replace envelop icon with key on login page
2023-11-28 16:44:03 +01:00
Marvin 7b15327b19 Use vpn_key icon 2023-11-28 15:47:35 +01:00
Hivert Quentin 6eae3f2b16 i18n(gl): Change Galician name to Galego 2023-11-28 08:49:31 +01:00
smizrahi 3cd9910638 fix(core): Fix user profile retrieval when using utf8mb4_bin collation - in this case the SQL results may return NSdata instead of NSString. Fixes #5902. 2023-11-27 15:51:33 +01:00
smizrahi fb542e6e26 Merge branch 'feature-5726-gab-autocomplete' 2023-11-27 11:19:17 +01:00
smizrahi 60e6bb5b3b Merge branch 'master' into feature-5726-gab-autocomplete 2023-11-27 11:18:31 +01:00
Hivert Quentin 86c9b4ec07 chore(js/css): update generated files 2023-11-24 01:01:04 -05:00
Hivert Quentin 2e670f7e62 feat(mail): Add an option in Preferences to display full email instead of name alone in mailboxes 2023-11-23 14:09:57 +01:00
Hivert Quentin 8babb23c58 chore(js/css): update generated files 2023-11-23 01:00:53 -05:00
Hivert Quentin 1ae02ebd70 fix(UI): selectAll button was still keeping alle items in memory 2023-11-22 11:07:58 +01:00
smizrahi aa53524751 feat(mail): Add SOGoMailHideInlineAttachments option to hide attachments when inline. Fixes #5490. Fix default values in sogo.conf. 2023-11-20 11:57:26 +01:00
Marvin 6734b4ea16 design(RootPage): Use lock icon for password field
https://pictogrammers.com/library/mdi/icon/key/
2023-11-18 15:46:46 +01:00
Hivert Quentin bb943e414d fix(sogo-tool): add protection to expire-sessions if the input is not an integer 2023-11-17 15:50:41 +01:00
smizrahi ca2c5c188e feat(mail): Add SOGoMailHideInlineAttachments option to hide attachments when inline. Fixes #5490. Improve bracket icon. 2023-11-17 10:46:22 +01:00
Hivert Quentin 0023a9ce3c fix(calendar): do not let DURATION and DTEND be both present in vevent 2023-11-17 09:24:12 +01:00
Hivert Quentin 1e8127b461 fix(calendar): properly set the start date range for yearly events 2023-11-16 11:18:23 +01:00
smizrahi 6b08f2e4de fix(core): Fix calendar issues with Thunderbird related to vlist exclusion. Fix error log on photo. Closes #5885 2023-11-13 23:02:07 +01:00
Hivert Quentin 605d8b1169 chore(js/css): update generated files 2023-11-11 00:00:50 -05:00
Hivert Quentin c872fb4d9c feat(preferences): can set SOGoForwardConstraints to 3 to accept both internal and external domains from SOGoForwardConstraintsDomains 2023-11-10 15:07:14 +01:00
smizrahi 11ad203388 feat(addressbook): Add global address book autocomplete on vlist 2023-11-09 14:08:16 +01:00
smizrahi 0b1f2b0405 feat(addressbook): Add global address book autocomplete on vlist 2023-11-08 22:36:40 +01:00
smizrahi 0560efd1f6 feat(addressbook): Add global address book autocomplete on vlist 2023-11-08 18:10:06 +01:00
smizrahi d1634054d6 fix(mail): Clean temporary files when mail is sent. Check if body part is NGMimeFileData to add path for cleaning. 2023-11-07 10:38:54 +01:00
Hivert Quentin 7481ccf370 fix(hmtl): prevent html injection of tag form 2023-11-06 14:04:53 +01:00
Hivert Quentin 65374e44fc fix(scripts): wrong type for c_value in script for complete unicode coverage 2023-11-06 09:21:52 +01:00
Hivert Quentin f8b7986501 fix(template): wrong ending tag 2023-11-04 11:32:17 +01:00
Hivert Quentin 8da3d06117 chore(js/css): update generated files 2023-11-04 00:00:46 -04:00
Hivert Quentin d0b4b50456 feat(core): add SOGoDisableExport option to disable export for mail, calendar and contacts 2023-11-03 18:20:12 +01:00
Hivert Quentin e0ef59d5f2 feat(core): Add SOGoDisableSharingAnyAuthUser option to disable sharing to any user authenticated for mail, contacts and calendar 2023-11-03 17:01:49 +01:00
Hivert Quentin 6e60b10755 feat(ux): select all button will unselect all if click again 2023-11-03 15:13:48 +01:00
Hivert Quentin 0df0e34073 doc(sogo-tool): Add doc to detailed sogo-tool 2023-11-02 17:20:05 +01:00
smizrahi f41ab2ed0c feat(mail): Add SOGoMailHideInlineAttachments option to hide attachments when inline. Fixes #5490. 2023-11-02 09:45:51 +01:00
smizrahi abce5f42cf feat(mail): Add SOGoMailHideInlineAttachments option to hide attachments when inline. Fixes #5490. 2023-11-01 18:25:01 +01:00
smizrahi 277b73c663 docs(calendar+addressbook): Add provisioning profile doc 2023-11-01 15:57:42 +01:00
smizrahi f35a37bbdd fix(mail): Clean temporary files when mail is sent 2023-11-01 15:40:33 +01:00
smizrahi 9c0948518b fix(mail): Fix duplication of images when saving draft. Fixes #5888. Clean old code 2023-11-01 10:29:25 +01:00
smizrahi 4fe01f96d7 feat(core): Add mobile provisioning download for Apple's devices. Update translations (incompatible icon under windows) 2023-10-31 12:15:00 +01:00
smizrahi a273f7aca5 feat(core): Add mobile provisioning download for Apple's devices. Update translations (incompatible icon under windows) 2023-10-31 12:10:05 +01:00
Hivert Quentin 8f635d4856 chore(js/css): update generated files 2023-10-31 00:00:55 -04:00
Hivert Quentin 068126cd22 chore(js/css): update generated files 2023-10-19 00:00:47 -04:00
smizrahi 9ef49bb56f feat(core): Add URL encryption for GDPR compliancy (fix some dav issues) 2023-10-18 16:37:08 +02:00
smizrahi 54a1f922f9 Merge branch 'master' into feature-gdpr 2023-10-18 13:29:29 +02:00
smizrahi 65f8336255 feat(core): Add mobile provisioning download for Apple's devices. Fix shared calendar and contacts 2023-10-18 11:43:35 +02:00
WoodySlum c67ad819ab Merge pull request #347 from leecher1337/fix-5884
fix(eas): Fix for excessive memory usage on mailboxes with thousands of folders (by @leecher1337)
2023-10-17 21:03:39 +02:00
smizrahi 64a6319635 feat(core): Add URL encryption for GDPR compliancy (refactoring) 2023-10-17 16:43:50 +02:00
smizrahi 31c6f78913 feat(core): Add URL encryption for GDPR compliancy 2023-10-17 16:17:25 +02:00
Hivert Quentin 4b45dc7584 fix(translation): incorret value causing trouble to transifex 2023-10-17 09:23:33 +02:00
leecher1337 063fb2be3d Fix for excessive memory usage on mailboxes with thousands of folders 2023-10-16 16:12:24 +02:00
smizrahi ee9899fe3d fix(mail): Fix duplication of images when saving draft. Fixes #5888 2023-10-13 10:29:42 +02:00
smizrahi 81f2d88db3 fix(core): Add cache for CDefaultsSize to avoid spamming SQL request (pgsql) 2023-10-11 12:57:45 +02:00
Hivert Quentin baf6bdfedf chore(js/css): update generated files 2023-10-11 06:23:20 -04:00
Hivert Quentin c569ed2d77 fix(http): set correct Accept header when dowloading eml file 2023-10-11 11:32:26 +02:00
smizrahi 87e1240ace fix(core): Add cache for CDefaultsSize to avoid spamming SQL request 2023-10-10 17:37:59 +02:00
Hivert Quentin b396a277f0 feat(reply): add references header when replying 2023-10-04 14:55:27 +02:00
Hivert Quentin e460e5377c chore(js/css): update generated files 2023-10-04 00:00:47 -04:00
Hivert Quentin 1d3da92bab fix(addressbook): autocomplete correctly return two contacts from two ldap with same id 2023-10-03 14:28:57 +02:00
Hivert Quentin b1d8475520 chore(language): fixed french and double key 2023-10-03 09:12:02 +02:00
smizrahi d38cc86d29 feat(core): Add mobile provisioning download for Apple's devices. Fix build issue 2023-09-28 09:18:43 +02:00
Hivert Quentin df2f73f1f6 chore(js/css): update generated files 2023-09-28 00:00:48 -04:00
smizrahi 19915e3dff feat(core): Add mobile provisioning download for Apple's devices 2023-09-27 17:28:29 +02:00
smizrahi 6d16ee7988 feat(core): Add mobile provisioning download for Apple's devices 2023-09-27 17:28:29 +02:00
Hivert Quentin 5b73a60aa6 chore(release): 5.9.0 2023-09-27 10:20:23 +02:00
Hivert Quentin 487ad1a849 i18n(cy): Update French translations 2023-09-25 16:33:17 +02:00
Hivert Quentin 0e2deac164 i18n(cy): Update Welsh translations 2023-09-25 16:24:51 +02:00
Hivert Quentin 5ee12a4507 i18n(uk): Update Ukrainian translations 2023-09-25 16:24:34 +02:00
Hivert Quentin dfc1ab7e0e i18n(tr_TR): Update TurkishTurkey translations 2023-09-25 16:24:34 +02:00
Hivert Quentin d5c1b742f4 i18n(sv): Update Swedish translations 2023-09-25 16:24:34 +02:00
Hivert Quentin ea8f04ac7c i18n(es_ES): Update SpanishSpain translations 2023-09-25 16:24:06 +02:00
Hivert Quentin 64865aa85a i18n(es_AR): Update SpanishArgentina translations 2023-09-25 16:24:06 +02:00
Hivert Quentin b08b5dd79a i18n(sl_SI): Update Slovenian translations 2023-09-25 16:24:05 +02:00
Hivert Quentin 8a10213b75 i18n(sk): Update Slovak translations 2023-09-25 16:24:05 +02:00
Hivert Quentin 1710112c4e i18n(sr@latin): Update SerbianLatin translations 2023-09-25 16:24:05 +02:00
Hivert Quentin 2242c21323 i18n(sr): Update Serbian translations 2023-09-25 16:24:05 +02:00
Hivert Quentin da609c448e i18n(ru): Update Russian translations 2023-09-25 16:08:07 +02:00
Hivert Quentin 9aebf9bf02 i18n(ro_RO): Update Romanian translations 2023-09-25 16:08:07 +02:00
Hivert Quentin 46d5779088 i18n(pt): Update Portuguese translations 2023-09-25 16:08:06 +02:00
Hivert Quentin 92d38c108e i18n(pl): Update Polish translations 2023-09-25 16:08:06 +02:00
Hivert Quentin 02d32d5f6c i18n(nn_NO): Update NorwegianNynorsk translations 2023-09-25 16:08:06 +02:00
Hivert Quentin 0368bad3c5 i18n(nb_NO): Update NorwegianBokmal translations 2023-09-25 16:08:06 +02:00
Hivert Quentin 865aba3248 i18n(sr_ME@latin): Update Montenegrin translations 2023-09-25 16:08:06 +02:00
Hivert Quentin bbb09555f0 i18n(mk_MK): Update Macedonian translations 2023-09-25 16:08:06 +02:00
Hivert Quentin 493f5f625f i18n(lv): Update Latvian translations 2023-09-25 16:08:05 +02:00
Hivert Quentin ed5199aee7 i18n(kk): Update Kazakh translations 2023-09-25 16:08:05 +02:00
Hivert Quentin 2fee2f8782 i18n(it): Update Italian translations 2023-09-25 16:08:03 +02:00
Hivert Quentin e50b66b62e i18n(id_ID): Update Indonesian translations 2023-09-25 16:08:03 +02:00
Hivert Quentin ee3ed0779c i18n(is): Update Icelandic translations 2023-09-25 16:08:02 +02:00
Hivert Quentin e3d392b12a i18n(hu): Update Hungarian translations 2023-09-25 16:08:01 +02:00
Hivert Quentin 41842e2d39 i18n(he): Update Hebrew translations 2023-09-25 16:08:01 +02:00
Hivert Quentin 86e5872dc1 i18n(gl): Update Galician translations 2023-09-25 16:08:01 +02:00
Hivert Quentin 7ff0699648 i18n(fi): Update Finnish translations 2023-09-25 16:08:01 +02:00
Hivert Quentin 03d43079d3 i18n(nl): Update Dutch translations 2023-09-25 16:08:01 +02:00
Hivert Quentin 9ef58e7c6d i18n(da_DK): Update Danish translations 2023-09-25 16:08:00 +02:00
Hivert Quentin 1a5408c2b3 i18n(cs): Update Czech translations 2023-09-25 16:08:00 +02:00
Hivert Quentin b1df5d4d14 i18n(hr_HR): Update Croatian translations 2023-09-25 16:08:00 +02:00
Hivert Quentin e330c552c3 i18n(zh_TW): Update ChineseTaiwan translations 2023-09-25 16:08:00 +02:00
Hivert Quentin 300cfe4e27 i18n(zh_CN): Update ChineseChina translations 2023-09-25 16:08:00 +02:00
Hivert Quentin c82a48f96f i18n(ca): Update Catalan translations 2023-09-25 16:07:59 +02:00
Hivert Quentin 6606f9865a i18n(bg_BG): Update Bulgarian translations 2023-09-25 16:07:58 +02:00
Hivert Quentin ce6efafe54 i18n(pt_BR): Update BrazilianPortuguese translations 2023-09-25 16:07:58 +02:00
Hivert Quentin fc93272d70 i18n(bs_BA): Update Bosnian translations 2023-09-25 16:07:58 +02:00
Hivert Quentin 6293f62d83 i18n(eu): Update Basque translations 2023-09-25 16:07:56 +02:00
Hivert Quentin 132912b566 i18n(ar): Update Arabic translations 2023-09-25 16:07:56 +02:00
Hivert Quentin 97d01fb14e i18n(ja): Update Japanese translations 2023-09-25 16:07:56 +02:00
Hivert Quentin 4974648c33 i18n(lt): Update Lithuanian translations 2023-09-25 16:07:56 +02:00
Hivert Quentin 0f0ecf71c3 i18n(de): Update German translations 2023-09-25 16:07:55 +02:00
Hivert Quentin ff263b99fe fix(contacts): Fix 5875 2023-09-25 15:11:45 +02:00
Hivert Quentin 8604389152 fix(translation): french syntax 2023-09-25 10:33:28 +02:00
Hivert Quentin bba1da1b2b fix(sieve): sieve availibility was checked even if we disable it 2023-09-18 11:44:45 +02:00
Hivert Quentin bafb1ffc69 chore(js/css): update generated files 2023-09-14 00:00:46 -04:00
Hivert Quentin 5534d4b6ee fix(notification): body was not send + missing tooltip 2023-09-13 14:52:08 +02:00
Hivert Quentin 8efdee4ef9 chore(html): change support mail to sogo 2023-09-12 11:23:25 +02:00
Hivert Quentin 320938445c chore(js/css): update generated files 2023-09-12 00:00:45 -04:00
smizrahi dc23721b75 fix(js): Fix url redirection to null when disconnected. Closes #5844. 2023-09-11 14:55:44 +02:00
smizrahi 4abf12b4bb fix(preferences): Signature could not be saved when SOGoCreateIdentitiesDisabled is set to YES and no signature is on identity 2023-09-11 11:56:10 +02:00
smizrahi 1a257c7768 fix(calendar): Fix Out of range value for column 'c_startdate' SQL error. Add log. 2023-09-11 11:34:20 +02:00
Hivert Quentin a1528bcc03 fix(cas): correction on pgtUrl 2023-09-09 11:43:24 +02:00
Hivert Quentin 041bb98e9d chore(cas): make script both python2 and 3 compatible 2023-09-09 10:15:34 +02:00
Hivert Quentin f2df405616 fix(calendar): some kinds of events are not shown 2023-09-07 13:17:44 +02:00
Hivert Quentin 68b1c1e5df fix(CAS): key based on tciket was too long for memecached 2023-09-07 09:14:24 +02:00
smizrahi f0719ee409 fix(addressbook): Remove vlist from dav request for Thunderbird only. In Thunderbird 115, using vlist results in contact duplication 2023-09-06 14:02:05 +02:00
smizrahi 87b89ed094 docs(calendar): Update Apple's calendar instructions for 5.8.9 2023-09-04 10:23:29 +02:00
Hivert Quentin 402e54a6fc chore(js/css): update generated files 2023-09-02 00:00:53 -04:00
Hivert Quentin 16af360006 fix(notifications): bug with older user profile 2023-09-01 17:21:38 +02:00
Hivert Quentin 27c9dd36e5 chore(js/css): update generated files 2023-09-01 00:00:53 -04:00
Hivert Quentin 3309346b46 feat(notificaiton) move custom message to body instead of subject 2023-08-31 15:22:56 +02:00
Hivert Quentin d133961302 chore(js/css): update generated files 2023-08-31 00:00:46 -04:00
Hivert Quentin 6b647050fc feat(notification): add custom message 2023-08-30 17:04:49 +02:00
Hivert Quentin acaddf5d4f chore(js/css): update generated files 2023-08-30 00:00:46 -04:00
Hivert Quentin 82b69d1414 feat(notification): add enotify sieve script 2023-08-29 10:07:53 +02:00
smizrahi 4f7c73143f fix(calendar): Fix MacOS X Ventura calendar support. Fixes #5639 2023-08-22 18:11:53 +02:00
smizrahi 1c93796ae1 test(timeout): Change some timeouts fo tests 2023-08-17 11:27:04 +02:00
smizrahi fce02db7d1 fix(calendar): Revert commit e47566e3a and change HTTP status from 409 to 403 when event has a sequence number lower than than the new one. This should fix some Apple's duplication events 2023-08-16 17:18:59 +02:00
Hivert Quentin c5814d60a4 fix(calendar): recurrenid wasn't working properly 2023-08-16 17:18:59 +02:00
Hivert Quentin f3ddc867cf chore(js/css): update generated files 2023-08-16 07:30:31 -04:00
smizrahi 5e471c5d66 fix(js): Fix invalid sass version 2023-08-16 13:27:34 +02:00
smizrahi a1a07ddcee chore(js/css): update generated files 2023-08-16 06:59:10 +00:00
smizrahi cab5bc011e fix(calendar): Avoid event duplication when adding an ics with an older sequence number and event already added in calendar 2023-08-14 17:18:37 +02:00
smizrahi 4d2c77195e fix(calendar): When an event has no date (weird case), it is not possible to remove the event (NSException) 2023-08-14 17:08:27 +02:00
Hivert Quentin e959583390 fix(mail): Templates namespace is correctly returned 2023-08-09 16:30:33 +02:00
smizrahi 82178eeba9 fix(calendar): Fix NAME:NSInvalidArgumentException REASON:SOGoAppointmentFolder(instance) does not recognize davCalendarProxyReadFor 2023-08-09 14:44:35 +02:00
smizrahi 382b8a0a1a fix(calendar): Do not send tentative participation mail when user open mail or add ics to calendar app 2023-08-08 15:49:15 +02:00
Hivert Quentin f3981c4276 fix(calendar): deleted occurence of repeated event could cause some troubles with other imrbicated occurences
The fix is within SOPE/NGCards/iCalRecurrenceCalculator.m:244, the others files are juste modified to make the code more readable
2023-08-07 17:16:50 +02:00
Hivert Quentin f5f74f751a fix(preferences): Fix language list sorting on preferences page 2023-08-04 10:36:50 +02:00
Hivert Quentin 528d178256 chore(packaging): debian 12 2023-08-02 11:36:36 +02:00
Hivert Quentin f3dce5d50b chore(js/css): update generated files 2023-08-01 00:00:53 -04:00
smizrahi b7e529dd49 fix(mail): When signature is too big and changing identity, a 'regex too big' error occurs. In this case just add signature at the end. Fixes #5695. 2023-07-31 10:41:37 +02:00
smizrahi 68cef9ae03 feat(calendar): Add CALDAV Mac OS X Sonoma (14) support 2023-07-31 09:19:23 +02:00
smizrahi 87bbba0c8d feat(calendar): Add CALDAV Mac OS X Sonoma (14) support 2023-07-31 09:19:15 +02:00
smizrahi e0d76fba65 fix(login): Fix language list sorting on login page 2023-07-27 14:15:26 +02:00
Hivert Quentin 5b352635b8 chore(js/css): update generated files 2023-07-27 00:00:52 -04:00
smizrahi 8582e423bd feat(preferences): Add user preferences to enable / disable signature for new mail, reply and forward 2023-07-26 19:38:57 +02:00
Hivert Quentin 577d991d42 chore(js/css): update generated files 2023-07-26 00:00:48 -04:00
smizrahi 5d315ac180 fix(addressbook): Fix broken image in shared address book when image is null 2023-07-25 20:39:49 +02:00
smizrahi 326bc68f4c fix(preferences): Fix the issue when signature is too long and preferences are not saved. Note that MySQL profile table should be updated with a migration script 2023-07-25 17:22:11 +02:00
smizrahi ec27f7e714 fix(user): Replace length with count for NSArray 2023-07-25 16:08:08 +02:00
smizrahi e731303439 docs(ldap): Fix ldap field invalid names 2023-07-25 10:08:23 +02:00
Hivert Quentin 0cb896d4fe chore(js/css): update generated files 2023-07-21 00:00:46 -04:00
Hivert Quentin ba4c7a5300 fix(preferences): add autocomplete for recipients in automatic forward 2023-07-20 09:26:16 +02:00
Hivert Quentin 40512f6b06 chore(js/css): update generated files 2023-07-20 00:00:48 -04:00
smizrahi d1a51e8340 feat(core): Add in user settings to force password change for a user when logging - update documentation 2023-07-17 11:07:43 +02:00
Hivert Quentin bd8b49d2c9 fix(calendar): Repeated events didn't use the correct timezone in some cases 2023-07-12 15:00:24 +02:00
smizrahi 57ba5397e5 fix(calendar): Fix duplicates in series appointments for Apple's Calendar 2023-07-12 08:49:01 +02:00
Hivert Quentin d55030323b chore(js/css): update generated files 2023-07-12 00:00:46 -04:00
smizrahi 9918f75580 feat(core): Add in user settings to force password change for a user when logging 2023-07-11 18:48:54 +02:00
smizrahi 7600e9e179 fix(calendar): Fix duplicates in series appointments for Apple's Calendar 2023-07-11 15:26:31 +02:00
smizrahi a48440754c fix(ui): Disable send button while mail is sending. Fixes #5825. 2023-07-10 11:22:08 +02:00
Hivert Quentin e53e5493c6 style(UX): add missing tooltips 2023-07-07 17:35:32 +02:00
WoodySlum 6eee167876 Merge pull request #345 from modir/patch-1
fix(doc): Update SOGo"s developer
2023-07-05 10:03:14 +02:00
Raffael 8e4d801df0 Changing "Inverse" to "Alinto" 2023-06-29 00:10:49 +02:00
smizrahi b666137a70 fix(preferences): Disable auxiliary mail accounts creation when SOGoCreateIdentitiesDisabled is enabled 2023-06-28 14:36:19 +02:00
Hivert Quentin a95f6c0799 chore(js/css): update generated files 2023-06-21 18:00:51 -04:00
smizrahi 09d03261af feat(preferences): Add subtext in vacation filter to explain the behavior 2023-06-21 16:33:54 +02:00
smizrahi 8a8dcae029 fix(calendar): Add negative timestamp support to use date before 1970-01-01. Fixes #4914 (partial) 2023-06-21 15:51:18 +02:00
smizrahi fccc910791 docs(installation): Add RHEL9 support 2023-06-20 22:27:26 +02:00
smizrahi e42f28cf22 chore(release): 5.8.4 2023-06-20 22:27:08 +02:00
smizrahi eb42e1787c fix(preferences): Fix NSException issue when SOGoCreateIdentitiesDisabled is set 2023-06-20 22:25:12 +02:00
Hivert Quentin 8baa770681 chore(js/css): update generated files 2023-06-14 18:00:51 -04:00
Hivert Quentin af59e57fc0 fix(mail): Fix a bug when pressing the key DEL more than once to delete mails 2023-06-14 17:47:23 +02:00
smizrahi 22372a7598 fix(calendar): Fix calendar green dot stucks in original day when browser is opened for days and day change. Fixes #5783 2023-06-12 10:18:26 +02:00
Hivert Quentin 117540dbb1 chore(npm): update ckeditor with galician language 2023-06-12 08:56:30 +02:00
Hivert Quentin 665861d830 i181(gl_ES) add Galician language support 2023-06-12 08:56:30 +02:00
smizrahi f9fdad251d fix(mail): Removed invalid HTML tags in mail body. Fixes #5755 2023-06-07 14:42:21 +02:00
smizrahi f22672cef8 fix(calendar): Fix calendar green dot stucks in original day when browser is opened for days and day changed 2023-06-07 10:06:59 +02:00
smizrahi fa5264e822 fix(calendar): Increase timeout to 60 seconds for large ics calendar. Closes #5781. 2023-06-06 09:04:01 +02:00
smizrahi b68a215ea2 fix(calendar): Fix regression on calendar event modification. Start date was set to 1970. Fixes #5777. 2023-06-01 17:13:49 +02:00
smizrahi e87d511e79 chore(js/css): update generated files 2023-05-30 16:32:40 +00:00
Hivert Quentin 854e83c22c fix(mail): autocomplete can find two lists named the same but in different address books 2023-05-30 18:29:55 +02:00
smizrahi 24200e7d34 chore(js/css): update generated files 2023-05-30 16:26:08 +00:00
smizrahi 9da6521cfb Merge branch 'master' into SOGo-5.8 2023-05-30 18:25:41 +02:00
Hivert Quentin c64fabd09b fix link in changelog + add files in gitignore 2023-05-30 14:59:04 +02:00
smizrahi a44e96755a Update gitignore 2023-05-24 18:17:56 +02:00
smizrahi 1a06996381 test(core): Add junit output format 2023-05-24 18:17:41 +02:00
smizrahi a7ecb5816d Merge branch 'SOGo-5.8' into fix-green-dot 2023-05-24 11:13:12 +02:00
smizrahi e3a7acd329 Merge branch 'SOGo-5.8' into fix-5768 2023-05-23 15:54:54 +02:00
smizrahi 23da4cca7b fix(mail): Signature images are not handled properly in forwarded or replied to messages. Save extracted body parts (for signature) into file system. Fixes #5768 2023-05-23 15:50:48 +02:00
smizrahi ab954a150e fix(mail): Fix email without subject according to RFC 2822. Fixes #5743 2023-05-22 18:23:28 +02:00
root 7b13006390 chore(js/css): update generated files 2023-05-22 16:21:59 +00:00
smizrahi 139ef15284 fix(calendar): Fix search issue on repeated events when one title is changed the event could not be found 2023-05-22 16:17:32 +02:00
smizrahi d436afb054 fix(mail): Signature images are not handled properly in forwarded or replied to messages. Save extracted body parts (for signature) into file system. Fixes #5768 2023-05-22 10:54:48 +02:00
Hivert Quentin e076d0db73 fix(calendar,preferences) Small fixes for commit 5c4d467d and 6050c4ee 2023-05-22 10:11:58 +02:00
Hivert Quentin 5c4d467d06 feat(calendar): add copy to clipboard button in "links to calendar" panel 2023-05-19 13:14:55 +02:00
Hivert Quentin 6050c4ee61 feat(preferences): show special characters when changing password 2023-05-17 18:12:21 +02:00
smizrahi 9ab59e7420 Merge branch 'master' into sogo-5.8 2023-05-16 10:58:23 +02:00
smizrahi 572abfb589 Merge branch 'master' into sogo-5.8 2023-05-16 10:34:19 +02:00
smizrahi 32a04998e5 fix(calendar): Fix Out of range value for column 'c_startdate' SQL error 2023-05-15 20:17:55 +02:00
smizrahi 158a5becc9 fix(calendar): Fix Out of range value for column 'c_startdate' SQL error 2023-05-15 20:07:34 +02:00
Hivert Quentin 9c7ef089c2 fix(mail): Recipients with umlaut (ü) trigerred the end of string
With chrome and edge and a german keyboard, the ü was considered as a separator keycode when adding recipients
2023-05-15 15:13:15 +02:00
smizrahi 1df2586745 fix(addressbook): Fix asynchronous selection of contacts - cards are not added as they're not yet loaded. 2023-05-10 10:32:46 +02:00
root 807622dd53 chore(js/css): update generated files 2023-05-10 08:27:21 +00:00
smizrahi 650ca16ff7 fix(calendar): Fix calendar green dot stucks in original day when browser is opened for days and day changed 2023-04-19 23:31:49 +02:00
550 changed files with 9958 additions and 2615 deletions

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ translated. Start by duplicating the English templates:
* [SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html](https://raw.githubusercontent.com/inverse-inc/sogo/master/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html)
* [SoObjects/Mailer/SOGoMailEnglishReply.wo/SOGoMailEnglishReply.html](https://raw.githubusercontent.com/inverse-inc/sogo/master/SoObjects/Mailer/SOGoMailEnglishReply.wo/SOGoMailEnglishReply.html)
The third type is the locale file formatted as a **plist**. Duplicate the English locale:
The third type is the locale file formatted as a **plist**. Duplicate the English locale. Beware that words with other characters than [a-zA-Z] (accents, non-latin...) must be between double quotes ("). You can look at other files as arabic or french to have an example:
* [UI/MainUI/English.lproj/Locale](https://raw.githubusercontent.com/inverse-inc/sogo/master/UI/MainUI/English.lproj/Locale)
+10
View File
@@ -12,6 +12,7 @@
ActiveSync/ActiveSync.SOGo
Documentation/*.docbook
Documentation/*.pdf
Documentation/*.html
SoObjects/SOGo/SOGo.framework/
SoObjects/SOGo/derived_src/
Tests/*/config.py
@@ -46,3 +47,12 @@ UI/WebServerResources/js/vendor/ckeditor/skins/n1theme/*.md
# Transifex
.tx/config_20230213185112.bak
# Tests
Tests/node_modules
Tests/lib/config.js
Tests/results.xml
Tests/results
# SAML2 autogenerated files
SoObjects/SOGo/SOGoSAML2Exceptions.*
+1 -1
View File
@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = fr: French, pt_BR: BrazilianPortuguese, ru: Russian, sk: Slovak, es_AR: SpanishArgentina, fi: Finnish, id_ID: Indonesian, lt: Lithuanian, tr_TR: TurkishTurkey, zh_TW: ChineseTaiwan, ar: Arabic, cs: Czech, nn_NO: NorwegianNynorsk, sl_SI: Slovenian, uk: Ukrainian, zh_CN: ChineseChina, sv: Swedish, bg_BG: Bulgarian, cy: Welsh, is: Icelandic, sr: Serbian, lv: Latvian, pl: Polish, ro_RO: Romanian, bs_BA: Bosnian,ca: Catalan, he: Hebrew, kk: Kazakh, pt: Portuguese, es_ES: SpanishSpain, eu: Basque, hu: Hungarian, ja: Japanese, mk_MK: Macedonian, nb_NO: NorwegianBokmal, nl: Dutch, sr@latin: SerbianLatin, da_DK: Danish, de: German, hr_HR: Croatian, it: Italian, sr_ME@latin: Montenegrin
lang_map = fr: French, pt_BR: BrazilianPortuguese, ru: Russian, sk: Slovak, es_AR: SpanishArgentina, fi: Finnish, id_ID: Indonesian, lt: Lithuanian, tr_TR: TurkishTurkey, zh_TW: ChineseTaiwan, ar: Arabic, cs: Czech, nn_NO: NorwegianNynorsk, sl_SI: Slovenian, uk: Ukrainian, zh_CN: ChineseChina, sv: Swedish, bg_BG: Bulgarian, cy: Welsh, is: Icelandic, sr: Serbian, lv: Latvian, pl: Polish, ro_RO: Romanian, bs_BA: Bosnian,ca: Catalan, he: Hebrew, kk: Kazakh, pt: Portuguese, es_ES: SpanishSpain, eu: Basque, hu: Hungarian, ja: Japanese, mk_MK: Macedonian, nb_NO: NorwegianBokmal, nl: Dutch, sr@latin: SerbianLatin, da_DK: Danish, de: German, hr_HR: Croatian, it: Italian, sr_ME@latin: Montenegrin, gl_ES: Galician
[o:alinto:p:sogo:r:soobjects-appointments]
source_file = SoObjects/Appointments/English.lproj/Localizable.strings
+2 -2
View File
@@ -2627,17 +2627,17 @@ FIXME
// We first check of any of the collections we want to sync are already
// in an other sync process. If that's the case, we do not do anything
// and we return immediately. So we'll let the other sync process terminate
globalMetadata = [self globalMetadataForDevice];
for (j = 0; j < [allCollections count]; j++)
{
aCollection = [allCollections objectAtIndex: j];
globalMetadata = [self globalMetadataForDevice];
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
{
if (debugOn)
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
[self logWithFormat: @"EAS - Discard response %@", globalMetadata];
[output appendString: @"<Status>13</Status>"];
[output appendString: @"</Sync>"];
+206 -1
View File
@@ -2,9 +2,214 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [5.8.3](https://github.com/Alinto/sogo/compare/SOGo-5.8.2...SOGo-5.9.0) (2023-05-30)
## [5.9.1](https://github.com/Alinto/sogo/compare/SOGo-5.9.0...SOGo-5.9.1) (2023-12-12)
### Features
* **addressbook:** Add global address book autocomplete on vlist ([11ad203](https://github.com/Alinto/sogo/commit/11ad203388ad33a22bd300047745869a27872cd9) [0b1f2b0](https://github.com/Alinto/sogo/commit/0b1f2b0405410628cb7ba8602b9286d71256d8e6) [0560efd](https://github.com/Alinto/sogo/commit/0560efd1f6a94bcb256d970ebe5535954e36cafe), [f85c531](https://github.com/Alinto/sogo/commit/f85c53190c38c4e28536f78e43623f6776248291))
* **core:** Add mobile provisioning download for Apple's devices ([19915e3](https://github.com/Alinto/sogo/commit/19915e3dffc14c90b03277f9f97b4e6b1158aa87) [6d16ee7](https://github.com/Alinto/sogo/commit/6d16ee798863a7f196c14915c47888adf0758f1a) [d38cc86](https://github.com/Alinto/sogo/commit/d38cc86d29ffce580bf26dd278a6b68556471c14) [cd50ee0](https://github.com/Alinto/sogo/commit/cd50ee017656f3e6cfd46cff7147258d4893706b) [65f8336](https://github.com/Alinto/sogo/commit/65f8336255577566282039f4058fe4f3c38a16d4) [4fe01f9](https://github.com/Alinto/sogo/commit/4fe01f96d73690bf1a2d60755bb552d25324ccaf) [a273f7a](https://github.com/Alinto/sogo/commit/a273f7aca5df120a50af0486161880040ee20519))
* **core:** add SOGoDisableExport option to disable export for mail, calendar and contacts ([d0b4b50](https://github.com/Alinto/sogo/commit/d0b4b50456cf3e23aa12d5cf7165531a7670edf6))
* **core:** Add SOGoDisableSharingAnyAuthUser option to disable sharing to any user authenticated for mail, contacts and calendar ([e0ef59d](https://github.com/Alinto/sogo/commit/e0ef59d5f212aced72a17e3af9e72a434eedd68e))
* **mail:** Add an option in Preferences to display full email instead of name alone in mailboxes ([2e670f7](https://github.com/Alinto/sogo/commit/2e670f7e6293b703f8cfe23ebf923ce13b463a19))
* **mail:** Add SOGoMailHideInlineAttachments option to hide attachments when inline. Fixes [#5490](https://bugs.sogo.nu/view.php?id=5490). ([f41ab2e](https://github.com/Alinto/sogo/commit/f41ab2ed0c71903b80bda66adc54cc94b0cf70d6) [abce5f4](https://github.com/Alinto/sogo/commit/abce5f42cf4daef721ebf6b48d727eb957e20b14) [aa53524](https://github.com/Alinto/sogo/commit/aa535247513b06804fce666fe20ebcc9bd231c1f) [ca2c5c1](https://github.com/Alinto/sogo/commit/ca2c5c188ed980629f494e5b8e23ba42cdd54f58))
* **password:** Add specials characters to POLICY_MIN_SPECIAL_SYMBOLS ([7d66699](https://github.com/Alinto/sogo/commit/7d666992198e5e91e29081b99443607b1c96d354))
* **preferences:** can set SOGoForwardConstraints to 3 to accept both internal and external domains from SOGoForwardConstraintsDomains ([c872fb4](https://github.com/Alinto/sogo/commit/c872fb4d9c68382431a52181073ba1964cf44c22))
* **reply:** add references header when replying ([b396a27](https://github.com/Alinto/sogo/commit/b396a277f00d879d2c179db780d6f621a9057d0c))
* **ux:** select all button will unselect all if click again ([6e60b10](https://github.com/Alinto/sogo/commit/6e60b107556212123f41d95e4399d9605a4c16f9) [1ae02eb](https://github.com/Alinto/sogo/commit/1ae02ebd708da0a53ae588531e122adddaa7a5de))
### Bug Fixes
* **addressbook:** autocomplete correctly return two contacts from two ldap with same id ([1d3da92](https://github.com/Alinto/sogo/commit/1d3da92bab972ec7ec935eba72128e72387216c0))
* **calendar:** do not let DURATION and DTEND be both present in vevent ([0023a9c](https://github.com/Alinto/sogo/commit/0023a9ce3c4027bb32699efc5529a4a53d7699f6))
* **calendar:** If content is NSData, properly convert it to NSString ([21571ab](https://github.com/Alinto/sogo/commit/21571ab1e000da47b91b3f21bfaba89d1abec71f))
* **calendar:** properly set the start date range for yearly events ([1e8127b](https://github.com/Alinto/sogo/commit/1e8127b46122e83df02c394536950288b0ee16aa))
* **core:** Add cache for CDefaultsSize to avoid spamming SQL request ([87e1240](https://github.com/Alinto/sogo/commit/87e1240ace297645559e698a8c1f1a76b9901895) [81f2d88](https://github.com/Alinto/sogo/commit/81f2d88db362cc21c50858295d0e9d375db61f67))
* **core:** Fix calendar issues with Thunderbird related to vlist exclusion. Fix error log on photo. Closes [#5885](https://bugs.sogo.nu/view.php?id=5885) ([6b08f2e](https://github.com/Alinto/sogo/commit/6b08f2e4decc87ee57609355dee5a6657bca6110))
* **core:** Fix user profile retrieval when using utf8mb4_bin collation - in this case the SQL results may return NSdata instead of NSString. Fixes [#5902](https://bugs.sogo.nu/view.php?id=5902). ([3cd9910](https://github.com/Alinto/sogo/commit/3cd991063805f88a5672b10fbcc04a4149e13e26))
* **hmtl:** prevent html injection of tag form ([7481ccf](https://github.com/Alinto/sogo/commit/7481ccf37087c3f456d7e5a844da01d0f8883098))
* **http:** set correct Accept header when dowloading eml file ([c569ed2](https://github.com/Alinto/sogo/commit/c569ed2d77cf23c274c3dfc7cd3533cf268d09af))
* **mail:** Clean temporary files when mail is sent ([f35a37b](https://github.com/Alinto/sogo/commit/f35a37bbdd2005a63b42f9bab1588ce9a935b112) [d163405](https://github.com/Alinto/sogo/commit/d1634054d6f4dd18bdb82a7903b3bbbceae5b0e8))
* **mail:** Fix duplication of images when saving draft. Fixes [#5888](https://bugs.sogo.nu/view.php?id=5888) ([ee9899f](https://github.com/Alinto/sogo/commit/ee9899fe3d6aa7bf694f959a8e4f09faa39e737a) [9c09485](https://github.com/Alinto/sogo/commit/9c0948518b1e8365d6fadb3fccae1a0257b518ed))
* **scripts:** wrong type for c_value in script for complete unicode coverage ([65374e4](https://github.com/Alinto/sogo/commit/65374e44fc2a861d3a7c9f1f9ba25505601d79f1))
* **sogo-tool:** add protection to expire-sessions if the input is not an integer ([bb943e4](https://github.com/Alinto/sogo/commit/bb943e414d3fc8b71ebb169e31c261cd2dcd6d37))
* **translation:** incorret value causing trouble to transifex ([4b45dc7](https://github.com/Alinto/sogo/commit/4b45dc7584984602071efe99b9638c6231efba8c))
### Localization
* **ar:** Update Arabic translations ([28d883d](https://github.com/Alinto/sogo/commit/28d883dac1ccbafb878b9e13ba38f570f44ea325))
* **bg_BG:** Update Bulgarian translations ([3815bc1](https://github.com/Alinto/sogo/commit/3815bc1c5e6fe37aaad5492c246697fe34e4286d))
* **bs_BA:** Update Bosnian translations ([2afbcbe](https://github.com/Alinto/sogo/commit/2afbcbeaed24571785437aa5af1c092ba129ce97))
* **ca:** Update Catalan translations ([3040543](https://github.com/Alinto/sogo/commit/3040543c797fe9176df048b8a72a2c6f7dbfd3b7))
* **cs:** Update Czech translations ([029a821](https://github.com/Alinto/sogo/commit/029a82143f1d0d82ca8d6a5c9e5a14ac351718bf))
* **cy:** Update Welsh translations ([06a6ed7](https://github.com/Alinto/sogo/commit/06a6ed776824f47e5606c29a162903380e72fbd9))
* **da_DK:** Update Danish translations ([76911fd](https://github.com/Alinto/sogo/commit/76911fd4454c9f0213f11b4e2e95b29010eed9a4))
* **de:** Update German translations ([8a4bc2a](https://github.com/Alinto/sogo/commit/8a4bc2a71642a2a431008c02214ffdd0eb2ace23))
* **es_AR:** Update SpanishArgentina translations ([527fded](https://github.com/Alinto/sogo/commit/527fded19ceaf995963e997659b79c5e1290b8c5))
* **es_ES:** Update SpanishSpain translations ([057a1ba](https://github.com/Alinto/sogo/commit/057a1ba1053f462cd0633b86c3ee86d76acbd6bb))
* **eu:** Update Basque translations ([33f3a1b](https://github.com/Alinto/sogo/commit/33f3a1b716a006df8a28c416fc947e44fc285e37))
* **fi:** Update Finnish translations ([dba2332](https://github.com/Alinto/sogo/commit/dba2332864fa38116615c9b58eb3590fe251a37a))
* **gl:** Change Galician name to Galego ([6eae3f2](https://github.com/Alinto/sogo/commit/6eae3f2b1601033558a791c27bc4e27acf0825e7))
* **gl:** Update Galician translations ([5aa7ebf](https://github.com/Alinto/sogo/commit/5aa7ebf2aefef6e45423b2193c136fadced51375))
* **he:** Update Hebrew translations ([694c21d](https://github.com/Alinto/sogo/commit/694c21de99522733c95304d0e775e81493ff7200))
* **hr_HR:** Update Croatian translations ([715d25d](https://github.com/Alinto/sogo/commit/715d25d40a84b518b1d8f97b4946045a4be761b5))
* **hu:** Update Hungarian translations ([0b22fa6](https://github.com/Alinto/sogo/commit/0b22fa6cd5b55db5d021bbbfcc53d411ff4bb9d7))
* **id_ID:** Update Indonesian translations ([a16d29b](https://github.com/Alinto/sogo/commit/a16d29b70b50eeee14a3ad1b627e8f8a59216461))
* **is:** Update Icelandic translations ([2de6241](https://github.com/Alinto/sogo/commit/2de624122045d9df877f39f665d271056af0fba8))
* **it:** Update Italian translations ([53036b3](https://github.com/Alinto/sogo/commit/53036b323fe65b4bf40c838ff0578fa076bfea93))
* **ja:** Update Japanese translations ([41fb85e](https://github.com/Alinto/sogo/commit/41fb85e6a320b25776ce63672ef4251e932f82f0))
* **kk:** Update Kazakh translations ([2174195](https://github.com/Alinto/sogo/commit/21741954dd2762b3f03c0b5b21b8feb4d826cfb1))
* **lt:** Update Lithuanian translations ([6e9e7f9](https://github.com/Alinto/sogo/commit/6e9e7f98ef379ce26937e00daedfad88383615a4))
* **lv:** Update Latvian translations ([a9ecaed](https://github.com/Alinto/sogo/commit/a9ecaed5fa6fe8fecf08ec0354bbb187824d2fbb))
* **mk_MK:** Update Macedonian translations ([d6c9ecd](https://github.com/Alinto/sogo/commit/d6c9ecdeb0490fa44bc1c444819b5c82ccf89073))
* **nb_NO:** Update NorwegianBokmal translations ([3d5f352](https://github.com/Alinto/sogo/commit/3d5f35211189802ef031be688cadddc2ab684d37))
* **nl:** Update Dutch translations ([402c3d0](https://github.com/Alinto/sogo/commit/402c3d0a1f83d32af2fca2f3e5abafbe8850de08))
* **nn_NO:** Update NorwegianNynorsk translations ([6d270fe](https://github.com/Alinto/sogo/commit/6d270fe7a4bad2253f7e8427be660405460b2139))
* **pl:** Update Polish translations ([c75f210](https://github.com/Alinto/sogo/commit/c75f210864de1a7bb313c1ba21be987143a2f7ca))
* **pt_BR:** Update BrazilianPortuguese translations ([cfab856](https://github.com/Alinto/sogo/commit/cfab85624b3e725f121ea61d181340189e969229))
* **pt:** Update Portuguese translations ([e4f07c4](https://github.com/Alinto/sogo/commit/e4f07c466e195d59f72b934567cb3aa4d18cb9f3))
* **ro_RO:** Update Romanian translations ([d9c716c](https://github.com/Alinto/sogo/commit/d9c716c1b679a792876c9bc7ebed2d530ab8d89f))
* **ru:** Update Russian translations ([3809ec3](https://github.com/Alinto/sogo/commit/3809ec3904a7882239e50c671aa72c9f7f013c62))
* **sk:** Update Slovak translations ([cd7e06c](https://github.com/Alinto/sogo/commit/cd7e06c2ede339d078e54b76b6078523b78383fc))
* **sl_SI:** Update Slovenian translations ([ca21bf4](https://github.com/Alinto/sogo/commit/ca21bf4ce1758717eac8ec961788d067e9428456))
* **sr_ME@latin:** Update Montenegrin translations ([fe7369e](https://github.com/Alinto/sogo/commit/fe7369e427d7cc6f4efb23a78437bd2b2c65e6cd))
* **sr@latin:** Update SerbianLatin translations ([9126cae](https://github.com/Alinto/sogo/commit/9126cae27d49b7f199f2a6b259eb40589b011a55))
* **sr:** Update Serbian translations ([ed444fd](https://github.com/Alinto/sogo/commit/ed444fdc2266888f9d15d557e1ba92896158d870))
* **sv:** Update Swedish translations ([10369aa](https://github.com/Alinto/sogo/commit/10369aa654058b50285ae7239a9356f736c7ac1c))
* **tr_TR:** Update TurkishTurkey translations ([8d1fc1e](https://github.com/Alinto/sogo/commit/8d1fc1e306d926b3efeab2e3017823201254caf0))
* **uk:** Update Ukrainian translations ([0e50d37](https://github.com/Alinto/sogo/commit/0e50d37531d850b46009af2af5049a6b46541da0))
* **zh_CN:** Update ChineseChina translations ([cf87022](https://github.com/Alinto/sogo/commit/cf87022348296b9659e83e3a3985b48d89f5e01f))
* **zh_TW:** Update ChineseTaiwan translations ([fc2a82c](https://github.com/Alinto/sogo/commit/fc2a82c3b0f4b780df3b4d27ad0da78e34f665c6))
## [5.9.0](https://github.com/Alinto/sogo/compare/SOGo-5.8.3...SOGo-5.9.0) (2023-09-27)
### Features
* **calendar:** Add CALDAV Mac OS X Sonoma (14) support ([68cef9a](https://github.com/Alinto/sogo/commit/68cef9ae039e3040348a6a16733f4017a7e8ba42) [87bbba0](https://github.com/Alinto/sogo/commit/87bbba0c8d7893a776ff9ccafbe8227e98aa14ed))
* **core:** Add in user settings to force password change for a user when logging ([9918f75](https://github.com/Alinto/sogo/commit/9918f7558028f7a9d403d93c034f62a384ede4f2) [d1a51e8](https://github.com/Alinto/sogo/commit/d1a51e8340a74e7803cb808f7ff8a7bf959759c1))
* **notification:** add enotify sieve script in preferences ([82b69d1](https://github.com/Alinto/sogo/commit/82b69d14149c4ef3f6e913232bed478635009ed5) [6b64705](https://github.com/Alinto/sogo/commit/6b647050fc64b9beb024379ed7d37b6a54356798))
* **preferences:** Add user preferences to enable / disable signature for new mail, reply and forward ([8582e42](https://github.com/Alinto/sogo/commit/8582e423bdd69e200ff8cdec7f1d94634455e519))
### Enhancements
* **calendar:** add copy to clipboard button in "links to calendar" panel ([5c4d467](https://github.com/Alinto/sogo/commit/5c4d467d06161324826ee1ef0a713fc340dfaed5))
* **preferences:** Add subtext in vacation filter to explain the behavior ([09d0326](https://github.com/Alinto/sogo/commit/09d03261af82071b451fcba32a431f5c0432fa5d))
* **preferences:** show special characters when changing password ([6050c4e](https://github.com/Alinto/sogo/commit/6050c4ee61811b989e39b3c38a2e86360213be28))
* **packaging:** Add debian 10 support ([528d178](https://github.com/Alinto/sogo/commit/528d1782563c10bb0905ab159eb3577973615ae4))
### Bug Fixes
* **addressbook:** Fix asynchronous selection of contacts - cards are not added as they're not yet loaded. ([1df2586](https://github.com/Alinto/sogo/commit/1df25867455b46ff634e3a53cdf10fbb8f2a6a4b))
* **addressbook:** Fix broken image in shared address book when image is null ([5d315ac](https://github.com/Alinto/sogo/commit/5d315ac18066bd952fc45121ad1fe431a7b0305c))
* **addressbook:** Remove vlist from dav request for Thunderbird only. In Thunderbird 115, using vlist results in contact duplication ([f0719ee](https://github.com/Alinto/sogo/commit/f0719ee409dee693e7b4792bd127ccdd4b3043f1))
* **calendar:** Add negative timestamp support to use date before 1970-01-01. Fixes [#4914](https://bugs.sogo.nu/view.php?id=4914) (partial) ([8a8dcae](https://github.com/Alinto/sogo/commit/8a8dcae0298b81a486ad454bd58b90821eec6894))
* **calendar:** Avoid event duplication when adding an ics with an older sequence number and event already added in calendar ([cab5bc0](https://github.com/Alinto/sogo/commit/cab5bc011eb3afb5ff36e0f49f9919380c5835e9))
* **calendar:** deleted occurence of repeated event could cause some troubles with other imrbicated occurences ([f3981c4](https://github.com/Alinto/sogo/commit/f3981c42765f04431dd34af35d446aca03a1b5bc))
* **calendar:** Do not send tentative participation mail when user open mail or add ics to calendar app ([382b8a0](https://github.com/Alinto/sogo/commit/382b8a0a1a37fe89d268224a28570134bd2d2481))
* **calendar:** Fix calendar green dot stucks in original day when browser is opened for days and day change. Fixes [#5783](https://bugs.sogo.nu/view.php?id=5783) ([22372a7](https://github.com/Alinto/sogo/commit/22372a7598adbcf9c229f174f58842a89ada963f))
* **calendar:** Fix calendar green dot stucks in original day when browser is opened for days and day changed ([f22672c](https://github.com/Alinto/sogo/commit/f22672cef852116d08ba6a8720721fd0ce2f717f) [650ca16](https://github.com/Alinto/sogo/commit/650ca16ff78ecbd0dbf23bdf381461d75dda864e))
* **calendar:** Fix duplicates in series appointments for Apple's Calendar ([57ba539](https://github.com/Alinto/sogo/commit/57ba5397e5c14b3cb0a31c5617718c2866fca72f) [7600e9e](https://github.com/Alinto/sogo/commit/7600e9e1798062db167b1206829d1ca8be8c8881))
* **calendar:** Fix MacOS X Ventura calendar support. Fixes [#5639](https://bugs.sogo.nu/view.php?id=5639) ([4f7c731](https://github.com/Alinto/sogo/commit/4f7c73143f38b1e7e00a51b9457b55ce609a02a9))
* **calendar:** Fix NAME:NSInvalidArgumentException REASON:SOGoAppointmentFolder(instance) does not recognize davCalendarProxyReadFor ([82178ee](https://github.com/Alinto/sogo/commit/82178eeba95d19842afcd309d1a291e971d04bbe))
* **calendar:** Fix Out of range value for column 'c_startdate' SQL error ([32a0499](https://github.com/Alinto/sogo/commit/32a04998e50cc4adbc72b7dc36de67b8e9e4b0f2) [158a5be](https://github.com/Alinto/sogo/commit/158a5becc902b1d241575ac5098ceb527a0011ff) [1a257c7](https://github.com/Alinto/sogo/commit/1a257c7768fb9010db32e050650693d26f3e5a29))
* **calendar:** Fix regression on calendar event modification. Start date was set to 1970. Fixes [#5777](https://bugs.sogo.nu/view.php?id=5777). ([b68a215](https://github.com/Alinto/sogo/commit/b68a215ea29807db688e2f9a46a4a8d0907f83b8))
* **calendar:** Fix search issue on repeated events when one title is changed the event could not be found ([139ef15](https://github.com/Alinto/sogo/commit/139ef1528456d416ec5e99b56eba4f064d20738b))
* **calendar:** Increase timeout to 60 seconds for large ics calendar. Closes [#5781](https://bugs.sogo.nu/view.php?id=5781). ([fa5264e](https://github.com/Alinto/sogo/commit/fa5264e822867549aa5bc23fb905fd18c065c087))
* **calendar:** recurrenceid wasn't working properly ([c5814d6](https://github.com/Alinto/sogo/commit/c5814d60a4b3bb632a5ef50121453f592e9b4bd6))
* **calendar:** Repeated events didn't use the correct timezone in some cases ([bd8b49d](https://github.com/Alinto/sogo/commit/bd8b49d2c95a187b218bd7c4b8689a85faa3dda2))
* **calendar:** Revert commit e47566e3a and change HTTP status from 409 to 403 when event has a sequence number lower than than the new one. This should fix some Apple's duplication events ([fce02db](https://github.com/Alinto/sogo/commit/fce02db7d15c827c24a89d67a457f23379199e4e))
* **calendar:** some kinds of events are not shown ([f2df405](https://github.com/Alinto/sogo/commit/f2df4056161a96da750cba4a083a05ce1390ad55))
* **calendar:** When an event has no date (weird case), it is not possible to remove the event (NSException) ([4d2c771](https://github.com/Alinto/sogo/commit/4d2c77195e72def1e39caab6c402c9b297d844ff))
* **cas:** correction on pgtUrl ([a1528bc](https://github.com/Alinto/sogo/commit/a1528bcc0304ca4cce1a088c557e0e7d806309d9))
* **CAS:** key based on tciket was too long for memecached ([68b1c1e](https://github.com/Alinto/sogo/commit/68b1c1e5dfac8383e6cbafadf639105d25680cb4))
* **contacts:** Fix 5875 ([ff263b9](https://github.com/Alinto/sogo/commit/ff263b99fe5234752e875cd25101948c3fd177a5))
* **js:** Fix invalid sass version ([5e471c5](https://github.com/Alinto/sogo/commit/5e471c5d66f48d01cc1a75419dbdbaed57600331))
* **js:** Fix url redirection to null when disconnected. Closes [#5844](https://bugs.sogo.nu/view.php?id=5844). ([dc23721](https://github.com/Alinto/sogo/commit/dc23721b75405ff27624210598799366bdad91a9))
* **login:** Fix language list sorting on login page ([e0d76fb](https://github.com/Alinto/sogo/commit/e0d76fba65d46a8299bb00aa677308521f7cfc03))
* **mail:** autocomplete can't find two lists named the same but in different address books ([854e83c](https://github.com/Alinto/sogo/commit/854e83c22c5ca47915e0f426482d004e5ecc9868))
* **mail:** Fix a bug when pressing the key DEL more than once to delete mails ([af59e57](https://github.com/Alinto/sogo/commit/af59e57fc03f0bb5b5908510ad5da8ad11a601d9))
* **mail:** Fix email without subject according to RFC 2822. Fixes [#5743](https://bugs.sogo.nu/view.php?id=5743) ([ab954a1](https://github.com/Alinto/sogo/commit/ab954a150e99b4b35317a836791e831d460a1059))
* **mail:** Recipients with umlaut (ü) trigerred the end of string ([9c7ef08](https://github.com/Alinto/sogo/commit/9c7ef089c28df73ff3171efa1ca7f34d0d8b3a6c))
* **mail:** Removed invalid HTML tags in mail body. Fixes [#5755](https://bugs.sogo.nu/view.php?id=5755) ([f9fdad2](https://github.com/Alinto/sogo/commit/f9fdad251da20617430e1626a72c574221215ff1))
* **mail:** Signature images are not handled properly in forwarded or replied to messages. Save extracted body parts (for signature) into file system. Fixes [#5768](https://bugs.sogo.nu/view.php?id=5768) ([23da4cc](https://github.com/Alinto/sogo/commit/23da4cca7bbf5907f499d218c65b063d875a3120) [d436afb](https://github.com/Alinto/sogo/commit/d436afb05433bde00040b8b9cdd7432bf5d00bb0))
* **mail:** Templates namespace is correctly returned ([e959583](https://github.com/Alinto/sogo/commit/e959583390f50339f02f4870ef88a16c63b0dbd5))
* **mail:** When signature is too big and changing identity, a 'regex too big' error occurs. In this case just add signature at the end. Fixes [#5695](https://bugs.sogo.nu/view.php?id=5695). ([b7e529d](https://github.com/Alinto/sogo/commit/b7e529dd490c493a726878df0c712670fbd3fa3f))
* **notification:** body was not send + missing tooltip ([5534d4b](https://github.com/Alinto/sogo/commit/5534d4b6eed4b7b2343bb711fa9b7441f1d13ed7))
* **notifications:** bug with older user profile ([16af360](https://github.com/Alinto/sogo/commit/16af360006dd67e246590a41574634a669cf6070))
* **preferences:** add autocomplete for recipients in automatic forward ([ba4c7a5](https://github.com/Alinto/sogo/commit/ba4c7a5300d7eb5ea15c13a8b3fd9b27aa4cf53a))
* **preferences:** Disable auxiliary mail accounts creation when SOGoCreateIdentitiesDisabled is enabled ([b666137](https://github.com/Alinto/sogo/commit/b666137a7036a1fb095b17c767f342028df8211c))
* **preferences:** Fix language list sorting on preferences page ([f5f74f7](https://github.com/Alinto/sogo/commit/f5f74f751a63f6de3e247db7fff7da5ca215bdff))
* **preferences:** Fix NSException issue when SOGoCreateIdentitiesDisabled is set ([eb42e17](https://github.com/Alinto/sogo/commit/eb42e1787c427c8ac2bae9ecb9129fcf430eaaca))
* **preferences:** Fix the issue when signature is too long and preferences are not saved. Note that MySQL profile table should be updated with a migration script ([326bc68](https://github.com/Alinto/sogo/commit/326bc68f4c928ed2be8cea6d57d62a6e2ca04f6b))
* **preferences:** Signature could not be saved when SOGoCreateIdentitiesDisabled is set to YES and no signature is on identity ([4abf12b](https://github.com/Alinto/sogo/commit/4abf12b4bb27cfafdb3cc725f677e8f66fe5709e))
* **sieve:** sieve availibility was checked even if we disable it ([bba1da1](https://github.com/Alinto/sogo/commit/bba1da1b2b8d4babef7ab1a523741877cf58d61f))
* **translation:** french syntax ([8604389](https://github.com/Alinto/sogo/commit/860438915212a06f983da8c283d386a48745d049))
* **ui:** Disable send button while mail is sending. Fixes [#5825](https://bugs.sogo.nu/view.php?id=5825). ([a484407](https://github.com/Alinto/sogo/commit/a48440754cb9e4ea1bc13c5bc68c05bd93f89d51))
* **user:** Replace length with count for NSArray ([ec27f7e](https://github.com/Alinto/sogo/commit/ec27f7e7141c8ddae3450b36344415e98ae8f794))
### Localization
* **ar:** Update Arabic translations ([132912b](https://github.com/Alinto/sogo/commit/132912b56610141d6bcd6a5ce9170837796b5991))
* **bg_BG:** Update Bulgarian translations ([6606f98](https://github.com/Alinto/sogo/commit/6606f9865a619cecf8f37e0c4c39ca8fa0aa31cc))
* **bs_BA:** Update Bosnian translations ([fc93272](https://github.com/Alinto/sogo/commit/fc93272d70661db3eafcf2c089d2f8cae65bd799))
* **ca:** Update Catalan translations ([c82a48f](https://github.com/Alinto/sogo/commit/c82a48f96f3f114d3ac771f13fe0687bb24308f1))
* **cs:** Update Czech translations ([1a5408c](https://github.com/Alinto/sogo/commit/1a5408c2b323f46d03f2078ac17895493b0cd986))
* **cy:** Update French translations ([487ad1a](https://github.com/Alinto/sogo/commit/487ad1a849290a21807e2bca55d884116b9991bf))
* **cy:** Update Welsh translations ([0e2deac](https://github.com/Alinto/sogo/commit/0e2deac164b16b6bd105bd9d629275ae1147a902))
* **da_DK:** Update Danish translations ([9ef58e7](https://github.com/Alinto/sogo/commit/9ef58e7c6d143e2ac7d13719ec2e2be281de377f))
* **de:** Update German translations ([0f0ecf7](https://github.com/Alinto/sogo/commit/0f0ecf71c30ce7b5742ce7e6b6e9cb55b1b85a84))
* **es_AR:** Update SpanishArgentina translations ([64865aa](https://github.com/Alinto/sogo/commit/64865aa85a715095788452b1132bafe8f8fea018))
* **es_ES:** Update SpanishSpain translations ([ea8f04a](https://github.com/Alinto/sogo/commit/ea8f04ac7c5baf6c70bf4741298cb7e0b7147750))
* **eu:** Update Basque translations ([6293f62](https://github.com/Alinto/sogo/commit/6293f62d83bb4e855ec199cf92dce8e8f9086134))
* **fi:** Update Finnish translations ([7ff0699](https://github.com/Alinto/sogo/commit/7ff0699648a9531270f2c6299bced91d2d8256b5))
* **gl:** Update Galician translations ([86e5872](https://github.com/Alinto/sogo/commit/86e5872dc1303cfc5302ef728be40df73c0894d2))
* **he:** Update Hebrew translations ([41842e2](https://github.com/Alinto/sogo/commit/41842e2d3956fc6c34e74c9b79294e11233aa70f))
* **hr_HR:** Update Croatian translations ([b1df5d4](https://github.com/Alinto/sogo/commit/b1df5d4d142da783e0dc87bc509bba6e71ac1b88))
* **hu:** Update Hungarian translations ([e3d392b](https://github.com/Alinto/sogo/commit/e3d392b12ab4d3ff03cadba52f0848e1063d1573))
* **id_ID:** Update Indonesian translations ([e50b66b](https://github.com/Alinto/sogo/commit/e50b66b62e494564306b58157112e2c71a75c5cf))
* **is:** Update Icelandic translations ([ee3ed07](https://github.com/Alinto/sogo/commit/ee3ed0779cc29121d04267cab44998a4296deed6))
* **it:** Update Italian translations ([2fee2f8](https://github.com/Alinto/sogo/commit/2fee2f8782e0db1c7ba5fa0d3305149b7ce25506))
* **ja:** Update Japanese translations ([97d01fb](https://github.com/Alinto/sogo/commit/97d01fb14e45b6e2f4cda977de462ab5fa5a782f))
* **kk:** Update Kazakh translations ([ed5199a](https://github.com/Alinto/sogo/commit/ed5199aee71d53ccf7cd1dec7e08fb33ab3ac31b))
* **lt:** Update Lithuanian translations ([4974648](https://github.com/Alinto/sogo/commit/4974648c3323e853ca0ac440105020fb8a215805))
* **lv:** Update Latvian translations ([493f5f6](https://github.com/Alinto/sogo/commit/493f5f625fa87bab69b1d6b13b6ed9d0278b00f2))
* **mk_MK:** Update Macedonian translations ([bbb0955](https://github.com/Alinto/sogo/commit/bbb09555f0c07e2ea57ca12cd1ec8a218c475e59))
* **nb_NO:** Update NorwegianBokmal translations ([0368bad](https://github.com/Alinto/sogo/commit/0368bad3c5bd90107703f469b7605fa0c1a1c277))
* **nl:** Update Dutch translations ([03d4307](https://github.com/Alinto/sogo/commit/03d43079d3934a6ef20098df425e4c15d30aca6f))
* **nn_NO:** Update NorwegianNynorsk translations ([02d32d5](https://github.com/Alinto/sogo/commit/02d32d5f6ced2c2411b549e198cda59352942142))
* **pl:** Update Polish translations ([92d38c1](https://github.com/Alinto/sogo/commit/92d38c108e627520a5143a8059ff013a4a0d6080))
* **pt_BR:** Update BrazilianPortuguese translations ([ce6efaf](https://github.com/Alinto/sogo/commit/ce6efafe54673f55ae02e348c7844f7258f5cb3a))
* **pt:** Update Portuguese translations ([46d5779](https://github.com/Alinto/sogo/commit/46d5779088c4ceb34578c3233fea34cf8885d858))
* **ro_RO:** Update Romanian translations ([9aebf9b](https://github.com/Alinto/sogo/commit/9aebf9bf022b6ad49b14e5980ef4b8d22ff4f59d))
* **ru:** Update Russian translations ([da609c4](https://github.com/Alinto/sogo/commit/da609c448e57168b6e691b5c756eb06c49e4c50c))
* **sk:** Update Slovak translations ([8a10213](https://github.com/Alinto/sogo/commit/8a10213b751fc04a3d3f08b96eb58f87dc794663))
* **sl_SI:** Update Slovenian translations ([b08b5dd](https://github.com/Alinto/sogo/commit/b08b5dd79a68eadd7e5482d55bf0ace54d96d3b1))
* **sr_ME@latin:** Update Montenegrin translations ([865aba3](https://github.com/Alinto/sogo/commit/865aba32486c3bacdd6ca78e32ef4cf37a123219))
* **sr@latin:** Update SerbianLatin translations ([1710112](https://github.com/Alinto/sogo/commit/1710112c4e7d7997d277fbb22413d28cec4388a0))
* **sr:** Update Serbian translations ([2242c21](https://github.com/Alinto/sogo/commit/2242c213238c88318fbda0a7fc2ffccea3d42c32))
* **sv:** Update Swedish translations ([d5c1b74](https://github.com/Alinto/sogo/commit/d5c1b742f45130cd3a9efd6bbacb8ce53a1db7ca))
* **tr_TR:** Update TurkishTurkey translations ([dfc1ab7](https://github.com/Alinto/sogo/commit/dfc1ab7e0ecf7d304d62a5a5a1bfd9c583ca070d))
* **uk:** Update Ukrainian translations ([5ee12a4](https://github.com/Alinto/sogo/commit/5ee12a45074f679aa90c8596d0e95d7e6cdb345d))
* **zh_CN:** Update ChineseChina translations ([300cfe4](https://github.com/Alinto/sogo/commit/300cfe4e27e60b0b6f9ba80ff1ce9dc88a58efdb))
* **zh_TW:** Update ChineseTaiwan translations ([e330c55](https://github.com/Alinto/sogo/commit/e330c552c3917cf662fe4b124686d504904e4e13))
### [5.8.4](https://github.com/Alinto/sogo/compare/SOGo-5.8.3...SOGo-5.8.4) (2023-06-20)
### Bug Fixes
* **preferences:** Fix NSException issue when SOGoCreateIdentitiesDisabled is set ([ef958f6](https://github.com/Alinto/sogo/commit/ef958f6aaaa606eaa7c06c1edd3a93667b869d3b))
### Enhancements
* **packaging:** Add RHEL9 support
## [5.8.3](https://github.com/Alinto/sogo/compare/SOGo-5.8.2...SOGo-5.8.3) (2023-05-30)
### Enhancements
* **calendar:** Add week number on month view. Fixes [#4457](https://bugs.sogo.nu/view.php?id=4457). ([96639b7](https://github.com/Alinto/sogo/commit/96639b70a05cc3646939f8d2e078c6896cfc6af5))
+587 -19
View File
@@ -21,7 +21,7 @@ devices with SOGo.
The instructions are based on version {release_version} of SOGo.
The latest version of this guide is available
at https://sogo.nu/downloads/documentation.html.
at https://www.sogo.nu/support.html#/documentation.
Introduction
------------
@@ -162,10 +162,10 @@ Operating System Requirements
The following operating systems are currently supported by SOGo:
* Red Hat Enterprise Linux (RHEL) Server 7 and 8
* Community ENTerprise Operating System (CentOS) 7 and 8
* Debian GNU/Linux 7.0 (Wheezy), 8.0 (Jessie), 9.0 (Stretch) and 10.0 (Buster)
* Ubuntu 14.04 (Trusty), 16.04 (Xenial), 18.04 (Bionic) and 20.04 (Focal)
* Red Hat Enterprise Linux (RHEL) Server 7, 8 and 9
* Community ENTerprise Operating System (CentOS) 7, 8 and 9
* Debian GNU/Linux 7.0 (Wheezy), 8.0 (Jessie), 9.0 (Stretch), 10 (Buster), 11 (Bullseye) and 12 (Bookworm)
* Ubuntu 14.04 (Trusty), 16.04 (Xenial), 18.04 (Bionic), 20.04 (Focal) and 22.04 (Jammy)
Make sure the required components are started automatically at boot time
and that they are running before proceeding with the SOGo configuration.
@@ -227,7 +227,7 @@ Some of the softwares on which SOGo depends are available from the repository
"Extra Packages for Enterprise Linux" (EPEL). To add EPEL to your packages
sources, install the following package:
On RHEL/CentOS 7,
On RHEL/CentOS 7
----
rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
----
@@ -237,12 +237,17 @@ For RHEL/CentOS 8
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
----
For RHEL/CentOS 9
----
dnf install -y epel-release && dnf update -y
dnf install -y yum-utils && dnf config-manager --set-enabled crb
----
SOGo relies on the GNUstep and ytnef packages provided by Alinto and must not use the
packages from EPEL. Adjust the repository definition to exclude those packages:
----
sed -i '/enabled=1/a \
exclude=gnustep* ytnef*' /etc/yum.repos.d/epel.repo
sed -i '/enabled=1/a exclude=gnustep* ytnef*' /etc/yum.repos.d/epel.repo
----
For more information on EPEL, visit http://fedoraproject.org/wiki/EPEL/.
@@ -618,6 +623,7 @@ for SOGo. Possible values are:
* `English`
* `Finnish`
* `French`
* `Galician`
* `German`
* `Hungarian`
* `Icelandic`
@@ -687,6 +693,13 @@ Defaults to `NO` when unset.
|S |SOGoDisableSharing
|List of modules where sharing should be disabled, for example `(Mail, Calendar)`. Modules can be `Mail`, `Contacts` and `Calendar`. Default value empty list (sharing enabled for everybody).
|S |SOGoDisableSharingAnyAuthUser
|List of modules where sharing with any authenticated user option should be disabled, for example `(Mail, Calendar)`. Modules can be `Mail`, `Contacts` and `Calendar`. Default value empty list (sharing enabled for everybody).
|S |SOGoDisableExport
|List of modules where export should be disabled, for example `(Mail, Calendar)`. Modules can be `Mail`, `Contacts` and `Calendar`. Default value empty list (export enabled for everybody).
|S |SOGoPasswordChangeEnabled
|Parameter used to allow or not users to change their passwords from
SOGo.
@@ -711,7 +724,7 @@ of 1 day by default.
|Parameter used to configure which languages are available from SOGo's
Web interface. Available languages are specified as an array of string.
The default value is: `( "Arabic", "Basque", "Catalan", "Czech", "Dutch", "Danish", "Welsh", "English", "SpanishSpain", "SpanishArgentina", "Finnish", "French", "German", "Icelandic", "Italian", "Hungarian", "BrazilianPortuguese", "NorwegianBokmal", "NorwegianNynorsk", "Polish", "Russian", "Slovak", "Ukrainian", "Swedish" )`
The default value is: `( "Arabic", "Basque", "Catalan", "Czech", "Dutch", "Danish", "Welsh", "English", "SpanishSpain", "SpanishArgentina", "Finnish", "French", "Galician", "German", "Icelandic", "Italian", "Hungarian", "BrazilianPortuguese", "NorwegianBokmal", "NorwegianNynorsk", "Polish", "Russian", "Slovak", "Ukrainian", "Swedish" )`
|D |SOGoHideSystemEMail
|Parameter used to control if SOGo should hide or not the system email
@@ -811,9 +824,19 @@ specified as an array of dictionaries.
|JWT secret according to RFC-7519. Default value is `SOGo`.
|D |SOGoCreateIdentitiesDisabled
|Disable identity creation for users in preferences. If `YES`, users won't be able to add new identities and will allow to change only full name, signature and default identity. Default value is `NO`.
|Disable identity creation for users in preferences. If `YES`, users won't be able to add new identities and will allow to change only full name, signature and default identity. Default value is `NO`. Note : If this settings is set to `YES`, it will not be possible to crete auxiliary mail accounts.
|S |SOGoURLEncryptionEnabled
|Enable URL encryption to make SOGo GDPR compatible. Setting this parameter to `YES` will encrypt username in URL. The encryption data are cached to avoid high cpu usage, ig the value of thie parameter is changed, memcached server needs to be restarted. If the encryption is enabled, the DAV url will change. Default value is `NO`.
|S |SOGoURLEncryptionPassphrase
|Passphrase for `SOGoURLEncryptionEnabled`. The string must be 128 bits (16 characters). If this settings change, the cache server must be restarted, and the DAV url will change. Default value is `SOGoSuperSecret0`.
|S |SOGoGlobalAddressBookFirstEntries
|Display first entries in Global Address Book. Default value is `NO`. If source is LDAP, the LDAP overlay `sssvlv` must be enabled on the system for server side sorting.
|S |SOGoGlobalAddressBookFirstEntriesCount
|Number of entries displayed when `SOGoGlobalAddressBookFirstEntries` is enabled. Default value is `100`.
|=======================================================================
@@ -1257,9 +1280,9 @@ mapping = {
|===
2+h|Name
|First |givenName
|First |givenname
|Last |sn
|DisplayName |displayName _or_ cn _or_ givenName + sn
|DisplayName |displayname _or_ cn _or_ givenname + sn
|Nickname |mozillanickname
2+h|Internet
@@ -1268,7 +1291,7 @@ mapping = {
|ScreenName |nsaimid
2+h|Phones
|Work |telephoneNumber
|Work |telephonenumber
|Home |homephone
|Mobile |mobile
|Fax |facsimiletelephonenumber
@@ -1289,7 +1312,7 @@ mapping = {
|Address |street + mozillaworkstreet2
|City |l
|State/Province |st
|Zip/Postal code |postalCode
|Zip/Postal code |postalcode
|Country |c
|Web page |mozillaworkurl
@@ -2267,18 +2290,27 @@ Defaults to `NO` when unset.
|D |SOGoForwardConstraints
|Parameter used to set constraints on possible addresses used when
automatically forwarding mails. When set to `0` (default), no constraint
is enforced. When set to `1`, only internal domains can be used. When
set to `2`, only external domains can be used.
is enforced. When set to `1`, only internal domains can be used.
When set to `2`, only external domains defined in `SOGoForwardConstraintsDomains`
can be used. When set to `3`, internal domains and other domains defined
in `SOGoForwardConstraintsDomains` can be used.
|D |SOGoForwardConstraintsDomains
|Parameter used to set which domains are allowed as external domains
when SOGoForwardConstraints is set to `2`. For example, setting:
|Parameter used to set which external domains are allowed
when SOGoForwardConstraints is set to `2` or `3`. For example, setting:
SOGoForwardConstraintsDomains = ("gmail.com", "googlemail.com");
will allow users to forward emails to only `gmail.com` and `googlemail.com` domains.
When empty or undefined, no constraints are imposed.
|D |SOGoNotificationEnabled
|Parameter used to activate the edition from the preferences window of
notifications for emails. Requires Sieve script support on the IMAP
host.
Defaults to `NO` when unset.
|D |SOGoSieveScriptsEnabled
|Parameter used to activate the edition from the preferences windows of
server-side mail filters. Requires Sieve script support on the IMAP
@@ -2468,6 +2500,13 @@ Defaults to `NO` when unset.
Defaults to `inline` when unset.
|U |SOGoMailDisplayFullEmail
|Show recipients or sender full email in mailboxes if set to `YES`. Default value is `NO`.
|U |SOGoMailHideInlineAttachments
|Hide inline message as attachements if set to `YES`. Default value is `NO`.
|U |SOGoMailCustomFullName
|The string to use as full name when composing an email, if
_SOGoMailCustomFromEnabled_ is set in the user's domain defaults.
@@ -2509,6 +2548,21 @@ Possible values are:
Defaults to `below`.
|U |SOGoMailUseSignatureOnNew
|Add signature to new message
Defaults to `YES`.
|U |SOGoMailUseSignatureOnReply
|Add signature to reply
Defaults to `YES`.
|U |SOGoMailUseSignatureOnForward
|Add signature to forward
Defaults to `YES`.
|U |SOGoMailComposeMessageType
|The message composition format. Possible values are:
@@ -2834,6 +2888,21 @@ The _cronjob_ should look like this:
0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds
----
_Password_ — Force user password change at login
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following commands force user to change his password at login :
----
/usr/sbin/sogo-tool user-preferences set settings [USER] ForceResetPassword 1
----
Disable :
----
/usr/sbin/sogo-tool user-preferences unset settings [USER] ForceResetPassword
----
Managing User Accounts
----------------------
@@ -3163,7 +3232,7 @@ your site. However, be aware that in order to use the SOGo Integrator
plug in, you will need to repackage it with specific modifications.
Please refer to the documentation published online:
https://sogo.nu/downloads/documentation.html
https://www.sogo.nu/support.html#/documentation
If you only use the SOGo Connector plug in, you can still easily access
your data.
@@ -3200,6 +3269,12 @@ http://127.0.0.1/SOGo/dav/jdoe/
Note that the trailing slash is important for the old Apple iCal 3 application.
Since SOGo 5.9.1, calendar can be installed using provisioning profiles. On the calendar interface next to the calendar name (eg `Personal`), click on the three dots button and click on `Download iOS - MacOS configuration profile`.
You have to add the profile to your device :
* Go to Settings / Profiles / Click on + / Continue
* Type your SOGo account password and click on install
Apple AddressBook
~~~~~~~~~~~~~~~~~
@@ -3243,6 +3318,12 @@ The virtual host should be defined like:
This configuration is also required if you want to configure a CardDAV
account on an Apple iOS device (version 4.0 and later).
Since SOGo 5.9.1, address book can be installed using provisioning profiles. On the address book interface next to the address book name (eg `Personal`), click on the three dots button and click on `Download iOS - MacOS configuration profile`.
You have to add the profile to your device :
* Go to Settings / Profiles / Click on + / Continue
* Type your SOGo account password and click on install
Microsoft ActiveSync
~~~~~~~~~~~~~~~~~~~~
@@ -3253,6 +3334,488 @@ any mobile devices that support Microsoft ActiveSync. Microsoft Outlook
The Microsoft ActiveSync server URL is generally something
like: `http://127.0.0.1/Microsoft-Server-ActiveSync`.
Using sogo-tool
---------------
The command _sogo-tool_ allows to do some operations on database and sieve filter. It is included with
the sogo package on Debian/Ubuntu but must be installed manually on RHEl/CentOS:
yum install sogo-tool
*_WARNING_: Use sogo-tool with full awareness of what you are doing. This is an admin tool that can cause loss of data
or completely make the webmail unusable by a user.*
sogo-tool backup/restore
~~~~~~~~~~~~~~~~~~~~~~~~
The backup tool saves the information of a user into a file. The information saved are its preferences, its events and its contacts.
sogo-tool backup directory ALL|user1 [user2] ...
* First argument, *directory* must be a path, only the last subdirectory will be created, the previous ones must exist.
* Second argument is *ALL* to backup all users or put each user space-separated.
* Each user's info will take one file, the filename will be the username.
* The files are readable but in a specific format for _sogo-tool restore_.
Example:
----
sogo-tool backup /tmp/foo ALL # Will save all users into /tmp/foo
sogo-tool backup /etc/sogo_backup/foo user1 user2 # Will save only user1 and user2. The directory /etc/sogo_backup must exist.
----
Using the files produced by the backup, you can restore all or some information of a user with the _restore_ command:
sogo-tool restore [-l|-p|-f/-F folder/ALL] [-c credentialFile] directory user
* First argument must be one of the four mode options *-l*, *-p*, *-f* or *-F*.
* Second, optional, is *-c* with the credential file. Only useful with *-p* mode.
* *directory* is the path where the backup file is.
* *user* is the name of the backup file, which is the username if you use the _backup_ tool.
The differents mode are:
* *-l* will only list the folders (meaning calendar and address book) that would be restored with *-f* or *-F*.
Example:
----
sogo-tool restore -l /tmp/backup user1
----
Result:
----
Calendar/60C8-65323D80-7-4D9F7D80 (new_calendar)
Calendar/personal (Personal Calendar)
Contacts/personal (Personal Address Book)
----
* *-p* will restore the user's preferences. If the user has an active sieve script (filter, vacation, forward...) you must provide a credential file with
the parameters *-c*. The credential file is a simple one-line file that contains the "username:password" of an admin account of your imap/sieve server.
Example:
----
sogo-tool restore -p /tmp/backup user1
sogo-tool restore -p -c /var/sogo/cred /tmp/backup user1
----
* *-f* will restore the events/contacts of folders (calendar and address book) from the backup file that don't exist in the database. If the event/contact
was deleted but is in the backup file, it will be restored. If the event/contact exist, nothing will be done even if it has been modified compared to the
backup file. If a whole folder has been deleted but is in the backup file, it will be restored. *-f* expect a value that can be ALL to restore all folders,
or the name of the folder to restore. You can list them with the *-l* mode.
Example:
----
sogo-tool restore -f ALL /tmp/backup user1
sogo-tool restore -f "Contacts/personal" tmp/backup user1
sogo-tool restore -f "Calendar/60C8-65323D80-7-4D9F7D80" tmp/backup user1
----
The commands will either prints out nothing or any events/contacts restored:
----
restoring record '60CA-65323D00-1-680B0A00.ics'
restoring record '60C8-65323D00-1-4D9F7D80.vcf'
restoring record '60C8-65323D80-9-4D9F7D80.ics'
----
* *-F* has the same behavior as *-f* but it will first delete all contacts and events in database before restoring the backup.
So if an event/contact is not in the backup file, it will be lost. If a folder exists but is not in the backup file, nothing will happen
to it and its content.
*-F* expect a value that can be ALL to restore all folders, or the name of the folder to restore. You can list them with the *-l* mode.
Example:
----
sogo-tool restore -F ALL /tmp/backup user1
sogo-tool restore -F "Contacts/personal" tmp/backup user1
sogo-tool restore -F "Calendar/60C8-65323D80-7-4D9F7D80" tmp/backup user1
----
sogo-tool checkup
~~~~~~~~~~~~~~~~~
Check the events and contacts data's integrity of a user
sogo-tool checkup [-d] user...
* *-d*, optional, will delete all corrupted data.
* *user* is a single username or multiple user each space-separated.
Will print out nothing if no corrupted data were found or one of the following messages:
* Corrupted calendar item (missing tags) in path <path> with c_name = <ics_id>
* Corrupted calendar item (unparsable) in path <path> with c_name = <ics_id>
* Missing start date of event in path <path> with c_name = <ics_id> (<event_name>)"
* Missing end date of event in path <path> with c_name = <ics_id> (<event_name>)"
* Start date (<start_date>) is not before end date (<end_date>) for event in path <path> with c_name = <ics_id> (<event_name>)
* Corrupted card item (unparsable) in path <path> with c_name = <vcard_id>
It can also print log from SOGo if it raise errors:
* <0x0x55e0956c4d00[VSCardSaxDriver]> serious inconsistency among begin/end tags
Example:
----
sogo-tool checkup user1
sogo-tool checkup -d user1
----
sogo-tool cleanup
~~~~~~~~~~~~~~~~~
Will purge all user's deleted events and contacts which the deletion is older than a number of days.
sogo-tool cleanup [days] ALL|user1 [user2]
* *days* the age of deleted records to purge in days
* Second argument is *ALL* to purge events of all users or put each user space-separated.
Example:
----
sogo-tool cleanup 30 ALL
sogo-tool cleanup 30 user1 user2
----
Outputs:
----
Purged 3 records from folder /Users/user1/Calendar/60C7-65325300-9-18F39AA0
Purged 1 records from folder /Users/user1/Calendar/60C8-65323D80-7-4D9F7D80
Purged 0 records from folder /Users/user1/Calendar/personal
Purged 0 records from folder /Users/user1/Contacts/personal
Purged 5 records from folder /Users/user2/Calendar/personal
----
sogo-tool create-folder
~~~~~~~~~~~~~~~~~~~~~~~
Create a folder (Calendar or Address Book) for a user.
create-folder user type [displayname ...]
* *user* is the name of the user
* *type* is either "Calendar" or "Contacts"
* *displayname* is the folder's name. If the name is already taken, a new folder will still be made but with another uid.
Example:
----
sogo-tool create-folder user1 Contacts Pro_Contacts Ext_Contacts
sogo-tool create-folder user2 Calendar Pro_Calendar
----
sogo-tool dump-defaults
~~~~~~~~~~~~~~~~~~~~~~~
Output all current defaults value of GNUstep and SOGo (sogo.conf)
sogo-tool dump-defaults [-f <filename>|[all]]
* Can be used without arguments and will output the defaults for gnustep domain 'sogod'.
* *all* option will output all defaults value found from sogo and gnustep in property list format.
* *-f* expect a filepath of an .xml file and will output it in property list format (sogo.conf). May need some tweaks, though.
Example:
----
sogo-tool dump-defaults
sogo-tool dump-defaults all
sogo-tool dump-defaults -f /tmp/foo/conf.xml
----
sogo-tool expire-sessions
~~~~~~~~~~~~~~~~~~~~~~~~~
Expires user sessions from database without activity for specified number of minutes. When a user log in to sogo for
the first time, sogo will create a entry in database's table OCSSessionsFolderURL with the user's information. Sogo will
also set a cookie with an encoded value to fetch this database table. That way, each time the user make a request with that cookie,
sogo will know which connected user it is. This command will remove all user's session from database
without activity for specified number of minutes. Those users will have to log in again.
sogo-tool expire-sessions [nbMinutes]
* *nbMinutes* Integer, number of minutes. All session without activity in these last minutes will be removed.
Example:
----
sogo-tool expire-sessions #Will print usage.
sogo-tool expire-sessions 160 #Will remove session which last activity is older than 160 minutes.
sogo-tool expire-sessions 0 #Will remove session which last activity is older than 0 minutes.
----
sogo-tool manage-acl
~~~~~~~~~~~~~~~~~~~~
Manage access-control list (ACL) of a user for folders (Calendar and Address Book).
sogo-tool manage-acl get|add|remove|subscribe|unsubscribe owner folder user|group <rights>
* First argument is the action among *get*, *add*, *remove*, *subscribe* and *unsubscribe*.
* Second argument is the username of the folder's owner.
* Third argument is the name of the folder.
* Fourth argument is the user whom to manage its acl. It also can be _ALL_, _anonymous_ and _<defaults>_.
* Fifth argument, only needed with *add* action, is the rights to set for the user.
The actions:
* *get* will print out the folder's rights of the user or nothing if the user is not found or doesn't have any rights.
Example:
----
sogo-tool manage-acl get sogo-tests1 Calendar/5E1F-653FC400-1-38155940 sogo-tests2
----
Result:
----
Rights for sogo-tests2 ["PublicModifier", "ConfidentialModifier", "PrivateModifier", "ObjectCreator", "ObjectEraser"]
----
* *add* will add the given rights to the user for the given folder. the value right is a json string of an array with each right to set.
Be Careful as there is no check for the rights already set. It could be better to first remove them, then add them.
The rights for Address Book are:
* _ObjectCreator_: can create cards (contact of list)
* _ObjectEditor_: can modify cards
* _ObjectViewer_: can view cards
* _ObjectEraser_: can delete cards
The rights for Calendar are:
* _ObjectCreator_: can create event or task
* _ObjectEraser_: can delete event or task
* _<type>_ can be _Public_, _Confidential_ or _Private_
* _<type>Viewer_: can view the whole events (ex: PrivateViewer)
* _<type>DAndTViewer_: can only view the date and time of events
* _<type>Modifier_: can view and edit the events
* _<type>Responder_: can view the events
Example:
----
sogo-tool manage-acl add sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2 '["ObjectViewer", "ObjectEraser"]'
----
* *remove* all user's rights to the folder.
Example:
----
sogo-tool manage-acl remove sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
----
* *subscribe* the user to the folder.
Example:
----
sogo-tool manage-acl subscribe sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
----
* *unsubscribe* the user to the folder.
Example:
----
sogo-tool manage-acl unsubscribe sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
----
sogo-tool manage-eas
~~~~~~~~~~~~~~~~~~~~
Manage EAS folders
sogo-tool manage-eas listdevices|listfolders|resetdevice|resetfolder|mergevcard|mergevevent user <deviceId | folderId> <YES | NO>
* First argument is the action among *listdevices*, *listfolders*, *resetdevice*, *resetfolder*, *mergevcard* and *mergevevent*.
* Second argument is the *user* whom to perform the action.
* Third argument is the *deviceId* of, if the action is *resetFolder*, the *folderId*. No need of this argument if the action is *listdevices*
* Fourth argument, only use with *mergevcard* and *mergevevent* is either YES to merge and *NO* to un-merge.
The actions:
* *listdevices*: list the devices belonging to user.
* *listfolders*: list all folders of deviceId for user.
* *resetdevice*: force deviceId of user to resync everything.
* *resetfolder*: force folderId of user to resync everything.
* *mergevcard*: merge/un-merge all addressbooks into one for deviceId of user.
* *mergevevent*: merge/un-merge all calendars into one for deviceId of user.
Examples:
----
sogo-tool manage-eas listdevices janedoe
sogo-tool manage-eas listfolders janedoe androidc316986417
sogo-tool manage-eas resetdevice janedoe androidc316986417
sogo-tool manage-eas resetfolder janedow androidc316986417 folderlala-dada-sasa_7a13_1a2386e0_e
sogo-tool manage-eas mergevcard janedow androidc316986417 YES
sogo-tool manage-eas mergevevent janedow androidc316986417 YES
----
sogo-tool remove
~~~~~~~~~~~~~~~~
Remove all folders (Calendar and Address Book) and Preference settings of a user. The personal Calendar and Address Book
will stay but be emptied of all entries. The Preferences will go back to defaults values as for a new user.
sogo-tool remove user1 [user2]
* Arguments one user or several space-separated.
* You can add -v after sogo*tool to list all removed folders
Example:
----
sogo-tool remove user1
sogo-tool -v remove user2
----
Output example with -v options:
----
Deleting /Users/user2/Calendar/B088-65363400-3-2B9DF0C0
Deleting /Users/user2/Calendar/personal
Deleting /Users/user2/Contacts/B087-65363380-5-374DA380
Deleting /Users/user2/Contacts/B087-65363400-7-374DA380
Deleting /Users/user2/Contacts/personal
----
sogo-tool remove-doubles
~~~~~~~~~~~~~~~~~~~~~~~~
remove duplicate contacts from the specified user addressbook
sogo-tool remove-doubles USER FOLDER
* *USER* is the name of the user to perform the removal
* *FOLDER* is the name of the folder to clean from duplicates
Two contacts are considered duplicates when they have the same mails or display name. When two or more contacts
are duplicates, each one will get a score. The ones with the highest score will be kept and the others
will be discarded. If two records have the same score, the first one to have reach it will be kept.
The scores are distributed as such:
* Record which has been the last modified: +1
* Record has the most content: +2
* Record has the most quick field set: +3
* Record is in a contact list: +6
Example:
----
sogo-tool remove-doubles user1 personal
sogo-tool remove-doubles user2 489-65376E80-1-2D2BA000
----
sogo-tool rename-user
~~~~~~~~~~~~~~~~~~~~~
Update records pertaining to a user after a change of user id. Will change all folders path, subscriptions from others users,
all mention of user id in database.
sogo-tool rename-user fromuserid touserid
* *fromuserid* Previous user id
* *touserid* New user id
If the new user id already exist, the command will output a message and do nothing.
**WARNING** This command only change database of sogo, if you use ldap or others databases they will still keep the old id.
Example:
----
sogo-tool rename-user old_username new_username
----
sogo-tool truncate-calendar
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Remove old calendar entries from the specified user calendar.
sogo-tool truncate-calendar [-r] USER FOLDER DATE
* *-r* optional, to also delete recurrent events that have occurrence after *DATE*
* *USER* is the name of the owner of the calendar
* *FOLDER* is the name of the calendar folder to clean up
* *DATE* UTC datetime, event older than this date will be removed
Without *-r*, only reccurrent events which all occurrences are older than *DATE* will be removed.
With *-r*, event the if there are occurrences after *DATE*, the recurrent event will be removed.
*DATE* can take the value `date +%FT%T` which is the current time
Example:
----
sogo-tool truncate-calendar sogo-tests1 personal 2023-10-25T00:00:00
sogo-tool truncate-calendar -r sogo-tests2 5E38-6538BE80-5-AFF8310 2022-01-01T15:30:00
sogo-tool truncate-calendar -r sogo-tests2 personal `date +%FT%T` #will delete all event older than now
----
Result:
----
No record to remove. All records kept.
----
----
Removing 1 records...
Removed 1 records.
----
sogo-tool update-autoreply
~~~~~~~~~~~~~~~~~~~~~~~~~~
_This command is only useful if your sieve server doesn't have the capabilities *date* or *relational*_
In that case this command will check vacation's setting of all the users. If vacation is enabled/disabled,
the starting and ending date, the body and others parameters. If needed, the command
will write the sieve script accordingly.
sogo-tool update-autoreply -p credentialFile
* *-p- The credential file, a simple one-line file that contains "username:password" of an admin account of your imap/sieve server.
Example:
----
sogo-tool update-autoreply -p /var/sogo/cred
----
sogo-tool user-preferences
~~~~~~~~~~~~~~~~~~~~~~~~~~
Get, set or unset user defaults / settings in the database.
_Settings_ stores parameters for user's calendar, mail and contacts. It can be graphic
as month view or week view for calendar but it also stores subscription, delegation...
It also stores the private Salt for TOTP and the parameter ForceResetPassword to force the
user to change password.
_Defaults_ stores all parameters that can be found in Preferences panel. So it goes from default language to
autoreply to many others options.
user-preferences get|set|unset defaults|settings user key [value|-f filename] [-p credentialFile]
* First argument is the action *get*, *set* or *unset*
* Second argument is parameter type *settings* or *defaults*
* Third argument *user* is the name of the user where to make the action
* Fourth argument *key* is the name of the parameters where to make the action
* If the action is *set*, you should provide either *value* as fifth argument or a file with the value inside with *-f* filename/path
* If the action is *set* or *unset* and the *key* concerns a sieve script (filter, vacation, forward...), you
should provide a credential file, a one-line file that contains "username:password" of an admin account of your imap/sieve server.
As there is a lot of parameters, this documentation will not go into details for each one. To know what are
the key names and their value you should got to your database in table defined by _SOGoProfileURL_ in your sogo.conf.
Here, for each row (user) you will find c_defaults and c_settings which are json with the keys. However, if
a parameter has never been set, it won't appears in those json. The clean way to know the missing keys is to
set it up in one of your dummy/dev/test account then see the values in _SOGoProfileURL_. Be careful, a value
can be a json itself, only the primary key can be get/set/unset.
Example with forwarding, *get* action:
----
sogo-tool user-preferences get defaults user1 Forward
----
If forward has never been set the result will be:
----
Value for key "Forward" not found in defaults
----
Else it will be
----
{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":1}
----
*unset* action:
----
sogo-tool user-preferences unset defaults user1 Forward -p /etc/sogo/cred
----
*set* action:
----
sogo-tool user-preferences set defaults user1 Forward '{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}' -p cred
----
or create a json file which contains _{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}_ then:
----
sogo-tool user-preferences set defaults user1 Forward -f /path/filename -p cred
----
Upgrading
---------
@@ -3262,6 +3825,11 @@ current version of SOGo from the previous release.
[cols="100a"]
|=======================================================================
h|5.9.0
|Run the shell script `sql-update-5.8.4_to_5.9.0.sh` (if you are using MySQL). This will change the `c_defaults` to `MEDIUMTEXT`.
For mac Apple's Calendar users who re-create their calendar in 5.8.2, the account must be removed and re-added (https://bugs.sogo.nu/view.php?id=5639#c16901). This can be achived in `` / `System Settings` / `Internet Accounts`.
h|5.8.2
|Changes in Mac OS X Ventura 13.3. For mac Apple's Calendar users, the account must be removed and re-added (https://bugs.sogo.nu/view.php?id=5639#c16901). This can be achived in `` / `System Settings` / `Internet Accounts`.
@@ -22,7 +22,7 @@ devices (iPhone, iPod touch, and iPad).
The instructions are based on version {release_version} of SOGo.
The latest version of this guide is available at
https://sogo.nu/downloads/documentation.html.
https://www.sogo.nu/support.html#/documentation.
Introduction
------------
@@ -22,7 +22,7 @@ Guide_ for more information on installing and configuring SOGo.
The instructions are based on version {release_version} of SOGo.
The latest version of this guide is available
at https://sogo.nu/downloads/documentation.html.
at https://www.sogo.nu/support.html#/documentation.
Introduction
------------
+11 -4
View File
@@ -349,8 +349,11 @@ static BOOL debugLeaks;
{
if ([[context request] handledByDefaultHandler])
authenticator = [SOGoWebAuthenticator sharedSOGoWebAuthenticator];
else
else {
authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
[authenticator setContext: context];
}
}
return authenticator;
@@ -363,10 +366,14 @@ static BOOL debugLeaks;
{
SOGoUser *user;
id userFolder;
user = [SOGoUser userWithLogin: _key roles: nil];
NSData *decodedLogin;
NSString *login;
login = [SOGoUser getDecryptedUsernameIfNeeded: _key withContext: _ctx];
user = [SOGoUser userWithLogin: login roles: nil];
if (user)
userFolder = [$(@"SOGoUserFolder") objectWithName: _key
userFolder = [$(@"SOGoUserFolder") objectWithName: login
inContainer: self];
else
userFolder = nil;
+4 -3
View File
@@ -12,16 +12,16 @@ There are multiple ways you can contribute to the project
## Source Code
You can browse the lastest version of the source code online from Inverse's github repository:
You can browse the lastest version of the source code online from Alinto's github repository:
* [https://github.com/Alinto/sogo](https://github.com/Alinto/sogo)
To compile SOGo, you first need to obtain the source code of both SOGo and SOPE. The source code of SOPE and SOGo can be obtained from Inverse's github repositories:
To compile SOGo, you first need to obtain the source code of both SOGo and SOPE. The source code of SOPE and SOGo can be obtained from Alinto's github repositories:
* [https://github.com/Alinto/sope/archive/master.zip](https://github.com/Alinto/sope/archive/master.zip)
* [https://github.com/Alinto/sogo/archive/master.zip](https://github.com/Alinto/sogo/archive/master.zip)
The source code of the SOGo Connector extension for Thunderbird 78+ can be obtained from Inverse's github repository:
The source code of the SOGo Connector extension for Thunderbird 78+ can be obtained from Alinto's github repository:
* [https://github.com/Alinto/sogo-connector](https://github.com/Alinto/sogo-connector)
@@ -46,6 +46,7 @@ SOGo and its associated components are available in various languages. The follo
* [nl] Dutch - Roel van Os
* [fi] Finnish - Kari Salmu
* [fr] French - [Alinto](https://www.alinto.com)
* [gl] Galician - Simon Lang
* [de] German - Alexander Greiner-Baer
* [he] Hebrew - Raz Aidlitz
* [hu] Hungarian - Sándor Kuti
+8 -1
View File
@@ -1145,9 +1145,16 @@ andAttribute: (EOAttribute *)_attribute
{
// Update c_startdate for appointments
if ([theComponent respondsToSelector:@selector(startDate)]) {
NSTimeInterval t;
startDate = [theComponent startDate];
t = [startDate timeIntervalSince1970];
if (startDate) {
[quickRow setObject:[NSNumber numberWithUnsignedInt:[startDate timeIntervalSince1970]] forKey:@"c_startdate"];
if (t < (long long)INT_MAX && t > (long long)INT_MIN) {
[quickRow setObject:[NSNumber numberWithInt: t] forKey:@"c_startdate"];
} else {
[self errorWithFormat:@"Unexpected value. Invalid start date for timestamp %f", t];
[quickRow setObject:[NSNumber numberWithInt: 0] forKey:@"c_startdate"];
}
}
}
+13 -13
View File
@@ -159,28 +159,28 @@ static NGCardsSaxHandler *sax = nil;
CardElement *newChild;
if (aChild)
{
childTag = [aChild tag];
newChild = nil;
mappedClass = [self classForTag: [childTag uppercaseString]];
if (mappedClass)
{
if (![aChild isKindOfClass: mappedClass])
{
childTag = [aChild tag];
newChild = nil;
mappedClass = [self classForTag: [childTag uppercaseString]];
if (mappedClass)
{
if (![aChild isKindOfClass: mappedClass])
{
//NSLog (@"warning: new child to entity '%@': '%@' converted to '%@'",
// tag, childTag, NSStringFromClass(mappedClass));
newChild = [aChild elementWithClass: mappedClass];
}
}
}
// else
// NSLog (@"warning: no mapped class for tag '%@'",
// childTag);
if (!newChild)
newChild = aChild;
[children addObject: newChild];
[newChild setParent: self];
}
if (!newChild)
newChild = aChild;
[children addObject: newChild];
[newChild setParent: self];
}
}
- (CardElement *) uniqueChildWithTag: (NSString *) aTag
+33 -34
View File
@@ -85,13 +85,12 @@
tzId = [self value: 0 ofAttribute: @"tzid"];
if ([tzId length])
{
calendar
= (iCalCalendar *) [self searchParentOfClass: [iCalCalendar class]];
{
calendar = (iCalCalendar *) [self searchParentOfClass: [iCalCalendar class]];
timeZone = [calendar timeZoneWithId: tzId];
//if (!timeZone)
//[self logWithFormat: @"timezone '%@' not found in calendar", tzId];
}
}
return timeZone;
}
@@ -169,37 +168,37 @@
count = [subValues count];
dates = [NSMutableArray arrayWithCapacity: count];
for (i = 0; i < count; i++)
{
date = [subValues objectAtIndex: i];
iTZ = [self timeZone];
{
date = [subValues objectAtIndex: i];
iTZ = [self timeZone];
if (iTZ)
dateTime = [iTZ dateForDateTimeString: date];
else
{
initialDate = [date asCalendarDate];
if (initialDate)
dateTime = initialDate;
/*
{
if ([date hasSuffix: @"Z"] || [date hasSuffix: @"z"])
dateTime = initialDate;
else
{
// same TODO as above
tz = [NSTimeZone defaultTimeZone];
dateTime = [initialDate addYear: 0 month: 0 day: 0
hour: 0 minute: 0
second: -[tz secondsFromGMTForDate: initialDate]];
}
}
*/
else
dateTime = nil;
}
if (dateTime)
[dates addObject: dateTime];
}
if (iTZ)
dateTime = [iTZ dateForDateTimeString: date];
else
{
initialDate = [date asCalendarDate];
if (initialDate)
dateTime = initialDate;
/*
{
if ([date hasSuffix: @"Z"] || [date hasSuffix: @"z"])
dateTime = initialDate;
else
{
// same TODO as above
tz = [NSTimeZone defaultTimeZone];
dateTime = [initialDate addYear: 0 month: 0 day: 0
hour: 0 minute: 0
second: -[tz secondsFromGMTForDate: initialDate]];
}
}
*/
else
dateTime = nil;
}
if (dateTime)
[dates addObject: dateTime];
}
return dates;
}
+6
View File
@@ -72,8 +72,14 @@
- (void) setEndDate: (NSCalendarDate *) newEndDate
{
CardElement *c;
[(iCalDateTime *) [self uniqueChildWithTag: @"dtend"]
setDateTime: newEndDate];
c = [self uniqueChildWithTag: @"duration"];
if (c)
[self removeChild: c];
}
- (NSCalendarDate *) endDate
+6 -2
View File
@@ -124,8 +124,10 @@
if(!IS_EQUAL([_from created], [_to created], isEqualToDate:))
[updatedProperties addObject:@"created"];
if(!IS_EQUAL([_from lastModified], [_to lastModified], isEqualToDate:))
[updatedProperties addObject:@"lastModified"];
if(![_from durationAsTimeInterval] == [_to durationAsTimeInterval])
[updatedProperties addObject:@"timeStampAsDate"];
if(!IS_EQUAL([_from timeStampAsDate], [_to timeStampAsDate], isEqualToDate:))
[updatedProperties addObject:@"timeStampAsDate"];
if([_from durationAsTimeInterval] != [_to durationAsTimeInterval])
[updatedProperties addObject:@"duration"];
if(!IS_EQUAL([_from summary], [_to summary], isEqualToString:))
[updatedProperties addObject:@"summary"];
@@ -145,6 +147,8 @@
[updatedProperties addObject:@"organizer"];
if(!IS_EQUAL([_from recurrenceRules], [_to recurrenceRules], isEqual:))
[updatedProperties addObject:@"rrule"];
if(!IS_EQUAL([_from recurrenceId], [_to recurrenceId], isEqualToDate:))
[updatedProperties addObject:@"rid"];
if(!IS_EQUAL([_from exceptionRules], [_to exceptionRules], isEqual:))
[updatedProperties addObject:@"exrule"];
if(!IS_EQUAL([_from exceptionDates], [_to exceptionDates], isEqual:))
+147 -153
View File
@@ -180,8 +180,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
* There's no GUI to defined such conditions, so there's no
* problem for now.
*/
- (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
// TODO: check whether this is OK for multiday-events!
NSMutableArray *ranges;
@@ -240,31 +239,31 @@ static inline unsigned iCalDoWForNSDoW (int dow)
}
if (byMonth && [byMonth count] > 0)
{
int i;
for (i = 0; i < 12; i++)
byMonthList[i] = [byMonth containsObject: [NSString stringWithFormat: @"%i", i + 1]];
}
{
int i;
for (i = 0; i < 12; i++)
byMonthList[i] = [byMonth containsObject: [NSString stringWithFormat: @"%i", i + 1]];
}
/* precalculate month days */
if (byMonthDay)
{
NGMonthDaySet_fillWithByMonthDay (&byPositiveMonthDaySet, &byNegativeMonthDaySet, byMonthDay);
}
{
NGMonthDaySet_fillWithByMonthDay (&byPositiveMonthDaySet, &byNegativeMonthDaySet, byMonthDay);
}
if (repeatCount > 0)
{
numberOfMonthsInRange = [eventStartDate monthsBetweenDate: rEnd] + 1;
}
{
numberOfMonthsInRange = [eventStartDate monthsBetweenDate: rEnd] + 1;
}
else
{
diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
diff = -diff;
{
diff = [eventStartDate monthsBetweenDate: rStart];
if ((diff != 0) && [rStart compare: eventStartDate] == NSOrderedAscending)
diff = -diff;
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
}
numberOfMonthsInRange = [rStart monthsBetweenDate: rEnd] + 1;
}
ranges = [NSMutableArray arrayWithCapacity: numberOfMonthsInRange];
@@ -292,172 +291,167 @@ static inline unsigned iCalDoWForNSDoW (int dow)
monthIdxInRecurrence = diff + monthIdxInRange;
if (monthIdxInRecurrence < 0)
continue;
continue;
/* first check whether we are in the interval */
if ((monthIdxInRecurrence % interval) != 0)
continue;
continue;
cursor = [referenceDate dateByAddingYears: 0
months: monthIdxInRecurrence
days: 0];
cursor = [referenceDate dateByAddingYears: 0 months: monthIdxInRecurrence days: 0];
[cursor setTimeZone: timeZone];
numDaysInMonth = [cursor numberOfDaysInMonth];
/* check whether we match the BYMONTH constraint */
if (!byMonthList[[cursor monthOfYear] - 1])
continue;
continue;
/* check whether we match the BYMONTHDAY and BYDAY constraints */
didByFill = NO;
if (byMonthDay)
{
// Initialize the monthDays array with the positive days positions
NGMonthDaySet_copyOrUnion (&monthDays, &byPositiveMonthDaySet, !didByFill);
{
// Initialize the monthDays array with the positive days positions
NGMonthDaySet_copyOrUnion (&monthDays, &byPositiveMonthDaySet, !didByFill);
// Add to the array the days matching the negative days positions
int i;
for (i = 1; i <= 31; i++)
if (byNegativeMonthDaySet[i])
monthDays[numDaysInMonth - i + 1] = YES;
didByFill = YES;
}
// Add to the array the days matching the negative days positions
int i;
for (i = 1; i <= 31; i++)
if (byNegativeMonthDaySet[i])
monthDays[numDaysInMonth - i + 1] = YES;
didByFill = YES;
}
if (byDayMask)
{
if (!didByFill)
NGMonthDaySet_clear (&monthDays);
{
if (!didByFill)
NGMonthDaySet_clear (&monthDays);
if (bySetPos)
{
NSUInteger monthDay;
NSInteger currentPos;
iCalWeekDay currentWeekDay;
if (bySetPos)
{
NSUInteger monthDay;
NSInteger currentPos;
iCalWeekDay currentWeekDay;
currentWeekDay = [[cursor firstDayOfMonth] dayOfWeek];
currentPos = 1;
for (monthDay = 0; monthDay <= numDaysInMonth; monthDay++)
{
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES;
currentPos++;
}
currentWeekDay = (currentWeekDay + 1) % 7;
}
currentWeekDay = [[cursor firstDayOfMonth] dayOfWeek];
currentPos = 1;
for (monthDay = 0; monthDay <= numDaysInMonth; monthDay++)
{
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay+1] = YES;
currentPos++;
}
currentWeekDay = (currentWeekDay + 1) % 7;
}
currentWeekDay = [[cursor lastDayOfMonth] dayOfWeek];
currentPos = -1;
for (monthDay = numDaysInMonth; monthDay > 0; monthDay--)
{
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES;
currentPos--;
}
if (currentWeekDay > 0)
currentWeekDay--;
else
currentWeekDay = 6;
}
}
else
{
unsigned int firstDoWInMonth, currentWeekDay;
unsigned int weekDaysCount[7], currentWeekDaysCount[7];
int i, positiveOrder, negativeOrder;
currentWeekDay = [[cursor lastDayOfMonth] dayOfWeek];
currentPos = -1;
for (monthDay = numDaysInMonth; monthDay > 0; monthDay--)
{
if ([byDayMask occursOnDay: currentWeekDay])
{
if ([bySetPos containsObject:
[NSString stringWithFormat: @"%d", (int)currentPos]])
monthDays[monthDay] = YES;
currentPos--;
}
if (currentWeekDay > 0)
currentWeekDay--;
else
currentWeekDay = 6;
}
}
else
{
unsigned int firstDoWInMonth, currentWeekDay;
unsigned int weekDaysCount[7], currentWeekDaysCount[7];
int i, positiveOrder, negativeOrder;
firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
firstDoWInMonth = [[cursor firstDayOfMonth] dayOfWeek];
// Fill weekDaysCount to handle negative positions
currentWeekDay = firstDoWInMonth;
memset(weekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
weekDaysCount[currentWeekDay]++;
currentWeekDay = (currentWeekDay + 1) % 7;
}
// Fill weekDaysCount to handle negative positions
currentWeekDay = firstDoWInMonth;
memset(weekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
weekDaysCount[currentWeekDay]++;
currentWeekDay = (currentWeekDay + 1) % 7;
}
currentWeekDay = firstDoWInMonth;
memset(currentWeekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
if (!didByFill || monthDays[i])
{
positiveOrder = currentWeekDaysCount[currentWeekDay] + 1;
negativeOrder = currentWeekDaysCount[currentWeekDay] - weekDaysCount[currentWeekDay];
monthDays[i] = (([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: positiveOrder]) ||
([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: negativeOrder]));
}
currentWeekDaysCount[currentWeekDay]++;
currentWeekDay = (currentWeekDay + 1) % 7;
}
}
didByFill = YES;
}
currentWeekDay = firstDoWInMonth;
memset(currentWeekDaysCount, 0, 7 * sizeof(unsigned int));
for (i = 1; i <= numDaysInMonth; i++)
{
if (!didByFill || monthDays[i])
{
positiveOrder = currentWeekDaysCount[currentWeekDay] + 1;
negativeOrder = currentWeekDaysCount[currentWeekDay] - weekDaysCount[currentWeekDay];
monthDays[i] = (([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: positiveOrder]) ||
([byDayMask occursOnDay: (iCalWeekDay)currentWeekDay
withWeekNumber: negativeOrder]));
}
currentWeekDaysCount[currentWeekDay]++;
currentWeekDay = (currentWeekDay + 1) % 7;
}
}
didByFill = YES;
}
if (didByFill)
{
if (diff + monthIdxInRange == 0)
{
// When dealing with the month of the first occurence, remove days
// that occur before the first occurrence.
memset (monthDays, NO, sizeof (BOOL) * eventDayOfMonth);
// The first occurrence must always be included.
monthDays[eventDayOfMonth] = YES;
}
}
{
if (diff + monthIdxInRange == 0)
{
// When dealing with the month of the first occurence, remove days
// that occur before the first occurrence.
memset (monthDays, NO, sizeof (BOOL) * eventDayOfMonth);
// The first occurrence must always be included.
monthDays[eventDayOfMonth] = YES;
}
}
else
{
// No rules applied, take the dayOfMonth of the startDate
NGMonthDaySet_clear (&monthDays);
monthDays[eventDayOfMonth] = YES;
}
{
// No rules applied, take the dayOfMonth of the startDate
NGMonthDaySet_clear (&monthDays);
monthDays[eventDayOfMonth] = YES;
}
/*
Next step is to create NSCalendarDate instances from our 'monthDays'
set. We walk over each day of the 'monthDays' set. If its flag isn't
set, we continue.
If its set, we add the date to the instance.
The 'cursor' is the *startdate* of the event (not necessarily a
component of the sequence!) plus the currently processed month.
Eg:
startdate: 2007-01-30
cursor[1]: 2007-01-30
cursor[2]: 2007-02-28 <== Note: we have February!
Next step is to create NSCalendarDate instances from our 'monthDays'
set. We walk over each day of the 'monthDays' set. If its flag isn't
set, we continue.
If its set, we add the date to the instance.
The 'cursor' is the *startdate* of the event (not necessarily a
component of the sequence!) plus the currently processed month.
Eg:
startdate: 2007-01-30
cursor[1]: 2007-01-30
cursor[2]: 2007-02-28 <== Note: we have February!
*/
for (dom = 1, doCont = YES; dom <= numDaysInMonth && doCont; dom++)
{
NSCalendarDate *start;
if (!monthDays[dom])
continue;
{
NSCalendarDate *start;
if (!monthDays[dom])
continue;
start = [cursor dateByAddingYears: 0 months: 0 days: (dom - 1)];
doCont = [self _addInstanceWithStartDate: start
limitDate: until
limitRange: _r
toArray: ranges];
//NSLog(@"*** MONTHLY [%i/%i] adding %@%@ (count = %i)", dom, numDaysInMonth, start, (doCont?@"":@" .. NOT!"), count);
if (repeatCount > 0)
{
count++;
//NSLog(@"MONTHLY count = %i/%i", count, repeatCount);
doCont = (count < repeatCount);
}
}
start = [cursor dateByAddingYears: 0 months: 0 days: (dom - 1)];
doCont = [self _addInstanceWithStartDate: start
limitDate: until
limitRange: _r
toArray: ranges];
//NSLog(@"*** MONTHLY [%i/%i] adding %@%@ (count = %i)", dom, numDaysInMonth, start, (doCont?@"":@" .. NOT!"), count);
if (repeatCount > 0)
{
count++;
//NSLog(@"MONTHLY count = %i/%i", count, repeatCount);
doCont = (count < repeatCount);
}
}
if (!doCont) break; /* reached some limit */
}
return ranges;
+26 -35
View File
@@ -110,7 +110,7 @@ static Class yearlyCalcClass = Nil;
/* complex calculation convenience */
+ (void) _fillRanges: (NSMutableArray *) ranges
+ (void) _fillRanges: (NSMutableArray *) ranges
fromRules: (NSArray *) rrules
withinRange: (NGCalendarDateRange *) limits
startingWithDate: (NGCalendarDateRange *) first
@@ -121,17 +121,14 @@ static Class yearlyCalcClass = Nil;
rules = [rrules objectEnumerator];
while ((currentRule = [rules nextObject]))
{
if ([currentRule isKindOfClass: NSStringClass])
currentRule =
[iCalRecurrenceRule
recurrenceRuleWithICalRepresentation: (NSString *) currentRule];
{
if ([currentRule isKindOfClass: NSStringClass])
currentRule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation: (NSString *) currentRule];
calc = [self recurrenceCalculatorForRecurrenceRule: currentRule
withFirstInstanceCalendarDateRange: first];
[ranges addObjectsFromArray:
[calc recurrenceRangesWithinCalendarDateRange: limits]];
}
calc = [self recurrenceCalculatorForRecurrenceRule: currentRule
withFirstInstanceCalendarDateRange: first];
[ranges addObjectsFromArray: [calc recurrenceRangesWithinCalendarDateRange: limits]];
}
}
+ (void) _fillRanges: (NSMutableArray *) ranges
@@ -235,27 +232,25 @@ static Class yearlyCalcClass = Nil;
dates = [[self _dates: exdates withinRange: limits startingWithDate: first] objectEnumerator];
while ((currentDate = [dates nextObject]))
{
maxRanges = [ranges count];
for (count = maxRanges; count > 0; count--)
{
currentRange = [ranges objectAtIndex: count - 1];
compare = [[currentRange startDate] compare: currentDate];
if ((compare == NSOrderedAscending || compare == NSOrderedSame) &&
[[currentRange endDate] compare: currentDate] == NSOrderedDescending)
{
[ranges removeObjectAtIndex: count - 1];
}
}
}
{
maxRanges = [ranges count];
for (count = maxRanges; count > 0; count--)
{
currentRange = [ranges objectAtIndex: count - 1];
compare = [[currentRange startDate] compare: currentDate];
if ( compare == NSOrderedSame)
{
[ranges removeObjectAtIndex: count - 1];
}
}
}
}
+ (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
+ (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
firstInstanceCalendarDateRange: (NGCalendarDateRange *) _fir
recurrenceRules: (NSArray *) _rRules
exceptionRules: (NSArray *) _exRules
recurrenceDates: (NSArray *) _rDates
recurrenceDates: (NSArray *) _rDates
exceptionDates: (NSArray *) _exDates
{
NSMutableArray *ranges;
@@ -264,14 +259,10 @@ static Class yearlyCalcClass = Nil;
if ([_rRules count] > 0 || [_rDates count] > 0)
{
[self _fillRanges: ranges fromRules: _rRules
withinRange: _r startingWithDate: _fir];
[self _fillRanges: ranges fromDates: _rDates
withinRange: _r startingWithDate: _fir];
[self _removeExceptionsFromRanges: ranges withRules: _exRules
withinRange: _r startingWithDate: _fir];
[self _removeExceptionDatesFromRanges: ranges withDates: _exDates
withinRange: _r startingWithDate: _fir];
[self _fillRanges: ranges fromRules: _rRules withinRange: _r startingWithDate: _fir];
[self _fillRanges: ranges fromDates: _rDates withinRange: _r startingWithDate: _fir];
[self _removeExceptionsFromRanges: ranges withRules: _exRules withinRange: _r startingWithDate: _fir];
[self _removeExceptionDatesFromRanges: ranges withDates: _exDates withinRange: _r startingWithDate: _fir];
}
return ranges;
+50 -44
View File
@@ -122,37 +122,44 @@
NSEnumerator *dateList;
NSCalendarDate *rDate;
NSString *dateString;
NSTimeZone *rdateTimezone;
int offset;
unsigned i;
if (theTimeZone)
{
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"rdate"] objectEnumerator];
{
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"rdate"] objectEnumerator];
while ((dateString = [dateList nextObject]))
{
rDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [rDates count]; i++)
while ((dateString = [dateList nextObject]))
{
rDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [rDates count]; i++)
{
rDate = [rDates objectAtIndex: i];
// Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000),
// and changes to 2012-05-24 04:00:00 +0000
if ([theTimeZone isKindOfClass: [iCalTimeZone class]])
{
rDate = [(iCalTimeZone *) theTimeZone computedDateForDate: rDate];
}
else
{
offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: rDate];
rDate = (NSCalendarDate *) [rDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
}
if ((rdateTimezone =[rDate timeZone]))
{
//The property rdate can have the timezone, https://www.kanzaki.com/docs/ical/rdate.html
//In that case, dont force the
}
// Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000),
// and changes to 2012-05-24 04:00:00 +0000
else if ([theTimeZone isKindOfClass: [iCalTimeZone class]])
{
rDate = [(iCalTimeZone *) theTimeZone computedDateForDate: rDate];
}
else
{
offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: rDate];
rDate = (NSCalendarDate *) [rDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset];
}
[(NSMutableArray *) dates addObject: rDate];
}
}
}
}
}
}
else
dates = [self recurrenceDates];
@@ -351,33 +358,32 @@
unsigned i;
if (theTimeZone)
{
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"exdate"] objectEnumerator];
{
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"exdate"] objectEnumerator];
while ((dateString = [dateList nextObject]))
{
exDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [exDates count]; i++)
while ((dateString = [dateList nextObject]))
{
exDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [exDates count]; i++)
{
exDate = [exDates objectAtIndex: i];
// Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000),
// and changes to 2012-05-24 04:00:00 +0000
if ([theTimeZone isKindOfClass: [iCalTimeZone class]])
{
exDate = [(iCalTimeZone *) theTimeZone computedDateForDate: exDate];
}
else
{
offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: exDate];
exDate = (NSCalendarDate *) [exDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
}
// Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000),
// and changes to 2012-05-24 04:00:00 +0000
if ([theTimeZone isKindOfClass: [iCalTimeZone class]])
{
exDate = [(iCalTimeZone *) theTimeZone computedDateForDate: exDate];
}
else
{
offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: exDate];
exDate = (NSCalendarDate *) [exDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset];
}
[(NSMutableArray *) dates addObject: exDate];
}
}
}
}
}
}
else
dates = [self exceptionDates];
+31 -5
View File
@@ -40,15 +40,15 @@
@implementation iCalYearlyRecurrenceCalculator
- (NSArray *)
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSArray *byMonth;
NSCalendarDate *firStart, *lastDate, *rStart, *rEnd, *until, *referenceDate;
NSCalendarDate *firStart, *lastDate, *rStart, *rEnd, *until, *referenceDate, *rTemp;
NSInteger *hoursOfOffset;
iCalMonthlyRecurrenceCalculator *monthlyCalc;
unsigned j, yearIdxInRange, numberOfYearsInRange, count, interval, monthDiff;
int diff, repeatCount, currentMonth;
int diff, repeatCount, currentMonth, origNbDaysInMonth;
firStart = [firstRange startDate];
rStart = [_r startDate];
@@ -185,9 +185,35 @@
rStart = [referenceDate dateByAddingYears: 0
months: monthDiff
days: 0];
//Due to the bug with dateByAddingYears, we may take the previous day (see line 133)
hoursOfOffset = [rStart hourOfDay];
origNbDaysInMonth = [rStart numberOfDaysInMonth];
if(hoursOfOffset > 12) {
//If rStart is 1st 22:00, we should start at the previous day 22:00
rTemp = [NSCalendarDate dateWithYear: [rStart yearOfCommonEra]
month: 1+([rStart monthOfYear]-2)%12
day: 1
hour: [rStart hourOfDay]
minute: [rStart minuteOfHour]
second: 0
timeZone: [rStart timeZone]];
rStart = [NSCalendarDate dateWithYear: [rStart yearOfCommonEra]
month: 1+([rStart monthOfYear]-2)%12
day: [rTemp numberOfDaysInMonth]
hour: [rStart hourOfDay]
minute: [rStart minuteOfHour]
second: 0
timeZone: [rStart timeZone]];
}
rEnd = [rStart dateByAddingYears: 0
months: 0
days: [rStart numberOfDaysInMonth]];
days: origNbDaysInMonth];
rangeForMonth = [NGCalendarDateRange calendarDateRangeWithStartDate: rStart
endDate: rEnd];
rangesInMonth = [monthlyCalc recurrenceRangesWithinCalendarDateRange: rangeForMonth];
+19 -20
View File
@@ -33,9 +33,11 @@ import sys
config = { "cas-addr": "127.0.0.1",
"memcached-addrs": ["127.0.0.1:11211"] }
class CASProxyValidator:
def run(self):
if os.environ.has_key("GATEWAY_INTERFACE"):
if "GATEWAY_INTERFACE" in os.environ:
self._runAsCGI()
else:
self._runAsCmd()
@@ -43,19 +45,19 @@ class CASProxyValidator:
def _runAsCGI(self):
if self._cgiChecks():
form = cgi.FieldStorage()
if form.list == []:
message = "Empty parameters : assuming cert. validation"
self._printCGIError(message, 200)
return
if form.has_key("pgtId") and form.has_key("pgtIou"):
pgtIou = form.getfirst("pgtIou")
pgtId = form.getfirst("pgtId")
self._registerPGTIdAndIou(pgtIou, pgtId)
message = "'%s' set to '%s'" \
% ("cas-pgtiou:%s" % pgtIou, pgtId)
self._printCGIError(message, 200)
else:
self._printCGIError("Missing parameter.")
if form.list == []:
message = "Empty parameters : assuming cert. validation"
self._printCGIError(message, 200)
return
if form.has_key("pgtId") and form.has_key("pgtIou"):
pgtIou = form.getfirst("pgtIou")
pgtId = form.getfirst("pgtId")
self._registerPGTIdAndIou(pgtIou, pgtId)
message = "'%s' set to '%s'" \
% ("cas-pgtiou:%s" % pgtIou, pgtId)
self._printCGIError(message, 200)
else:
self._printCGIError("Missing parameter.")
def _cgiChecks(self):
rc = False
@@ -71,17 +73,14 @@ class CASProxyValidator:
return rc
def _printCGIError(self, message, code = 403):
print("Status: %d\n"
"Content-Type: text/plain; charset=utf-8\n\n%s"
% (code, message))
print("Status: %d\nContent-Type: text/plain; charset=utf-8\n\n%s" % (code, message))
def _runAsCmd(self):
if len(sys.argv) == 3:
self._registerPGTIdAndIou(sys.argv[1], sys.argv[2])
print "set '%s' to '%s'" \
% ("cas-pgtiou:%s" % sys.argv[1], sys.argv[2])
print("set '%s' to '%s'" % ("cas-pgtiou:%s" % sys.argv[1], sys.argv[2]))
else:
raise Exception, "Missing or too many parameters."
raise Exception("Missing or too many parameters.")
def _registerPGTIdAndIou(self, pgtIou, pgtId):
mc = memcache.Client(config["memcached-addrs"])
+1 -1
View File
@@ -141,7 +141,7 @@ CREATE TABLE sogo_quick_contact (
CREATE TABLE sogo_sessions_folder (
c_id varchar(255) NOT NULL,
c_value varchar(255) NOT NULL,
c_value varchar(4096) NOT NULL,
c_creationdate int(11) NOT NULL,
c_lastseen int(11) NOT NULL,
PRIMARY KEY (c_id)
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail
# This script only works with PostgreSQL and MySQL - it does:
#
# - increase the c_defaults column of user profile table to medium text
profiletype=$(sogo-tool dump-defaults -f /etc/sogo/sogo.conf | awk -F\" '/ SOGoProfileURL =/ {print $2}' | awk -F: '{ print $1 }')
if [ -z "$profiletype" ]; then
echo "Failed to obtain session table type" >&2
exit 1
fi
if [[ "$profiletype" == "mysql" ]]; then
profiletable=$(sogo-tool dump-defaults -f /etc/sogo/sogo.conf | awk -F\" '/ SOGoProfileURL =/ {print $2}' | awk -F/ '{print $NF}')
mysqlargs=$(sogo-tool dump-defaults -f /etc/sogo/sogo.conf | awk -F\" '/ SOGoProfileURL =/ {print $2}' | sed 's/mysql:\/\/\([^:]\+\):\([^@]\+\)@\([^\:]\+\):\([^\/]\+\)\/\([^\/]\+\).\+/-h \3 -P \4 -u \1 -p\2 \5/')
echo "Converting c_defaults from TEXT to MEDIUMTEXT in sessions table ($profiletable)"
mysql -v $mysqlargs -e "ALTER TABLE $profiletable MODIFY c_defaults MEDIUMTEXT;"
else
echo "Unsupported database type $profiletype"
exit 1
fi
exit 0
@@ -0,0 +1,82 @@
"Inviting the following persons is prohibited:" = "Está prohibido convidar ás seguintes persoas:";
"Personal Calendar" = "Calendario persoal";
vevent_class0 = "(Evento público)";
vevent_class1 = "(Evento privado)";
vevent_class2 = "(Evento confidencial)";
vtodo_class0 = "(Tarefa pública)";
vtodo_class1 = "(Tarefa privada)";
vtodo_class2 = "(Tarefa confidencial)";
/* Receipts */
"The event \"%{Summary}\" was created" = "Creouse o evento \"%{Summary}\"";
"The event \"%{Summary}\" was deleted" = "Eliminouse o evento \"%{Summary}\"";
"The event \"%{Summary}\" was updated" = "Actualizouse o evento \"%{Summary}\"";
"The task \"%{Summary}\" was created" = "Creouse a tarefa \"%{Summary}\"";
"The task \"%{Summary}\" was deleted" = "Eliminouse a tarefa \"%{Summary}\" ";
"The task \"%{Summary}\" was updated" = "Actualizouse a tarefa \"%{Summary}\"";
"The following attendees(s) were notified" = "Notificouse aos seguintes asistentes";
"The following attendees(s) were added" = "Agregaronse os seguintes asistentes";
"The following attendees(s) were removed" = "Eliminaronse os seguintes asistentes";
/* IMIP messages */
"calendar_label" = "Calendario";
"startDate_label" = "Inicio";
"endDate_label" = "Fin";
"time_label" = "Hora";
"to_label" = "para";
"due_label" = "Data de vencemento";
"location_label" = "Localización";
"summary_label" = "Resumo";
"comment_label" = "Comentario";
"organizer_label" = "Organizador";
"attendee_label" = "Asistente";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Invitación ao evento: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(enviado por %{SentBy})";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidoulle a %{Summary}.\n\nInicio: %{StartDate}\nFin: %{EndDate}\nDescrición: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText}convidoulle a %{Summary}.\n\nInicio: %{StartDate} a %{StartTime}\nFin: %{EndDate} en %{EndTime}\nDescrición: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Evento cancelado: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} cancelou este evento: %{Summary}.\n\nInicio: %{StartDate}\nFin: %{EndDate}\nDescrición: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
= "%{Organizer} %{SentByText}cancelou este evento: %{Summary}.\n\nInicio: %{StartDate} en %{StartTime}\nFin: %{EndDate} en %{EndTime}\nDescrición: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "A cita \"%{Summary}\" para o %{OldStartDate} cambiou";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "A cita \"%{Summary}\" para a %{OldStartDate} en %{OldStartTime} cambiou";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "Os seguintes parámetros cambiaron na reunión \"%{Summary}\":";
"Please accept or decline those changes."
= "Por favor, acepte ou rexeite eses cambios.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Invitación aceptada: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Invitación rexeitada: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Invitación delegada: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Aínda non se decidiu sobre a invitación: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText} aceptou a súa invitación ao evento.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText} rexeitou a súa invitación ao evento.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee}%{SentByText} delegou a invitación a %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} aínda non decidiu sobre a súa invitación ao evento.";
/* Reminder */
"Reminder: \"%{Summary}\" - %{StartDate}" = "Recordatorio: \"%{Summary}\" - %{StartDate}";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Non se pode acceder ao recurso: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Número máximo de reservas simultáneas (%{NumberOfSimultaneousBookings}) alcanzado para o recurso \"%{Cn} %{SystemEmail}\". O evento en conflito é \"%{EventTitle}\" e comeza en %{StartDate}.";
/* Participation role */
"Your participation is required to this event" = "Requírese a súa participación neste evento";
"Your participation is optional to this event" = "A súa participación é opcional a este evento";
"Your participation is not required to this event" = "Non se require a súa participación neste evento";
@@ -15,9 +15,9 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
"The task \"%{Summary}\" was created" = "Die Aufgabe \"%{Summary}\" wurde angelegt";
"The task \"%{Summary}\" was deleted" = "Die Aufgabe \"%{Summary}\" wurde gelöscht";
"The task \"%{Summary}\" was updated" = "Die Aufgabe \"%{Summary}\" wurde geändert";
"The following attendees(s) were notified" = "Die folgenden Teilnehmer wurden benachrichtigt";
"The following attendees(s) were added" = "Die folgenden Teilnehmer wurden hinzugefügt";
"The following attendees(s) were removed" = "Die folgenden Teilnehmer wurden entfernt";
"The following attendees(s) were notified" = "Die folgenden Teilnehmer*innen wurden benachrichtigt";
"The following attendees(s) were added" = "Die folgenden Teilnehmer*innen wurden hinzugefügt";
"The following attendees(s) were removed" = "Die folgenden Teilnehmer*innen wurden entfernt";
/* IMIP messages */
"calendar_label" = "Kalender";
@@ -30,12 +30,12 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
"summary_label" = "Zusammenfassung:";
"comment_label" = "Kommentar:";
"organizer_label" = "Organisator";
"attendee_label" = "Teilnehmer";
"attendee_label" = "Teilnehmer*in";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Termineinladung: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(gesendet von %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} Hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate}\nEnde: %{EndDate}\nBeschreibung: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} hat Sie eingeladen zu \"%{Summary}\".\n\nBeginn: %{StartDate} um %{StartTime}\nEnde: %{EndDate} um %{EndTime}\nBeschreibung: %{Description}";
/* Deletion */
@@ -47,13 +47,13 @@ vtodo_class2 = "(Vertrauliche Aufgabe)";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "Die Verabredung \"%{Summary}\" am %{OldStartDate} wurde geändert";
= "Der Termin \"%{Summary}\" am %{OldStartDate} wurde geändert";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "Der Termin \"%{Summary}\" am %{OldStartDate} um %{OldStartTime} hat sich geändert";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "Folgendes wurde am Termin \"%{Summary}\" geändert:";
"Please accept or decline those changes."
= "Bitte akzeptieren Sie diese Änderung oder lehnen Sie diese ab.";
= "Bitte akzeptieren Sie diese Änderungen, oder lehnen Sie diese ab.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Einladung zugestimmt: \"%{Summary}\"";
@@ -12,9 +12,9 @@ vtodo_class2 = "(Konfidenciali užduotis)";
"The event \"%{Summary}\" was created" = "Įvykis \"%{summary}\" buvo sukurtas";
"The event \"%{Summary}\" was deleted" = "Įvykis \"%{Summary}\" buvo ištrintas";
"The event \"%{Summary}\" was updated" = "Įvykis \"%{Summary}\" buvo atnaujintas";
"The task \"%{Summary}\" was created" = "The task \"%{Summary}\" was created";
"The task \"%{Summary}\" was deleted" = "The task \"%{Summary}\" was deleted";
"The task \"%{Summary}\" was updated" = "The task \"%{Summary}\" was updated";
"The task \"%{Summary}\" was created" = "Įvykis \"%{summary}\" buvo sukurtas";
"The task \"%{Summary}\" was deleted" = "Įvykis \"%{Summary}\" buvo ištrintas";
"The task \"%{Summary}\" was updated" = "Įvykis \"%{Summary}\" buvo atnaujintas";
"The following attendees(s) were notified" = "Šiems dalyviui(iams) buvo pranešta";
"The following attendees(s) were added" = "Šie dalyvis(iai) buvo pridėti";
"The following attendees(s) were removed" = "Šie dalyvis(iai) buvo pašalinti";
@@ -23,23 +23,23 @@ vtodo_class2 = "(Konfidenciali užduotis)";
"calendar_label" = "Kalendorius";
"startDate_label" = "Pradėti";
"endDate_label" = "Pabaiga";
"time_label" = "Time";
"to_label" = "to";
"time_label" = "Laikas";
"to_label" = "iki";
"due_label" = "Terminas";
"location_label" = "Vietovė";
"summary_label" = "Santrauka";
"comment_label" = "Komentaras:";
"organizer_label" = "Organizer";
"attendee_label" = "Attendee";
"organizer_label" = "Organizatorius";
"attendee_label" = "Dalyvis";
/* Invitation */
"Event Invitation: \"%{Summary}\"" = "Įvykio pakvietimas: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(išsiųsta nuo %{SentBy}) ";
"Event Invitation: \"%{Summary}\"" = "Kvietimas į renginį: \"%{Summary}\"";
"(sent by %{SentBy}) " = "(išsiųsta %{SentBy}) ";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}" = "%{Organizer} %{SentByText} pakvietė Jus į %{Summary}.\nPradžia: %{StartDate}\nPabaiga: %{EndDate}\nAprašymas: %{Description}";
"%{Organizer} %{SentByText}has invited you to %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}" = "%{Organizer} %{SentByText} pakvietė Jus į %{Summary}.\n\nPradžia: %{StartDate} nuo %{StartTime}\nPabaiga: %{EndDate} nuo %{EndTime}\nAprašymas: %{Description}";
/* Deletion */
"Event Cancelled: \"%{Summary}\"" = "Įvykis vadinasi: \"%{Summary}\"";
"Event Cancelled: \"%{Summary}\"" = "Įvykis atšauktas: \"%{Summary}\"";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate}\nEnd: %{EndDate}\nDescription: %{Description}"
= "%{Organizer} %{SentByText} atšaukė šį įvykį: %{Summary}.\n\nPradžia: %{StartDate}\nPabaiga: %{EndDate}\nAprašymas: %{Description}";
"%{Organizer} %{SentByText}has cancelled this event: %{Summary}.\n\nStart: %{StartDate} at %{StartTime}\nEnd: %{EndDate} at %{EndTime}\nDescription: %{Description}"
@@ -56,27 +56,27 @@ vtodo_class2 = "(Konfidenciali užduotis)";
= "Prašome priimti ar atmesti pakeitimus.";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Priimti pakvietima: \"%{Summary}\"";
"Accepted invitation: \"%{Summary}\"" = "Priimti pakvietimą: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Atmesti pakvietimą: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Deleguoti pakvietimą: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Dar neapsisprendęs dėl pakvietimo: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText} priėmė Kūsų įvykio pakvietimą.";
= "%{Attendee} %{SentByText} priėmė Jūsų įvykio pakvietimą.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText} atmetė Jūsų įvykio pakvietimą.";
= "%{Attendee} %{SentByText} atmetė Jūsų pakvietimą į renginį.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText} delegavo pakvietimą į %{Delegate}.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText} neapsisprendė dėl Jūsų įvykio pakvietimo.";
= "%{Attendee} %{SentByText} neapsisprendė dėl Jūsų kvietimo į renginį.";
/* Reminder */
"Reminder: \"%{Summary}\" - %{StartDate}" = "Reminder: \"%{Summary}\" - %{StartDate}";
"Reminder: \"%{Summary}\" - %{StartDate}" = "Priminimas: \"%{Summary}\" - %{StartDate}";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Negalime prieiti prie išteklių: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "Didžiausias užsakymų vienu metu skaičius (%{NumberOfSimultaneousBookings}) pasiekė ribą \"%{Cn} %{SystemEmail}\". Įvykio konfliktas yra \"%{EventTitle}\", ir prasideda nuo %{StartDate}.";
/* Participation role */
"Your participation is required to this event" = "Your participation is required to this event";
"Your participation is optional to this event" = "Your participation is optional to this event";
"Your participation is not required to this event" = "Your participation is not required to this event";
"Your participation is required to this event" = "Jūsų dalyvavimas šiame renginyje būtinas";
"Your participation is optional to this event" = "Jūsų dalyvavimas šiame renginyje neprivalomas";
"Your participation is not required to this event" = "Jūsų dalyvavimas šiame renginyje nebūtinas";
+167 -81
View File
@@ -664,18 +664,23 @@ static Class iCalEventK = nil;
cycle: (BOOL) _isCycle
{
NSString *format;
unsigned int start, end;
long long start, end;
start = (unsigned int) [_startDate timeIntervalSince1970];
end = (unsigned int) [_endDate timeIntervalSince1970];
start = (long long) [_startDate timeIntervalSince1970];
end = (long long) [_endDate timeIntervalSince1970];
// vTODOs don't necessarily have start/end dates
if (_isCycle)
format = (@"(c_startdate = NULL OR c_startdate <= %u)"
@" AND (c_cycleenddate = NULL OR c_cycleenddate >= %u)");
else
format = (@"(c_startdate = NULL OR c_startdate <= %u)"
@" AND (c_enddate = NULL OR c_enddate >= %u)");
if (start < 0 && end < 0) {
format = (@"(c_startdate = NULL OR c_startdate <= %lld)"
@" AND (c_cycleenddate = NULL OR c_cycleenddate >= %lld)");
} else {
if (_isCycle)
format = (@"(c_startdate = NULL OR c_startdate <= %lld)"
@" AND (c_cycleenddate = NULL OR c_cycleenddate >= %lld)");
else
format = (@"(c_startdate = NULL OR c_startdate <= %lld)"
@" AND (c_enddate = NULL OR c_enddate >= %lld )");
}
return [NSString stringWithFormat: format, end, start];
}
@@ -1277,11 +1282,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
cycleinfo = [content propertyList];
if (!cycleinfo)
{
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@",
theRecord];
return;
}
{
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", theRecord];
return;
}
rules = [cycleinfo objectForKey: @"rules"];
exRules = [cycleinfo objectForKey: @"exRules"];
rDates = [cycleinfo objectForKey: @"rDates"];
@@ -1317,54 +1321,48 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
eventTimeZone = [dtstart timeZone];
if (eventTimeZone)
{
// Adjust the range to check with respect to the event timezone (extracted from the start date)
checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]];
checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]];
recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate
endDate: checkEndDate];
}
{
// Adjust the range to check with respect to the event timezone (extracted from the start date)
checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]];
checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]];
recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate endDate: checkEndDate];
}
else
{
recurrenceRange = theRange;
if ([[theRecord objectForKey: @"c_isallday"] boolValue])
{
recurrenceRange = theRange;
if ([[theRecord objectForKey: @"c_isallday"] boolValue])
{
// The event lasts all-day and has no timezone (floating); we convert the range of the first event
// to the user's timezone
allDayTimeZone = timeZone;
offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]];
firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
[firstStartDate setTimeZone: allDayTimeZone];
[firstEndDate setTimeZone: allDayTimeZone];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate
endDate: firstEndDate];
}
// The event lasts all-day and has no timezone (floating); we convert the range of the first event
// to the user's timezone
allDayTimeZone = timeZone;
offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]];
firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset];
firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset];
[firstStartDate setTimeZone: allDayTimeZone];
[firstEndDate setTimeZone: allDayTimeZone];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate endDate: firstEndDate];
}
}
#warning this code is ugly: we should not mix objects with different types as \
it reduces readability
tz = eventTimeZone ? eventTimeZone : allDayTimeZone;
if (tz)
{
// Adjust the recurrence and exception dates
exDates = [component exceptionDatesWithTimeZone: tz];
rDates = [component recurrenceDatesWithTimeZone: tz];
// Adjust the recurrence rules "until" dates
rules = [component recurrenceRulesWithTimeZone: tz];
exRules = [component exceptionRulesWithTimeZone: tz];
}
{
// Adjust the recurrence and exception dates
exDates = [component exceptionDatesWithTimeZone: tz];
rDates = [component recurrenceDatesWithTimeZone: tz];
// Adjust the recurrence rules "until" dates
rules = [component recurrenceRulesWithTimeZone: tz];
exRules = [component exceptionRulesWithTimeZone: tz];
}
rules = [rules uniqueObjects];
// Calculate the occurrences for the given range
records = [NSMutableArray array];
ranges =
[NSMutableArray arrayWithArray:
ranges = [NSMutableArray arrayWithArray:
[iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange
firstInstanceCalendarDateRange: firstRange
recurrenceRules: rules
@@ -1375,33 +1373,32 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
// Add the master occurrence when dealing with RDATES.
// However, the master event must not be flagged with X-MOZ-FAKED-MASTER.
if ([component hasRecurrenceDates] &&
![[[component uniqueChildWithTag: @"x-moz-faked-master"]
flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[recurrenceRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
![[[component uniqueChildWithTag: @"x-moz-faked-master"]flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[recurrenceRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
max = [ranges count];
for (count = 0; count < max; count++)
{
oneRange = [ranges objectAtIndex: count];
fixedRow = [self fixupCycleRecord: row
cycleRange: oneRange
firstInstanceCalendarDateRange: firstRange
withEventTimeZone: eventTimeZone];
// We now adjust the c_nextalarm based on each occurences. For each of them, we use the master event
// alarm information since exceptions to recurrence rules might have their own, while that is not the
// case for standard occurences.
if ([component hasAlarms])
{
oneRange = [ranges objectAtIndex: count];
fixedRow = [self fixupCycleRecord: row
cycleRange: oneRange
firstInstanceCalendarDateRange: firstRange
withEventTimeZone: eventTimeZone];
// We now adjust the c_nextalarm based on each occurences. For each of them, we use the master event
// alarm information since exceptions to recurrence rules might have their own, while that is not the
// case for standard occurences.
if ([component hasAlarms])
{
[self _computeAlarmForRow: fixedRow
master: component];
}
[records addObject: fixedRow];
[self _computeAlarmForRow: fixedRow
master: component];
}
[records addObject: fixedRow];
}
[self _appendCycleExceptionsFromRow: row
firstInstanceCalendarDateRange: firstRange
@@ -1409,7 +1406,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
withTimeZone: allDayTimeZone
withCalendar: calendar
toArray: records];
[theRecords addObjectsFromArray: records];
} // if ([components count]) ...
}
@@ -1575,7 +1572,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
{
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '*%@*' OR c_category isCaseInsensitiveLike: '*%@*' OR c_location isCaseInsensitiveLike: '*%@*')",
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '*%@*' OR c_category isCaseInsensitiveLike: '*%@*' OR c_location isCaseInsensitiveLike: '*%@*' OR c_content isCaseInsensitiveLike: '*SUMMARY:*%@*')",
[title asSafeSQLLikeString],
[title asSafeSQLLikeString],
[title asSafeSQLLikeString],
[title asSafeSQLLikeString]]];
@@ -1698,6 +1696,23 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
else
ma = [NSMutableArray array];
if (title && [title length] > 0) {
// Case when multiple summary are set in c_content
// Need to filter on title
int i;
NSDictionary *currentItem;
for (i = [ma count] - 1; i >= 0; i--) {
currentItem = [ma objectAtIndex: i];
if (currentItem && [currentItem objectForKey: @"c_title"]
&& [[currentItem objectForKey: @"c_title"] length] > 0) {
if ([[currentItem objectForKey: @"c_title"] rangeOfString: title options: NSCaseInsensitiveSearch].location == NSNotFound) {
[ma removeObjectAtIndex: i];
}
}
}
}
return ma;
}
@@ -2536,7 +2551,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
resourcetype. Anything else will prevent the iPhone from querying the
collection. */
request = [context request];
if (!([request isIPhone] || [request isICal4]))
if (!([request isIPhone] || [request isICal4] || [request isMacOSXCalendar]))
{
gdRT = (NSArray *) [self groupDavResourceType];
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
@@ -2594,13 +2609,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
- (NSException *) setDavDescription: (NSString *) description
{
if (description) {
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
description = [description stringByReplacingOccurrencesOfString:@"/PERSONAL" withString:@"/personal"];
}
[self setFolderPropertyValue: description
inCategory: @"DavDescription"];
}
@@ -3832,4 +3840,82 @@ static NSComparisonResult _comparePermissions (id perm1, id perm2, void *context
} // if ([event hasRecurrenceRules]) ...
}
- (NSArray *) _calendarProxiedUsersWithWriteAccess: (BOOL) write
{
NSMutableDictionary *proxiedUsers;
NSArray *references, *elements;
NSString *currentLogin;
NSNumber *yesNumber;
SOGoAppointmentFolders *parentFolder;
SOGoUserSettings *us;
int count, max;
yesNumber = [NSNumber numberWithBool: YES];
us = [[SOGoUser userWithLogin: owner] userSettings];
references = [us subscribedCalendars];
max = [references count];
proxiedUsers = [NSMutableDictionary dictionaryWithCapacity: max];
for (count = 0; count < max; count++)
{
elements = [[references objectAtIndex: count]
componentsSeparatedByString: @":"];
if ([elements count])
{
currentLogin = [elements objectAtIndex: 0];
if (![proxiedUsers objectForKey: currentLogin])
{
parentFolder = [[container lookupName: currentLogin
inContext: context
acquire: NO]
lookupName: @"Calendar"
inContext: context acquire: NO];
if ([parentFolder hasProxyCalendarsWithWriteAccess: write
forUserWithLogin: owner])
[proxiedUsers setObject: yesNumber forKey: currentLogin];
}
}
}
return [proxiedUsers allKeys];
}
- (NSMutableArray *) _davCalendarProxyForWrite: (BOOL) write
{
NSMutableArray *proxyFor;
NSArray *proxiedUsers, *tag;
NSString *appName, *proxiedUser;
int count, max;
appName = [[context request] applicationName];
proxiedUsers = [self _calendarProxiedUsersWithWriteAccess: write];
max = [proxiedUsers count];
proxyFor = [NSMutableArray arrayWithCapacity: max];
if (max)
{
for (count = 0; count < max; count++)
{
proxiedUser = [proxiedUsers objectAtIndex: count];
tag = [NSArray arrayWithObjects: @"href", XMLNS_WEBDAV, @"D",
[NSString stringWithFormat: @"/%@/dav/%@/",
appName, proxiedUser],
nil];
[proxyFor addObject: tag];
}
}
return proxyFor;
}
- (NSArray *) davCalendarProxyWriteFor
{
return [self _davCalendarProxyForWrite: YES];
}
- (NSArray *) davCalendarProxyReadFor
{
return [self _davCalendarProxyForWrite: NO];
}
@end /* SOGoAppointmentFolder */
+68 -56
View File
@@ -105,21 +105,21 @@
interval = [[master endDate]
timeIntervalSinceDate: firstDate];
if ([newOccurence isAllDay])
{
nbrDays = ((float) abs (interval) / 86400);
[newOccurence setAllDayWithStartDate: date
{
nbrDays = ((float) abs (interval) / 86400);
[newOccurence setAllDayWithStartDate: date
duration: nbrDays];
}
}
else
{
[newOccurence setStartDate: date];
[newOccurence setEndDate: [date addYear: 0
month: 0
day: 0
hour: 0
minute: 0
second: interval]];
}
{
[newOccurence setStartDate: date];
[newOccurence setEndDate: [date addYear: 0
month: 0
day: 0
hour: 0
minute: 0
second: interval]];
}
return newOccurence;
}
@@ -191,11 +191,13 @@
iCalCalendar *iCalendarToSave;
iCalPerson *attendee;
SOGoUser *user;
WORequest *rq;
iCalendarToSave = nil;
user = [SOGoUser userWithLogin: theUID];
attendeeObject = [self _lookupEvent: [newEvent uid] forUID: theUID];
attendee = [newEvent userAsAttendee: user];
rq = [context request];
// If the atttende's role is NON-PARTICIPANT, we write nothing to its calendar
if ([[attendee role] caseInsensitiveCompare: @"NON-PARTICIPANT"] == NSOrderedSame)
@@ -217,8 +219,9 @@
return;
}
if ([newEvent recurrenceId])
if ([newEvent recurrenceId] && ![rq isICal4] && ![rq isICal])
{
// We must add an occurence to a non-existing event.
if ([attendeeObject isNew])
@@ -960,13 +963,15 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
NSArray *addedAttendees, *deletedAttendees, *updatedAttendees;
iCalEventChanges *changes;
NSException *ex;
WORequest *rq;
addedAttendees = nil;
deletedAttendees = nil;
updatedAttendees = nil;
rq = [context request];
changes = [newEvent getChangesRelativeToEvent: oldEvent];
if ([changes sequenceShouldBeIncreased])
if ([changes sequenceShouldBeIncreased] || [rq isICal4] || [rq isICal])
{
// Set new attendees status to "needs action" and recompute changes when
// the list of attendees has changed. The list might have changed since
@@ -2211,8 +2216,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// we receive an external invitation (IMIP/ITIP) and we accept it
// from a CUA - it gets added to a specific CalDAV calendar using a PUT
//
else if ([event userIsAttendee: ownerUser] && [self _shouldScheduleEvent: [event userAsAttendee: ownerUser]])
{
else if ([event userIsAttendee: ownerUser]
&& [self _shouldScheduleEvent: [event userAsAttendee: ownerUser]]
&& iCalPersonPartStatNeedsAction != [[event userAsAttendee: ownerUser] participationStatus])
{
[self sendResponseToOrganizer: event
from: ownerUser];
}
@@ -2304,7 +2311,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
recurrenceId = [oldEvent recurrenceId];
}
}
// We check if the PUT call is actually an PART-STATE change
// from one of the attendees - here's the logic :
//
@@ -2334,24 +2341,24 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
userIsOrganizer = [oldEvent userIsOrganizer: [context activeUser]];
}
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
//
// We also handle the case where Bob invites Alice and Bob has full access to Alice's calendar
// After inviting ALice, Bob opens the event in Alice's calendar and accept/declines the event.
//
if (!userIsOrganizer &&
if (!userIsOrganizer &&
!ownerIsOrganizer &&
[[context activeUser] hasEmail: [[newEvent organizer] rfc822Email]])
{
[[newEvent organizer] setCn: [ownerUser cn]];
[[newEvent organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[newEvent organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
ownerIsOrganizer = YES;
}
[[context activeUser] hasEmail: [[newEvent organizer] rfc822Email]])
{
[[newEvent organizer] setCn: [ownerUser cn]];
[[newEvent organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[newEvent organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
ownerIsOrganizer = YES;
}
// With Thunderbird 10, if you create a recurring event with an exception
// occurence, and invite someone, the PUT will have the organizer in the
@@ -2362,25 +2369,25 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if (ownerIsOrganizer)
{
// We check ACLs of the 'organizer' - in case someone forges the SENT-BY
NSString *uid;
// We check ACLs of the 'organizer' - in case someone forges the SENT-BY
NSString *uid;
uid = [[oldEvent organizer] uidInContext: context];
uid = [[oldEvent organizer] uidInContext: context];
if (uid && [[[context activeUser] login] caseInsensitiveCompare: uid] != NSOrderedSame)
{
SOGoAppointmentObject *organizerObject;
if (uid && [[[context activeUser] login] caseInsensitiveCompare: uid] != NSOrderedSame)
{
SOGoAppointmentObject *organizerObject;
organizerObject = [self _lookupEvent: [oldEvent uid] forUID: uid];
roles = [[context activeUser] rolesForObject: organizerObject
inContext: context];
organizerObject = [self _lookupEvent: [oldEvent uid] forUID: uid];
roles = [[context activeUser] rolesForObject: organizerObject
inContext: context];
if (![roles containsObject: @"ComponentModifier"] && ![[context activeUser] isSuperUser])
{
return [self exceptionWithHTTPStatus: 409
if (![roles containsObject: @"ComponentModifier"] && ![[context activeUser] isSuperUser])
{
return [self exceptionWithHTTPStatus: 409
reason: @"Not allowed to perform this action. Wrong SENT-BY being used regarding access rights on organizer's calendar."];
}
}
}
}
// A RECCURENCE-ID was removed
if (!newEvent && oldEvent)
@@ -2431,9 +2438,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// We first check of the sequences are alright. We don't accept attendees
// accepting "old" invitations. If that's the case, we return a 409
if ([[newEvent sequence] intValue] < [[oldEvent sequence] intValue])
return [self exceptionWithHTTPStatus: 409
if ([[newEvent sequence] intValue] < [[oldEvent sequence] intValue]) {
return [self exceptionWithHTTPStatus: 403
reason: @"sequences don't match"];
}
// Remove the RSVP attribute, as an action from the attendee
// was actually performed, and this confuses iCal (bug #1850)
@@ -2457,14 +2465,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([[changes updatedProperties] containsObject: @"exdate"])
{
[self changeParticipationStatus: @"DECLINED"
withDelegate: nil // FIXME (specify delegate?)
withDelegate: nil // FIXME (specify delegate?)
alarm: nil
forRecurrenceId: [self _addedExDate: oldEvent newEvent: newEvent]];
}
else if (attendee)
{
[self changeParticipationStatus: [attendee partStat]
withDelegate: delegate
withDelegate: delegate
alarm: nil
forRecurrenceId: recurrenceId];
}
@@ -2476,7 +2484,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
{
[self _handleRemovedUsers: [changes deletedAttendees]
withRecurrenceId: recurrenceId];
withRecurrenceId: recurrenceId];
}
}
} // if ([[newEvent attendees] count] || [[oldEvent attendees] count])
@@ -2485,9 +2493,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
changes = [iCalEventChanges changesFromEvent: oldEvent toEvent: newEvent];
if ([changes hasMajorChanges])
[self sendReceiptEmailForObject: newEvent
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
operation: EventUpdated];
}
} // else of if (isNew) ...
@@ -2496,9 +2504,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// We must NOT invoke [super PUTAction:] here as it'll resave
// the content string and we could have etag mismatches.
baseVersion = (isNew ? 0 : version);
ex = [self saveComponent: calendar
baseVersion: baseVersion];
baseVersion: baseVersion];
return ex;
}
@@ -2604,6 +2614,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
ex = [self matchesRequestConditionInContext: context];
if (ex)
return ex;
}
if (mustUpdate)
+39 -42
View File
@@ -391,28 +391,28 @@
inContext: localContext
acquire: acquire];
if (!obj)
{
if ([lookupName isEqualToString: @"master"])
obj = [self occurence: [self component: NO secure: NO]];
else if ([lookupName hasPrefix: @"occurence"])
{
recID = [lookupName substringFromIndex: 9];
occurence = [self lookupOccurrence: recID];
if (occurence)
isNewOccurence = NO;
else
{
if ([lookupName isEqualToString: @"master"])
obj = [self occurence: [self component: NO secure: NO]];
else if ([lookupName hasPrefix: @"occurence"])
{
recID = [lookupName substringFromIndex: 9];
occurence = [self lookupOccurrence: recID];
if (occurence)
isNewOccurence = NO;
else
{
occurence = [self newOccurenceWithID: recID];
isNewOccurence = YES;
}
if (occurence)
if (occurence)
{
obj = [self occurence: occurence];
if (isNewOccurence)
[obj setIsNew: isNewOccurence];
[obj setIsNew: isNewOccurence];
}
}
}
}
}
return obj;
}
@@ -470,36 +470,33 @@
calendar = &fullCalendar;
if (!*calendar)
{
if (secure)
iCalString = [self secureContentAsString];
else
iCalString = content;
{
if (secure)
iCalString = [self secureContentAsString];
else
iCalString = content;
if ([iCalString length] > 0)
{
ASSIGN (*calendar, [iCalCalendar parseSingleFromSource: iCalString]);
if (!secure)
originalCalendar = [*calendar copy];
}
else
{
if (create)
{
ASSIGN (*calendar, [iCalCalendar groupWithTag: @"vcalendar"]);
[*calendar setVersion: @"2.0"];
prodID = [NSString stringWithFormat:
@"-//Inverse inc./SOGo %@//EN",
SOGoVersion];
[*calendar setProdID: prodID];
tag = [[self componentTag] uppercaseString];
newComponent = [[*calendar classForTag: tag]
groupWithTag: tag];
[newComponent setUid: [self globallyUniqueObjectId]];
[*calendar addChild: newComponent];
}
}
if ([iCalString length] > 0)
{
ASSIGN (*calendar, [iCalCalendar parseSingleFromSource: iCalString]);
if (!secure)
originalCalendar = [*calendar copy];
}
else
{
if (create)
{
ASSIGN (*calendar, [iCalCalendar groupWithTag: @"vcalendar"]);
[*calendar setVersion: @"2.0"];
prodID = [NSString stringWithFormat:@"-//Inverse inc./SOGo %@//EN", SOGoVersion];
[*calendar setProdID: prodID];
tag = [[self componentTag] uppercaseString];
newComponent = [[*calendar classForTag: tag] groupWithTag: tag];
[newComponent setUid: [self globallyUniqueObjectId]];
[*calendar addChild: newComponent];
}
}
}
returnedCopy = [*calendar mutableCopy];
[returnedCopy autorelease];
@@ -88,7 +88,7 @@
}
tag = [NSArray arrayWithObjects: @"href", XMLNS_WEBDAV, @"D",
[NSString stringWithFormat: @"/SOGo/dav/%@/", nameInContainer],
[NSString stringWithFormat: @"/SOGo/dav/%@/", [self nameInContainer]],
nil];
[addresses addObjectUniquely: tag];
@@ -128,7 +128,7 @@
}
tag = [NSArray arrayWithObjects: @"href", XMLNS_WEBDAV, @"D",
[NSString stringWithFormat: @"/SOGo/dav/%@/", nameInContainer],
[NSString stringWithFormat: @"/SOGo/dav/%@/", [self nameInContainer]],
nil];
[addresses addObjectUniquely: tag];
@@ -190,7 +190,7 @@
- (NSArray *) davCalendarScheduleOutboxURL
{
return [[context request] isMacOSXVenturaCalendarApp] ? [self _davSpecialCalendarURLWithName: @"PERSONAL"] : [self _davSpecialCalendarURLWithName: @"personal"];
return [self _davSpecialCalendarURLWithName: @"personal"];
}
- (NSArray *) _calendarProxiedUsersWithWriteAccess: (BOOL) write
@@ -131,7 +131,7 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
curl_easy_setopt(curl, CURLOPT_URL, [location UTF8String]);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
authInfos = [self _loadAuthData];
+20 -19
View File
@@ -39,6 +39,7 @@
id occurrence;
iCalRepeatableEntityObject *component;
NSUInteger count, max, seconds, recSeconds;
NSCalendarDate* recId;
occurrence = nil;
@@ -50,26 +51,26 @@
component = [components objectAtIndex: 0];
if ([component isRecurrent] || [component recurrenceId])
{
// Skip the master event if required
count = ([component recurrenceId] ? 0 : 1);
while (!occurrence && count < max)
{
component = [components objectAtIndex: count];
recSeconds = [[component recurrenceId] timeIntervalSince1970];
if (recSeconds == seconds)
occurrence = component;
else
count++;
}
}
else
{
/* The "master" event could be that occurrence. */
{
// Skip the master event if required
count = ([component recurrenceId] ? 0 : 1);
while (!occurrence && count < max)
{
component = [components objectAtIndex: count];
recSeconds = [[component recurrenceId] timeIntervalSince1970];
if (recSeconds == seconds)
occurrence = component;
}
if (recSeconds == seconds)
occurrence = component;
else
count++;
}
}
else
{
/* The "master" event could be that occurrence. */
recSeconds = [[component recurrenceId] timeIntervalSince1970];
if (recSeconds == seconds)
occurrence = component;
}
return occurrence;
}
@@ -30,7 +30,7 @@
static NSString *properties[] = {@"organizer", @"startDate", @"endDate",
@"due", @"duration", @"summary",
@"rdate", @"rrule", @"exdate", @"exrule",
@"status", @"location",
@"status", @"location", @"rid",
nil};
NSString **currentProperty;
BOOL updateRequired;
@@ -408,24 +408,23 @@
doesOccur = [self isRecurrent];
if (doesOccur)
{
// Retrieve the range of the first event
firstRange = [self firstOccurenceRange]; // returns GMT dates
{
// Retrieve the range of the first event
firstRange = [self firstOccurenceRange]; // returns GMT dates
// Set the range to check with respect to the event timezone (extracted from the start date)
firstStartDate = (iCalDateTime *)[self uniqueChildWithTag: @"dtstart"];
timeZone = [(iCalDateTime *)firstStartDate timeZone];
if (timeZone)
startDate = [(iCalTimeZone *)timeZone computedDateForDate: theOccurenceDate];
else
startDate = theOccurenceDate;
endDate = [startDate addTimeInterval: [self occurenceInterval]];
checkRange = [NGCalendarDateRange calendarDateRangeWithStartDate: startDate
// Set the range to check with respect to the event timezone (extracted from the start date)
firstStartDate = (iCalDateTime *)[self uniqueChildWithTag: @"dtstart"];
timeZone = [(iCalDateTime *)firstStartDate timeZone];
if (timeZone)
startDate = [(iCalTimeZone *)timeZone computedDateForDate: theOccurenceDate];
else
startDate = theOccurenceDate;
endDate = [startDate addTimeInterval: [self occurenceInterval]];
checkRange = [NGCalendarDateRange calendarDateRangeWithStartDate: startDate
endDate: endDate];
// Calculate the occurrences for the given date
ranges =
[NSMutableArray arrayWithArray:
// Calculate the occurrences for the given date
ranges = [NSMutableArray arrayWithArray:
[iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: checkRange
firstInstanceCalendarDateRange: firstRange
recurrenceRules: [self recurrenceRulesWithTimeZone: timeZone]
@@ -433,19 +432,18 @@
recurrenceDates: [self recurrenceDatesWithTimeZone: timeZone]
exceptionDates: [self exceptionDatesWithTimeZone: timeZone]]];
// Add the master occurrence when dealing with RDATES.
// However, the master event must not be flagged with X-MOZ-FAKED-MASTER.
if ([self hasRecurrenceDates] &&
![[[self uniqueChildWithTag: @"x-moz-faked-master"]
flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[checkRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
doesOccur = [ranges dateRangeArrayContainsDate: startDate];
// Add the master occurrence when dealing with RDATES.
// However, the master event must not be flagged with X-MOZ-FAKED-MASTER.
if ([self hasRecurrenceDates] &&
![[[self uniqueChildWithTag: @"x-moz-faked-master"] flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[checkRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
doesOccur = [ranges dateRangeArrayContainsDate: startDate];
}
return doesOccur;
}
@@ -0,0 +1,2 @@
"Personal Address Book" = "Caderno persoal de direccións";
"Collected Address Book" = "Caderno de direccións recompilada";
@@ -1,2 +1,2 @@
"Personal Address Book" = "Asmeninė adresų knyga";
"Collected Address Book" = "Rinktinė adresų knyga";
"Collected Address Book" = "Rinktinė adresų knygą";
+1 -1
View File
@@ -418,7 +418,7 @@ convention:
[self setCategories: [o componentsSeparatedByString: @","]];
// Photo
if ([ldifRecord objectForKey: @"photo"])
if ([ldifRecord objectForKey: @"photo"] && [ldifRecord objectForKey: @"photo"] != [NSNull null])
[self setPhoto: [[ldifRecord objectForKey: @"photo"] stringByEncodingBase64]];
[self cleanupEmptyChildren];
@@ -271,6 +271,9 @@ static NSArray *folderListingFields = nil;
if ([data length])
[contactRecord setObject: data forKey: @"id"];
// Container name
[contactRecord setObject: [self displayName] forKey: @"containerName"];
// c_cn
data = [contactRecord objectForKey: @"c_cn"];
if (![data length])
@@ -27,6 +27,7 @@
@class NSDictionary;
@class NSString;
@class NGVList;
@class NSNull;
@interface SOGoContactLDIFEntry : SOGoObject <SOGoContactObject>
{
+1 -1
View File
@@ -162,7 +162,7 @@
- (BOOL) hasPhoto
{
return ([ldifEntry objectForKey: @"photo"] != nil);
return ([ldifEntry objectForKey: @"photo"] != nil && [ldifEntry objectForKey: @"photo"] != [NSNull null]);
}
- (NSString *) davEntityTag
+38 -14
View File
@@ -239,6 +239,13 @@
[newRecord setObject: [oldRecord objectForKey: @"c_uid"]
forKey: @"c_uid"];
//source id
[newRecord setObject: [source sourceID] forKey: @"sourceID"];
//source display name
[newRecord setObject: [self displayName]
forKey: @"containerName"];
// c_name => id
[newRecord setObject: [oldRecord objectForKey: @"c_name"]
forKey: @"c_name"];
@@ -427,26 +434,43 @@
{
NSArray *records, *result;
EOSortOrdering *ordering;
BOOL processed;
result = nil;
processed = NO;
if ([filter length] > 0 || ![source listRequiresDot])
[source setListRequiresDot: NO];
if ( ![source listRequiresDot])
{
records = [source fetchContactsMatching: filter
if ([filter length] > 0) {
records = [source fetchContactsMatching: filter
withCriteria: criteria
inDomain: domain];
[childRecords setObjects: records
forKeys: [records objectsForKey: @"c_name"
notFoundMarker: nil]];
records = [self _flattenedRecords: records];
ordering
= [EOSortOrdering sortOrderingWithKey: sortKey
selector: ((sortOrdering == NSOrderedDescending)
? EOCompareCaseInsensitiveDescending
: EOCompareCaseInsensitiveAscending)];
result
= [records sortedArrayUsingKeyOrderArray:
[NSArray arrayWithObject: ordering]];
processed = YES;
} else if ([[SOGoSystemDefaults sharedSystemDefaults] globalAddressBookFirstEntriesEnabled]) {
records = [source fetchContactsMatching: filter
withCriteria: criteria
inDomain: domain
limit: [[SOGoSystemDefaults sharedSystemDefaults] globalAddressBookFirstEntriesCount]];
processed = YES;
}
if (processed) {
[childRecords setObjects: records
forKeys: [records objectsForKey: @"c_name"
notFoundMarker: nil]];
records = [self _flattenedRecords: records];
ordering
= [EOSortOrdering sortOrderingWithKey: sortKey
selector: ((sortOrdering == NSOrderedDescending)
? EOCompareCaseInsensitiveDescending
: EOCompareCaseInsensitiveAscending)];
result
= [records sortedArrayUsingKeyOrderArray:
[NSArray arrayWithObject: ordering]];
}
}
return result;
+2
View File
@@ -74,6 +74,8 @@ Mailer_RESOURCE_FILES += \
SOGoMailFinnishReply.wo \
SOGoMailFrenchForward.wo \
SOGoMailFrenchReply.wo \
SOGoMailGalicianForward.wo \
SOGoMailGalicianReply.wo \
SOGoMailCroatianForward.wo \
SOGoMailCroatianReply.wo \
SOGoMailGermanForward.wo \
@@ -0,0 +1,5 @@
"OtherUsersFolderName" = "Outros usuarios";
"SharedFoldersName" = "Cartafoles compartidos";
"Report: Marked messages as junk" = "Informe: mensaxes marcadas como correo non desexado";
"Report: Marked messages as not junk" = "Informe: mensaxes marcadas como non desexados";
+6 -1
View File
@@ -56,6 +56,7 @@
int sourceIMAP4ID;
NSMutableDictionary *headers;
NSString *inReplyTo;
NSString *references;
NSString *text;
NSString *sourceURL;
NSString *sourceFlag;
@@ -63,6 +64,7 @@
BOOL isHTML;
BOOL sign;
BOOL encrypt;
NSMutableArray *tmpFiles;
// Used during S/MIME encryption
NSMutableDictionary *certificates;
@@ -95,6 +97,9 @@
- (NSString *) inReplyTo;
- (void) setInReplyTo: (NSString *) newInReplyTo;
- (NSString *) references;
- (void) setReferences: (NSString *) newReferences;
- (void) setSourceURL: (NSString *) newSurceURL;
- (void) setSourceFlag: (NSString *) newSourceFlag;
- (void) setSourceFolder: (NSString *) newSourceFolder;
@@ -117,7 +122,7 @@
/* NGMime representations */
- (NSData *) mimeMessageForRecipient: (NSString *) theRecipient;
- (NSData *)mimeMessageForRecipient:(NSString *)theRecipient extractingImages:(BOOL)extractImage;
/* operations */
- (NSArray *) allRecipients;
+102 -29
View File
@@ -67,6 +67,7 @@
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/NGMimeFileData+SOGo.h>
#import <NGCards/NGVCard.h>
@@ -138,9 +139,11 @@ static NSString *userAgent = nil;
sourceURL = nil;
sourceFlag = nil;
inReplyTo = nil;
references = nil;
isHTML = NO;
sign = NO;
encrypt = NO;
tmpFiles = [[NSMutableArray alloc] init];
}
return self;
@@ -155,6 +158,8 @@ static NSString *userAgent = nil;
[sourceURL release];
[sourceFlag release];
[inReplyTo release];
[references release];
[tmpFiles release];
[super dealloc];
}
@@ -361,6 +366,16 @@ static NSString *userAgent = nil;
ASSIGN (inReplyTo, newInReplyTo);
}
- (NSString *) references
{
return references;
}
- (void) setReferences: (NSString *) newReferences
{
ASSIGN (references, newReferences);
}
- (void) setSourceURL: (NSString *) newSourceURL
{
ASSIGN (sourceURL, newSourceURL);
@@ -421,6 +436,8 @@ static NSString *userAgent = nil;
forKey: @"isHTML"];
if (inReplyTo)
[infos setObject: inReplyTo forKey: @"inReplyTo"];
if (references)
[infos setObject: references forKey: @"references"];
if (sourceIMAP4ID > -1)
[infos setObject: [NSString stringWithFormat: @"%i", sourceIMAP4ID]
forKey: @"sourceIMAP4ID"];
@@ -492,6 +509,10 @@ static NSString *userAgent = nil;
value = [infoDict objectForKey: @"inReplyTo"];
if (value)
[self setInReplyTo: value];
value = [infoDict objectForKey: @"references"];
if (value)
[self setReferences: value];
}
//
@@ -570,7 +591,7 @@ static NSString *userAgent = nil;
id result;
error = nil;
message = [self mimeMessageForRecipient: nil];
message = [self mimeMessageForRecipient: nil extractingImages: NO];
if (!message)
{
@@ -914,11 +935,10 @@ static NSString *userAgent = nil;
if (filename)
{
NSMutableDictionary *currentInfo;
currentInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
filename, @"filename",
mimeType, @"mimetype",
bodyId, @"bodyid",
bodyId, @"bodyId",
nil];
[self saveAttachment: body
withMetadata: currentInfo];
@@ -1041,6 +1061,7 @@ static NSString *userAgent = nil;
{
BOOL fromSentMailbox;
NSString *msgID;
NSString *oldReferences;
NSMutableDictionary *info;
NGImap4Envelope *sourceEnvelope;
SOGoUserDefaults *ud;
@@ -1059,6 +1080,13 @@ static NSString *userAgent = nil;
msgID = [sourceEnvelope messageID];
if ([msgID length] > 0)
[self setInReplyTo: msgID];
oldReferences = [sourceMail references];
if ([oldReferences length] > 0)
[self setReferences: [oldReferences stringByAppendingFormat: @" %@", msgID]];
else
[self setReferences: msgID];
ud = [[context activeUser] userDefaults];
@@ -1072,19 +1100,16 @@ static NSString *userAgent = nil;
ud = [[context activeUser] userDefaults];
// TODO: Change mailMessageForwarding for reply
if ([[ud mailMessageForwarding] isEqualToString: @"inline"])
{
[self setText: [sourceMail contentForReply]];
if ([sourceMail isEncrypted])
[self _fetchAttachmentsFromEncryptedMail: sourceMail onlyImages: YES];
else if ([sourceMail isOpaqueSigned])
[self _fetchAttachmentsFromOpaqueSignedMail: sourceMail onlyImages: YES];
else
[self _fetchAttachmentsFromMail: sourceMail onlyImages: YES];
}
[self save];
[self setText: [sourceMail contentForReply]];
if ([sourceMail isEncrypted])
[self _fetchAttachmentsFromEncryptedMail: sourceMail onlyImages: YES];
else if ([sourceMail isOpaqueSigned])
[self _fetchAttachmentsFromOpaqueSignedMail: sourceMail onlyImages: YES];
else
[self _fetchAttachmentsFromMail: sourceMail onlyImages: YES];
[self save];
[self storeInfo];
}
@@ -1135,7 +1160,7 @@ static NSString *userAgent = nil;
// TODO: use subject for filename?
// error = [newDraft saveAttachment:content withName:@"forward.eml"];
signature = [[self mailAccountFolder] signature];
if ([signature length])
if ([signature length] && [ud mailUseSignatureOnForward])
{
nl = (isHTML ? @"<br />" : @"\n");
space = (isHTML ? @"&nbsp;" : @" ");
@@ -1835,6 +1860,9 @@ static NSString *userAgent = nil;
if (inReplyTo)
[map setObject: inReplyTo forKey: @"in-reply-to"];
if (references)
[map setObject: references forKey: @"references"];
/* add subject */
if ([(s = [headers objectForKey: @"subject"]) length] > 0)
[map setObject: [s asQPSubjectString: @"utf-8"]
@@ -1896,8 +1924,11 @@ static NSString *userAgent = nil;
if (_extractImages)
{
newText = [text htmlByExtractingImages: extractedBodyParts];
if ([extractedBodyParts count])
if ([extractedBodyParts count]) {
[self setText: newText];
[self addTmpFiles: extractedBodyParts];
}
}
map = [self mimeHeaderMapWithHeaders: _headers
@@ -1908,8 +1939,11 @@ static NSString *userAgent = nil;
//[self debugWithFormat: @"MIME Envelope: %@", map];
allBodyParts = [self bodyPartsForAllAttachments];
if (!allBodyParts)
return nil;
if (!allBodyParts) {
return nil;
} else {
[self addTmpFiles: allBodyParts];
}
//[self debugWithFormat: @"attachments: %@", bodyParts];
@@ -1929,12 +1963,12 @@ static NSString *userAgent = nil;
}
return message;
}
}
//
// Return a NGMimeMessage object with inline HTML images (<img src=data>) extracted as attachments (<img src=cid>).
//
- (NSData *) mimeMessageForRecipient: (NSString *) theRecipient
- (NSData *) mimeMessageForRecipient: (NSString *) theRecipient extractingImages: (BOOL)extractImage
{
NGMimeMessageGenerator *generator, *partGenerator;
NGMimeMessage *mimeMessage;
@@ -1947,7 +1981,7 @@ static NSString *userAgent = nil;
// Nothing to sign or encrypt, let's generate the message and return immediately
if (![self sign] && ![self encrypt])
{
mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: YES bodyOnly: NO];
mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: extractImage bodyOnly: NO];
if (mimeMessage)
{
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
@@ -1961,7 +1995,7 @@ static NSString *userAgent = nil;
partGenerator = [[[NGMimePartGenerator alloc] init] autorelease];
content = [partGenerator generateMimeFromPart: [self mimeMessageWithHeaders: nil
excluding: nil
extractingImages: YES
extractingImages: extractImage
bodyOnly: YES]];
if ([self sign])
@@ -2199,6 +2233,41 @@ static NSString *userAgent = nil;
return [self sendMailAndCopyToSent: YES];
}
// Extract tmp files in NGMimeBuildMimeTempDirectory from NSArray and store locally
// The tmp files will be deleted once the message sent
- (void) addTmpFiles:(NSArray *) parts {
NGMimeBodyPart *part;
for (part in parts) {
NGMimeFileData *body;
NSString *path;
body = [part body];
if (body && [body isKindOfClass: [NGMimeFileData class]]) {
path = [body path];
if (path) {
[tmpFiles addObject: path];
}
}
}
}
// Clean temporary files
- (void) cleanTmpFiles {
NSString *path;
NSFileManager *fm;
fm = [NSFileManager defaultManager];
for (path in tmpFiles) {
if ([fm fileExistsAtPath: path])
[fm removeFileAtPath: path handler: nil];
}
// Clean tmp files
[tmpFiles removeAllObjects];
}
//
//
//
@@ -2228,9 +2297,9 @@ static NSString *userAgent = nil;
recipient = [recipients objectAtIndex: i];
if ([[context activeUser] hasEmail: recipient])
message = messageForSent = [self mimeMessageForRecipient: nil];
message = messageForSent = [self mimeMessageForRecipient: nil extractingImages: YES];
else
message = [self mimeMessageForRecipient: recipient];
message = [self mimeMessageForRecipient: recipient extractingImages: YES];
if (!message)
return [NSException exceptionWithHTTPStatus: 500
@@ -2244,19 +2313,21 @@ static NSString *userAgent = nil;
inContext: context
systemMessage: NO];
if (error)
if (error) {
[self cleanTmpFiles];
return error;
}
}
// If the current user isn't part of the recipient list for encrypted emails
// let's generate a crypted email for its sent folder.
if (!messageForSent)
messageForSent = [self mimeMessageForRecipient: nil];
messageForSent = [self mimeMessageForRecipient: nil extractingImages: YES];
}
else
{
// Encryption is done or not, if we didn't have to.
message = messageForSent = [self mimeMessageForRecipient: nil];
message = messageForSent = [self mimeMessageForRecipient: nil extractingImages: YES];
if (!message)
return [NSException exceptionWithHTTPStatus: 500
@@ -2304,6 +2375,8 @@ static NSString *userAgent = nil;
[imap4 doesMailboxExistAtURL: [container imap4URL]])
[(SOGoDraftsFolder *) container expunge];
[self cleanTmpFiles];
return error;
}
@@ -2329,7 +2402,7 @@ static NSString *userAgent = nil;
NSString *str;
NSData *message;
message = [self mimeMessageForRecipient: nil];
message = [self mimeMessageForRecipient: nil extractingImages: NO];
if (message)
{
str = [[NSString alloc] initWithData: message
+22 -35
View File
@@ -136,12 +136,6 @@ static NSString *inboxFolderName = @"INBOX";
namespaceDict = [client namespace];
namespace = [namespaceDict objectForKey: @"personal"];
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
namespace = [namespaceDict objectForKey: @"PERSONAL"];
}
if (namespace)
[self _appendNamespace: namespace toFolders: folders];
@@ -1011,33 +1005,27 @@ static NSString *inboxFolderName = @"INBOX";
[[[self imap4Connection] client] namespace];
if ([_key hasPrefix: @"folder"])
{
folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];
{
folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];
namespaces = [NSMutableArray array];
[self _appendNamespaces: namespaces];
if ([namespaces containsObject: folderName])
klazz = [SOGoMailNamespace class];
else if ([folderName
isEqualToString: [self draftsFolderNameInContext: _ctx]])
klazz = [SOGoDraftsFolder class];
else if ([folderName
isEqualToString: [self sentFolderNameInContext: _ctx]])
klazz = [SOGoSentFolder class];
else if ([folderName
isEqualToString: [self trashFolderNameInContext: _ctx]])
klazz = [SOGoTrashFolder class];
else if ([folderName
isEqualToString: [self junkFolderNameInContext: _ctx]])
klazz = [SOGoJunkFolder class];
else if ([folderName
isEqualToString: [self templatesFolderNameInContext: _ctx]])
klazz = [SOGoTemplatesFolder class];
else
klazz = [SOGoMailFolder class];
obj = [klazz objectWithName: _key inContainer: self];
}
namespaces = [NSMutableArray array];
[self _appendNamespaces: namespaces];
if ([namespaces containsObject: folderName])
klazz = [SOGoMailNamespace class];
else if ([folderName isEqualToString: [self draftsFolderNameInContext: _ctx]])
klazz = [SOGoDraftsFolder class];
else if ([folderName isEqualToString: [self sentFolderNameInContext: _ctx]])
klazz = [SOGoSentFolder class];
else if ([folderName isEqualToString: [self trashFolderNameInContext: _ctx]])
klazz = [SOGoTrashFolder class];
else if ([folderName isEqualToString: [self junkFolderNameInContext: _ctx]])
klazz = [SOGoJunkFolder class];
else if ([folderName isEqualToString: [self templatesFolderNameInContext: _ctx]])
klazz = [SOGoTemplatesFolder class];
else
klazz = [SOGoMailFolder class];
obj = [klazz objectWithName: _key inContainer: self];
}
else
obj = [super lookupName: _key inContext: _ctx acquire: NO];
@@ -1222,9 +1210,8 @@ static NSString *inboxFolderName = @"INBOX";
{
if (!templatesFolder)
{
templatesFolder
= [self folderWithTraversal: [self templatesFolderNameInContext: _ctx]
andClassName: @"SOGoTemplatesFolder"];
templatesFolder = [self folderWithTraversal: [self templatesFolderNameInContext: _ctx]
andClassName: @"SOGoTemplatesFolder"];
[templatesFolder retain];
}
+31 -42
View File
@@ -1149,51 +1149,40 @@ static NSInteger _compareFetchResultsByUID (id entry1, id entry2, NSDictionary *
obj = [super lookupName: _key inContext: _ctx acquire: NO];
if (!obj)
{
if ([_key hasPrefix: @"folder"])
{
if ([_key hasPrefix: @"folder"])
{
mailAccount = [self mailAccountFolder];
folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];
fullFolderName = [NSString stringWithFormat: @"%@/%@",
[self traversalFromMailAccount], folderName];
if ([fullFolderName
isEqualToString:
[mailAccount draftsFolderNameInContext: _ctx]])
className = @"SOGoDraftsFolder";
else if ([fullFolderName
isEqualToString:
[mailAccount sentFolderNameInContext: _ctx]])
className = @"SOGoSentFolder";
else if ([fullFolderName
isEqualToString:
[mailAccount trashFolderNameInContext: _ctx]])
className = @"SOGoTrashFolder";
else if ([fullFolderName
isEqualToString:
[mailAccount junkFolderNameInContext: _ctx]])
className = @"SOGoJunkFolder";
else if ([fullFolderName
isEqualToString:
[mailAccount templatesFolderNameInContext: _ctx]])
className = @"SOGoTemplatesFolder";
/* else if ([folderName isEqualToString:
[mailAccount sieveFolderNameInContext: _ctx]])
obj = [self lookupFiltersFolder: _key inContext: _ctx]; */
else
className = @"SOGoMailFolder";
mailAccount = [self mailAccountFolder];
folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];
fullFolderName = [NSString stringWithFormat: @"%@/%@",
[self traversalFromMailAccount], folderName];
if ([fullFolderName isEqualToString: [mailAccount draftsFolderNameInContext: _ctx]])
className = @"SOGoDraftsFolder";
else if ([fullFolderName isEqualToString: [mailAccount sentFolderNameInContext: _ctx]])
className = @"SOGoSentFolder";
else if ([fullFolderName isEqualToString: [mailAccount trashFolderNameInContext: _ctx]])
className = @"SOGoTrashFolder";
else if ([fullFolderName isEqualToString: [mailAccount junkFolderNameInContext: _ctx]])
className = @"SOGoJunkFolder";
else if ([fullFolderName isEqualToString: [mailAccount templatesFolderNameInContext: _ctx]])
className = @"SOGoTemplatesFolder";
/* else if ([folderName isEqualToString:
[mailAccount sieveFolderNameInContext: _ctx]])
obj = [self lookupFiltersFolder: _key inContext: _ctx]; */
else
className = @"SOGoMailFolder";
obj = [NSClassFromString (className) objectWithName: _key
inContainer: self];
}
else if (isdigit ([_key characterAtIndex: 0])
&& [self exists])
{
obj = [SOGoMailObject objectWithName: _key inContainer: self];
if ([_key hasSuffix: @".eml"])
_key = [_key substringToIndex: [_key length] - 4];
[obj setCoreInfos: [prefetchedInfos objectForKey: _key]];
}
obj = [NSClassFromString (className) objectWithName: _key
inContainer: self];
}
else if (isdigit ([_key characterAtIndex: 0]) && [self exists])
{
obj = [SOGoMailObject objectWithName: _key inContainer: self];
if ([_key hasSuffix: @".eml"])
_key = [_key substringToIndex: [_key length] - 4];
[obj setCoreInfos: [prefetchedInfos objectForKey: _key]];
}
}
if (!obj && _acquire)
obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];
+3
View File
@@ -82,6 +82,9 @@
@interface SOGoMailFrenchForward : SOGoMailForward
@end
@interface SOGoMailGalicianForward : SOGoMailForward
@end
@interface SOGoMailGermanForward : SOGoMailForward
@end
+10 -1
View File
@@ -234,6 +234,12 @@
return [sourceMail contentForEditing];
}
- (BOOL)isSignatureEnabled {
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
return [ud mailUseSignatureOnForward];
}
- (NSString *) signature
{
BOOL fromSentMailbox;
@@ -275,7 +281,7 @@
if (identity)
{
signature = [identity objectForKey: @"signature"];
if ([signature length])
if ([signature length] && [self isSignatureEnabled])
{
nl = (htmlComposition ? @"<br />" : @"\n");
space = (htmlComposition ? @"&nbsp;" : @" ");
@@ -330,6 +336,9 @@
@implementation SOGoMailFrenchForward
@end
@implementation SOGoMailGalicianForward
@end
@implementation SOGoMailGermanForward
@end
@@ -0,0 +1,15 @@
<#newLine/><#newLine
/><#signaturePlacementOnTop><#signature/><#newLine/><#newLine/></#signaturePlacementOnTop
>-------- Mensaxe Orixinal --------<#newLine
/>Subject: <#subject/><#newLine
/>Date: <#date/><#newLine
/>From: <#from/><#newLine
/><#hasReplyTo>Responder a: <#replyTo/><#newLine/></#hasReplyTo
><#hasOrganization>Organización: <#organization/><#newLine/></#hasOrganization
>To: <#to/><#newLine
/><#hasCc>CC: <#cc/><#newLine/></#hasCc
><#hasNewsGroups>Grupos de noticias: <#newsgroups/><#newLine/></#hasNewsGroups
><#hasReferences>Referencias: <#references/><#newLine/></#hasReferences
><#newLine/><#newLine
/><#messageBody
/><#signaturePlacementOnBottom><#newLine/><#newLine/><#signature/></#signaturePlacementOnBottom>
@@ -0,0 +1,106 @@
outlookMode: WOConditional {
condition = outlookMode;
}
standardMode: WOConditional {
condition = outlookMode;
negate = YES;
}
subject: WOString {
value = subject;
escapeHTML = NO;
}
date: WOString {
value = date;
escapeHTML = NO;
}
from: WOString {
value = from;
escapeHTML = NO;
}
newLine: WOString {
value = newLine;
escapeHTML = NO;
}
hasReplyTo: WOConditional {
condition = hasReplyTo;
}
replyTo: WOString {
value = replyTo;
escapeHTML = NO;
}
hasOrganization: WOConditional {
condition = hasOrganization;
}
organization: WOString {
value = organization;
escapeHTML = NO;
}
to: WOString {
value = to;
escapeHTML = NO;
}
hasCc: WOConditional {
condition = hasCc;
}
cc: WOString {
value = cc;
escapeHTML = NO;
}
hasNewsGroups: WOConditional {
condition = hasNewsGroups;
}
newsgroups: WOString {
value = newsgroups;
escapeHTML = NO;
}
hasReferences: WOConditional {
condition = hasReferences;
}
references: WOString {
value = references;
escapeHTML = NO;
}
messageBody: WOString {
value = messageBody;
escapeHTML = NO;
}
signature: WOString {
value = signature;
escapeHTML = NO;
}
replyPlacementOnTop: WOConditional {
condition = replyPlacementOnTop;
}
replyPlacementOnBottom: WOConditional {
condition = replyPlacementOnTop;
negate = YES;
}
signaturePlacementOnTop: WOConditional {
condition = signaturePlacementOnTop;
}
signaturePlacementOnBottom: WOConditional {
condition = signaturePlacementOnTop;
negate = YES;
}
@@ -0,0 +1,18 @@
<#replyPlacementOnTop><#newLine/><#newLine/></#replyPlacementOnTop
><#signaturePlacementOnTop><#signature/><#newLine/><#newLine/></#signaturePlacementOnTop
><#outlookMode
>-------- Mensaxe Orixinal --------<#newLine
/>Subject: <#subject/><#newLine
/>Date: <#date/><#newLine
/>From: <#from/><#newLine
/><#hasReplyTo>Responder a: <#replyTo/><#newLine/></#hasReplyTo
><#hasOrganization>Organización: <#organization/><#newLine/></#hasOrganization
>To: <#to/><#newLine
/><#hasCc>CC: <#cc/><#newLine/></#hasCc
><#hasNewsGroups>Grupos de noticias: <#newsgroups/><#newLine/></#hasNewsGroups
><#hasReferences>Referencias: <#references/><#newLine/></#hasReferences
></#outlookMode
><#standardMode>On <#date/>, <#from/> escribiu:</#standardMode
><#newLine/><#newLine/><#messageBody
/><#replyPlacementOnBottom><#newLine/><#newLine/></#replyPlacementOnBottom
><#signaturePlacementOnBottom><#newLine/><#signature/></#signaturePlacementOnBottom>
@@ -0,0 +1,106 @@
outlookMode: WOConditional {
condition = outlookMode;
}
standardMode: WOConditional {
condition = outlookMode;
negate = YES;
}
subject: WOString {
value = subject;
escapeHTML = NO;
}
date: WOString {
value = date;
escapeHTML = NO;
}
from: WOString {
value = from;
escapeHTML = NO;
}
newLine: WOString {
value = newLine;
escapeHTML = NO;
}
hasReplyTo: WOConditional {
condition = hasReplyTo;
}
replyTo: WOString {
value = replyTo;
escapeHTML = NO;
}
hasOrganization: WOConditional {
condition = hasOrganization;
}
organization: WOString {
value = organization;
escapeHTML = NO;
}
to: WOString {
value = to;
escapeHTML = NO;
}
hasCc: WOConditional {
condition = hasCc;
}
cc: WOString {
value = cc;
escapeHTML = NO;
}
hasNewsGroups: WOConditional {
condition = hasNewsGroups;
}
newsgroups: WOString {
value = newsgroups;
escapeHTML = NO;
}
hasReferences: WOConditional {
condition = hasReferences;
}
references: WOString {
value = references;
escapeHTML = NO;
}
messageBody: WOString {
value = messageBody;
escapeHTML = NO;
}
signature: WOString {
value = signature;
escapeHTML = NO;
}
replyPlacementOnTop: WOConditional {
condition = replyPlacementOnTop;
}
replyPlacementOnBottom: WOConditional {
condition = replyPlacementOnTop;
negate = YES;
}
signaturePlacementOnTop: WOConditional {
condition = signaturePlacementOnTop;
}
signaturePlacementOnBottom: WOConditional {
condition = signaturePlacementOnTop;
negate = YES;
}
+6 -8
View File
@@ -40,18 +40,16 @@
folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];
fullFolderName = [NSString stringWithFormat: @"%@/%@",
[self traversalFromMailAccount], folderName];
if ([fullFolderName
isEqualToString: [mailAccount sentFolderNameInContext: _ctx]])
if ([fullFolderName isEqualToString: [mailAccount sentFolderNameInContext: _ctx]])
className = @"SOGoSentFolder";
else if ([fullFolderName isEqualToString:
[mailAccount draftsFolderNameInContext: _ctx]])
else if ([fullFolderName isEqualToString: [mailAccount draftsFolderNameInContext: _ctx]])
className = @"SOGoDraftsFolder";
else if ([fullFolderName isEqualToString:
[mailAccount trashFolderNameInContext: _ctx]])
else if ([fullFolderName isEqualToString: [mailAccount trashFolderNameInContext: _ctx]])
className = @"SOGoTrashFolder";
else if ([fullFolderName isEqualToString:
[mailAccount junkFolderNameInContext: _ctx]])
else if ([fullFolderName isEqualToString: [mailAccount junkFolderNameInContext: _ctx]])
className = @"SOGoJunkFolder";
else if ([fullFolderName isEqualToString: [mailAccount templatesFolderNameInContext: _ctx]])
className = @"SOGoTemplatesFolder";
/* else if ([folderName isEqualToString:
[mailAccount sieveFolderNameInContext: _ctx]])
obj = [self lookupFiltersFolder: _key inContext: _ctx]; */
+1
View File
@@ -97,6 +97,7 @@ extern NSArray *SOGoMailCoreInfoKeys;
- (NSString *) cc;
- (NSString *) from;
- (NSString *) inReplyTo;
- (NSString *) references;
- (NSString *) messageId;
- (NSString *) received;
+3
View File
@@ -81,6 +81,9 @@
@interface SOGoMailFrenchReply : SOGoMailReply
@end
@interface SOGoMailGalicianReply : SOGoMailReply
@end
@interface SOGoMailGermanReply : SOGoMailReply
@end
+11
View File
@@ -22,6 +22,8 @@
#import <NGExtensions/NSString+misc.h>
#import <NGImap4/NGImap4Envelope.h>
#import <SOGo/SOGoUserDefaults.h>
#import "SOGoMailObject+Draft.h"
#import "SOGoMailReply.h"
@@ -85,6 +87,12 @@
return s;
}
- (BOOL)isSignatureEnabled {
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
return [ud mailUseSignatureOnReply];
}
@end
@implementation SOGoMailArabicReply
@@ -129,6 +137,9 @@
@implementation SOGoMailFrenchReply
@end
@implementation SOGoMailGalicianReply
@end
@implementation SOGoMailGermanReply
@end
+8 -2
View File
@@ -93,7 +93,10 @@ SOGo_HEADER_FILES = \
\
JWT.h \
\
NGMimeBodyPart+SOGo.h
NGMimeBodyPart+SOGo.h \
NGMimeFileData+SOGo.h \
\
SOGoMobileProvision.h
all::
@touch SOGoBuild.m
@@ -180,7 +183,10 @@ SOGo_OBJC_FILES = \
\
JWT.m \
\
NGMimeBodyPart+SOGo.m
NGMimeBodyPart+SOGo.m \
NGMimeFileData+SOGo.m \
\
SOGoMobileProvision.m
SOGo_C_FILES += lmhash.c aes.c crypt_blowfish.c pkcs5_pbkdf2.c
+59 -12
View File
@@ -1384,6 +1384,21 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
- (NSArray *) fetchContactsMatching: (NSString *) match
withCriteria: (NSArray *) criteria
inDomain: (NSString *) theDomain
{
if ([match length] > 0) {
return [self fetchContactsMatching: (NSString *) match
withCriteria: (NSArray *) criteria
inDomain: (NSString *) theDomain
limit: -1];
} else {
return [NSMutableArray array];
}
}
- (NSArray *) fetchContactsMatching: (NSString *) match
withCriteria: (NSArray *) criteria
inDomain: (NSString *) theDomain
limit: (int) limit
{
NSAutoreleasePool *pool;
NGLdapConnection *ldapConnection;
@@ -1392,6 +1407,8 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
NSMutableArray *contacts;
EOQualifier *qualifier;
unsigned int i;
NSString *sortAttribute;
BOOL sortReverse;
contacts = [NSMutableArray array];
@@ -1400,18 +1417,48 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
ldapConnection = [self _ldapConnection];
qualifier = [self _qualifierForFilter: match onCriteria: criteria];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
else /* we do it like before */
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
if (limit > 0) {
[ldapConnection setQuerySizeLimit: limit];
}
if (limit > 0) {
// Sort results
sortAttribute = @"cn";
sortReverse = NO;
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields
sortAttribute: sortAttribute
sortReverse: sortReverse];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields
sortAttribute: sortAttribute
sortReverse: sortReverse];
else /* we do it like before */
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields
sortAttribute: sortAttribute
sortReverse: sortReverse];
} else {
// No sort results
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
else /* we do it like before */
entries = [ldapConnection deepSearchAtBaseDN: _baseDN
qualifier: qualifier
attributes: _lookupFields];
}
i = 0;
pool = [NSAutoreleasePool new];
+34
View File
@@ -0,0 +1,34 @@
/* NGMimeFileData+SOGo.h - this file is part of SOGo
*
* Copyright (C) 2023 Alinto
*
* Author: Sébastien Mizrahi <smizrahi@alinto.eu>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef NGMIMEFILEDATA_SOGO_H
#define NGMIMEFILEDATA_SOGO_H
#import <NGMime/NGMimeFileData.h>
@interface NGMimeFileData (SOGoExtensions)
- (NSString *) path;
@end
#endif /* NGMIMEFILEDATA_SOGO_H */
+32
View File
@@ -0,0 +1,32 @@
/* NGMimeFileData+SOGo.m - this file is part of SOGo
*
* Copyright (C) 2023 Alinto
*
* Author: Sébastien Mizrahi <smizrahi@alinto.eu>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import "NGMimeFileData+SOGo.h"
@implementation NGMimeFileData (SOGoExtensions)
- (NSString *) path {
return path;
}
@end
+7 -1
View File
@@ -26,6 +26,7 @@
#define NSSTRING_CRYPTO_H
#import <Foundation/NSString.h>
#import <Foundation/Foundation.h>
typedef enum {
encDefault, //!< default encoding, let the algorithm decide
@@ -35,8 +36,10 @@ typedef enum {
} keyEncoding;
@class NSObject;
@class NSScanner;
@interface NSString (SOGoCryptoExtension)
@interface
NSString(SOGoCryptoExtension)
- (BOOL) isEqualToCrypted: (NSString *) cryptedPassword
withDefaultScheme: (NSString *) theScheme
@@ -62,6 +65,9 @@ typedef enum {
+ (NSArray *) getDefaultEncodingForScheme: (NSString *) passwordScheme;
- (NSString *) encodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL)encodedURL exception:(NSException **)ex;
- (NSString *) decodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL)encodedURL exception:(NSException **)ex;
@end
#endif /* NSSTRING_CRYPTO_H */
+136
View File
@@ -29,6 +29,11 @@
#import "NSData+Crypto.h"
#import <NGExtensions/NGBase64Coding.h>
#import "aes.h"
#define AES_SIZE 8096
#define AES_KEY_SIZE 16
static const NSString *kAES128ECError = @"kAES128ECError";
@implementation NSString (SOGoCryptoExtension)
@@ -349,4 +354,135 @@
return [[NSData encodeDataAsHexString: [d asLM]] uppercaseString];
}
/**
* Encrypts the data using AES 128 ECB mechanism
*
* @param passwordScheme The 128 bits password key
* @param encodedURL YES if the special base64 characters shall be escaped for URL
* @param ex Exception pointer
* @return If successful, encrypted string in base64
*/
- (NSString *) encodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL)encodedURL exception:(NSException **)ex
{
NSData *inputData, *keyData, *outputData;
if (AES_KEY_SIZE != [passwordScheme length]) {
*ex = [NSException exceptionWithName:kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_KEY_SIZE * 8)] userInfo: nil];
return nil;
}
NSString *value;
int size, i;
uint8_t output[AES_SIZE], input[AES_SIZE];
inputData = [self dataUsingEncoding: NSUnicodeStringEncoding];
keyData = [passwordScheme dataUsingEncoding: NSUnicodeStringEncoding];
size = [inputData length] + 16; // Add one unicode char size 16 bits (NUL)
if (inputData == nil) {
*ex = [NSException exceptionWithName:kAES128ECError reason: @"Invalid input data (encrypt)" userInfo: nil];
return nil;
}
if (((AES_SIZE / 2) - 16) < [self length]) {
*ex = [NSException exceptionWithName:kAES128ECError reason: [NSString stringWithFormat:@"Invalid size (encrypt). max size is %d. Current size : %d", ((AES_SIZE / 2) - 16), [self length]] userInfo: nil];
return nil;
}
memset(output, 0x00, AES_SIZE);
memset(input, 0x00, AES_SIZE);
[inputData getBytes: input length: [inputData length]];
input[[inputData length]] = '\0'; // Add NUL
for(i = 0 ; i < (AES_SIZE / 16) ; ++i)
{
AES128_ECB_encrypt(input + (i*16), [keyData bytes], output+(i*16));
}
outputData = [NSData dataWithBytes: (char *)output length: size];
if (outputData) {
value = [outputData base64EncodedStringWithOptions: 0];
if (encodedURL) {
value = [value stringByReplacingOccurrencesOfString: @"+" withString: @"."];
value = [value stringByReplacingOccurrencesOfString: @"/" withString: @"_"];
value = [value stringByReplacingOccurrencesOfString: @"=" withString: @"-"];
}
return value;
} else {
*ex = [NSException exceptionWithName:kAES128ECError reason:@"Empty data" userInfo: nil];
}
return nil;
}
/**
* Decrypts the base64 data using AES 128 ECB mechanism
*
* @param passwordScheme The 128 bits password key
* @param encodedURL YES if the special base64 characters has been escaped for URL
* @param ex Exception pointer
* @return If successful, decrypted string
*/
- (NSString *) decodeAES128ECBBase64:(NSString *)passwordScheme encodedURL:(BOOL)encodedURL exception:(NSException **)ex
{
NSData *inputData, *keyData, *outputData;
NSString *value, *inputString, *tmpStr;
int i;
uint8_t output[AES_SIZE];
if (AES_KEY_SIZE != [passwordScheme length]) {
*ex = [NSException exceptionWithName:kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_KEY_SIZE * 8)] userInfo: nil];
return nil;
}
value = nil;
keyData = [passwordScheme dataUsingEncoding: NSUnicodeStringEncoding];
memset(output, 0x00, AES_SIZE);
inputString = [NSString stringWithString: self];
if (encodedURL) {
inputString = [inputString stringByReplacingOccurrencesOfString: @"." withString: @"+"];
inputString = [inputString stringByReplacingOccurrencesOfString: @"_" withString: @"/"];
inputString = [inputString stringByReplacingOccurrencesOfString: @"-" withString: @"="];
}
inputData = [[NSData alloc] initWithBase64EncodedString:inputString options:0];
if (inputData == nil) {
*ex = [NSException exceptionWithName:kAES128ECError reason: @"Invalid input data (decrypt)" userInfo: nil];
return nil;
}
if ((AES_SIZE) < [inputData length]) {
*ex = [NSException exceptionWithName:kAES128ECError reason: [NSString stringWithFormat:@"Invalid size (decrypt). max size is %d. Current size : %d", AES_SIZE, [inputData length]] userInfo: nil];
return nil;
}
for(i = 0; i < (AES_SIZE / 16); ++i)
{
AES128_ECB_decrypt([inputData bytes] + (i*16), [keyData bytes], output + (i*16));
}
outputData = [NSData dataWithBytes: output length:([inputData length] - 16)];
if (outputData) {
tmpStr = [[NSString alloc] initWithData: outputData encoding: NSUnicodeStringEncoding];
if (tmpStr) {
value = [NSString stringWithUTF8String: [tmpStr UTF8String]];
} else {
*ex = [NSException exceptionWithName:kAES128ECError reason:@"Empty converted decrypted data" userInfo: nil];
value = nil;
}
[tmpStr release];
} else {
*ex = [NSException exceptionWithName:kAES128ECError reason:@"Empty data" userInfo: nil];
}
return value;
}
@end
+4
View File
@@ -65,6 +65,7 @@
- (id) objectFromJSONString;
/* bare email addresses */
- (NSString *) mailDomain;
- (NSString *) pureEMailAddress;
- (NSString *) asQPSubjectString: (NSString *) encoding;
@@ -92,6 +93,9 @@
- (NSString *) encryptWithKey: (NSString *) theKey;
- (NSString *) decryptWithKey: (NSString *) theKey;
/* HTML */
- (NSString *) cleanInvalidHTMLTags;
@end
#endif /* NSSTRING_URL_H */
+35
View File
@@ -609,6 +609,18 @@ static int cssEscapingCount;
return newString;
}
- (NSString *) mailDomain
{
NSArray *mailSeparated;
mailSeparated = [self componentsSeparatedByString: @"@"];
if([mailSeparated count] == 2)
return [mailSeparated objectAtIndex: 1];
[self logWithFormat: @"Error while extracting domain from : %@", self];
return nil;
}
- (NSString *) pureEMailAddress
{
NSString *pureAddress;
@@ -979,6 +991,18 @@ static int cssEscapingCount;
newResult = [regex stringByReplacingMatchesInString:result options:0 range:NSMakeRange(0, [result length]) withTemplate:@"<ifr***"];
result = [NSString stringWithString: newResult];
// Remove <form
regex = [NSRegularExpression regularExpressionWithPattern:@"<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*f[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m"
options: NSRegularExpressionCaseInsensitive error:&error];
newResult = [regex stringByReplacingMatchesInString:result options:0 range:NSMakeRange(0, [result length]) withTemplate:@"<for*"];
result = [NSString stringWithString: newResult];
// Remove </form
regex = [NSRegularExpression regularExpressionWithPattern:@"<[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*/[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*f[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*o[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*r[\\s\\u200B&#x09;&#x0A;&#x0D;\\\\0]*m"
options: NSRegularExpressionCaseInsensitive error:&error];
newResult = [regex stringByReplacingMatchesInString:result options:0 range:NSMakeRange(0, [result length]) withTemplate:@"</for*"];
result = [NSString stringWithString: newResult];
// Remove onload
regex = [NSRegularExpression regularExpressionWithPattern:@"onload="
options: NSRegularExpressionCaseInsensitive error:&error];
@@ -1001,4 +1025,15 @@ static int cssEscapingCount;
return result;
}
- (NSString *) cleanInvalidHTMLTags {
// Clean HTML invalid tags as reported in https://bugs.sogo.nu/view.php?id=5755
NSString *s;
s = [NSString stringWithString: self];
s = [s stringByReplacingOccurrencesOfString:@"<!-->" withString:@""];
s = [s stringByReplacingOccurrencesOfString:@"<!--<!" withString:@"<!--"];
return s;
}
@end
+1 -2
View File
@@ -400,8 +400,7 @@
application = [WOApplication application];
request = [[application context] request];
pgtURL = [NSString stringWithFormat: @"https://%@/%@/casProxy",
[soURL host], [request applicationName]];
pgtURL = [NSString stringWithFormat: @"%@casProxy", [soURL absoluteString]];
return pgtURL;
}
+1
View File
@@ -106,6 +106,7 @@
//
// CAS support
//
- (NSString *) sha512HashTicket: (NSString* ) ticket;
- (NSString *) CASTicketFromIdentifier: (NSString *) identifier;
- (NSString *) CASSessionWithTicket: (NSString *) ticket;
- (void) setCASSession: (NSString *) casSession
+33 -5
View File
@@ -54,6 +54,10 @@
#import "SOGoCache.h"
#ifdef HAVE_OPENSSL
#include <openssl/sha.h>
#endif
/* Those used to be ivars but were converted to static globals to work around
a bug in GCC that prevent SOGo from using libmemcached > 0.37:
@@ -599,9 +603,32 @@ static memcached_st *handle = NULL;
forKey: theLogin];
}
//
// CAS session support
//
- (NSString *) sha512HashTicket: (NSString* ) ticket
{
int i;
NSMutableString *hashedTicket;
#if defined(HAVE_OPENSSL)
unsigned char hash[SHA512_DIGEST_LENGTH] = {};
const unsigned char *data = (const unsigned char *) [ticket UTF8String];
SHA512(data, strlen((char *)data), hash);
hashedTicket = [NSMutableString stringWithCapacity:SHA512_DIGEST_LENGTH * 2];
for( i = 0; i < SHA512_DIGEST_LENGTH; i++) {
[hashedTicket appendFormat:@"%02x", hash[i]];
}
#endif
if(!hashedTicket)
return ticket;
return [NSString stringWithString:hashedTicket];
}
- (NSString *) CASTicketFromIdentifier: (NSString *) identifier
{
return [self valueForKey: [NSString stringWithFormat: @"cas-id:%@",
@@ -610,8 +637,9 @@ static memcached_st *handle = NULL;
- (NSString *) CASSessionWithTicket: (NSString *) ticket
{
//Ticket can be more than 255 char so we hashed it to avoid memcached key limit
return [self valueForKey: [NSString stringWithFormat: @"cas-ticket:%@",
ticket]];
[self sha512HashTicket: ticket]]];
}
- (void) setCASSession: (NSString *) casSession
@@ -621,14 +649,14 @@ static memcached_st *handle = NULL;
[self setValue: ticket
forKey: [NSString stringWithFormat: @"cas-id:%@", identifier]];
[self setValue: casSession
forKey: [NSString stringWithFormat: @"cas-ticket:%@", ticket]];
forKey: [NSString stringWithFormat: @"cas-ticket:%@", [self sha512HashTicket: ticket]]];
}
- (NSString *) CASPGTIdFromPGTIOU: (NSString *) pgtIou
{
NSString *casPgtId, *key;
key = [NSString stringWithFormat: @"cas-pgtiou:%@", pgtIou];
key = [NSString stringWithFormat: @"cas-pgtiou:%@", [self sha512HashTicket: pgtIou]];
casPgtId = [self valueForKey: key];
/* we directly remove the value as it can only be used once anyway */
if (casPgtId)
@@ -641,7 +669,7 @@ static memcached_st *handle = NULL;
forPGTIOU: (NSString *) pgtIou
{
[self setValue: pgtId
forKey: [NSString stringWithFormat: @"cas-pgtiou:%@", pgtIou]];
forKey: [NSString stringWithFormat: @"cas-pgtiou:%@", [self sha512HashTicket: pgtIou]]];
}
- (void) removeCASSessionWithTicket: (NSString *) ticket
@@ -649,7 +677,7 @@ static memcached_st *handle = NULL;
NSString *key, *session;
if ((session = [self CASSessionWithTicket: ticket]))
{
key = [NSString stringWithFormat: @"cas-ticket:%@", ticket];
key = [NSString stringWithFormat: @"cas-ticket:%@", [self sha512HashTicket: ticket]];
[self removeValueForKey: key];
[self debugWithFormat: @"Removed CAS session: %@", session];
}
+13 -2
View File
@@ -100,8 +100,19 @@
if (data) {
if ([data isKindOfClass: [NSString class]])
ASSIGN (content, data);
else
ASSIGN (content, [NSString stringWithUTF8String:[data bytes]]);
else {
char lastByte;
[data getBytes:&lastByte range:NSMakeRange([data length]-1, 1)];
if (lastByte == 0x0) {
// string is null terminated
ASSIGN (content, [NSString stringWithUTF8String: [data bytes]]);
} else {
// string is not null terminated
NSString* myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
ASSIGN (content, myString);
[myString release];
}
}
}
data = [objectRecord objectForKey: @"c_version"];
if (data)
+2
View File
@@ -29,6 +29,8 @@
@interface SOGoDAVAuthenticator : SoHTTPAuthenticator <SOGoAuthenticator>
WOContext *context;
+ (id) sharedSOGoDAVAuthenticator;
@end
+12 -1
View File
@@ -41,12 +41,19 @@
{
static SOGoDAVAuthenticator *auth = nil;
if (!auth)
if (!auth) {
auth = [self new];
context = nil;
}
return auth;
}
- (void) setContext: (WOContext *) _context
{
context = _context;
}
- (BOOL) checkLogin: (NSString *) _login
password: (NSString *) _pwd
{
@@ -68,6 +75,10 @@
expire: &expire
grace: &grace]
&& perr == PolicyNoError);
if (context) {
[SOGoUser getEncryptedUsernameIfNeeded: [_login stringByReplacingString: @"%40"
withString: @"@"] withContext: context]; // Create cache entry
}
if (!rc)
{
sd = [SOGoSystemDefaults sharedSystemDefaults];
+6
View File
@@ -57,6 +57,7 @@
"English",
"Finnish",
"French",
"Galician",
"German",
"Hebrew",
"Hungarian",
@@ -108,6 +109,9 @@
SOGoMailMessageForwarding = "inline";
SOGoMailReplyPlacement = "below";
SOGoMailSignaturePlacement = "below";
SOGoMailUseSignatureOnNew = YES;
SOGoMailUseSignatureOnReply = YES;
SOGoMailUseSignatureOnForward = YES;
SOGoRefreshViewIntervals = ( 1, 2, 5, 10, 20, 30, 60 );
SOGoMailListViewColumnsOrder = ( "Thread", "Flagged", "Attachment", "Subject",
"From", "Unread", "Date", "Priority",
@@ -119,6 +123,8 @@
SOGoTemplatesFolderName = "Templates";
SOGoMailComposeMessageType = "html";
SOGoMailComposeFontSize = "0";
SOGoMailHideInlineAttachments = NO;
SOGoMailDisplayFullEmail = NO;
SOGoMailDisplayRemoteInlineImages = "never";
SOGoMailCertificateEnabled = YES;
+1
View File
@@ -53,6 +53,7 @@
- (BOOL) forwardEnabled;
- (int) forwardConstraints;
- (NSArray *) forwardConstraintsDomains;
- (BOOL) notificationEnabled;
- (BOOL) vacationEnabled;
- (BOOL) vacationPeriodEnabled;
- (NSString *) vacationDefaultSubject;
+6 -1
View File
@@ -217,7 +217,7 @@
v = [self integerForKey: @"SOGoForwardConstraints"];
return (v > 2 ? 0 : v);
return (v > 3 ? 0 : v);
}
- (NSArray *) forwardConstraintsDomains
@@ -225,6 +225,11 @@
return [self stringArrayForKey: @"SOGoForwardConstraintsDomains"];
}
- (BOOL) notificationEnabled
{
return [self boolForKey: @"SOGoNotificationEnabled"];
}
- (BOOL) vacationEnabled
{
return [self boolForKey: @"SOGoVacationEnabled"];
+26 -24
View File
@@ -150,7 +150,7 @@ static NSArray *childRecordFields = nil;
if ([pathElements count] > 1)
ocsName = [pathElements objectAtIndex: 1];
else
ocsName = [[context request] isMacOSXVenturaCalendarApp] ? @"Personal" : @"personal";
ocsName = @"personal";
path = [NSString stringWithFormat: @"/Users/%@/%@/%@",
login, [pathElements objectAtIndex: 0], ocsName];
@@ -400,14 +400,7 @@ static NSArray *childRecordFields = nil;
}
- (void) setOCSPath: (NSString *) _path
{
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
_path = [_path stringByReplacingOccurrencesOfString:@"/PERSONAL" withString:@"/personal"];
}
{
if (![ocsPath isEqualToString:_path])
{
if (ocsPath)
@@ -438,12 +431,6 @@ static NSArray *childRecordFields = nil;
cache = [SOGoCache sharedCache];
record = [[cache valueForKey: _path] objectFromJSONString];
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
_path = [_path stringByReplacingOccurrencesOfString:@"PERSONAL" withString:@"personal"];
}
// We check if we got a cache miss or a potentially bogus
// entry from the cache
@@ -471,13 +458,6 @@ static NSArray *childRecordFields = nil;
realNameInContainer = [self realNameInContainer];
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
realNameInContainer = [realNameInContainer stringByReplacingOccurrencesOfString:@"PERSONAL" withString:@"personal"];
}
return [NSString stringWithFormat: @"%@:%@/%@",
owner,
[container nameInContainer],
@@ -553,7 +533,7 @@ static NSArray *childRecordFields = nil;
publicParticle = @"";
path = [NSString stringWithFormat: @"/%@/dav%@/%@/%@/%@/",
appName, publicParticle,
[[self ownerInContext: nil] stringByEscapingURL],
[[SOGoUser getEncryptedUsernameIfNeeded:[self ownerInContext: nil] withContext: context] stringByEscapingURL],
[[container nameInContainer] stringByEscapingURL],
[[self realNameInContainer] stringByEscapingURL]];
currentDavURL = [self davURL];
@@ -729,7 +709,7 @@ static NSArray *childRecordFields = nil;
{
NSArray *records, *names;
NSString *sqlFilter, *compFilter;
EOQualifier *aclQualifier, *componentQualifier, *qualifier;
EOQualifier *aclQualifier, *componentQualifier, *qualifier, *vlistExclusionQualifier;
sqlFilter = [self aclSQLListingFilter];
if (sqlFilter)
@@ -758,8 +738,19 @@ static NSArray *childRecordFields = nil;
else
qualifier = aclQualifier;
// For Thunderbird, disable contact list
// This will be removed when VCARD will be implemented
if ([[context request] isThunderbird] && [self isKindOfClass: NSClassFromString(@"SOGoContactGCSFolder")]) {
vlistExclusionQualifier = [EOQualifier qualifierWithQualifierFormat: @"c_component != 'vlist'"];
qualifier = [[[EOAndQualifier alloc] initWithQualifiers:
vlistExclusionQualifier,
qualifier,
nil] autorelease];
}
records = [[self ocsFolder] fetchFields: childRecordFields
matchingQualifier: qualifier];
if (![records isNotNull])
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
@@ -1196,9 +1187,20 @@ static NSArray *childRecordFields = nil;
{
GCSFolder *folder;
EOFetchSpecification *fetchSpec;
EOQualifier *vlistExclusionQualifier;
folder = [self ocsFolder];
// For Thunderbird, disable contact list
// This will be removed when VCARD will be implemented
if ([[context request] isThunderbird] && [self isKindOfClass: NSClassFromString(@"SOGoContactGCSFolder")]) {
vlistExclusionQualifier = [EOQualifier qualifierWithQualifierFormat: @"c_component != 'vlist'"];
qualifier = [[[EOAndQualifier alloc] initWithQualifiers:
vlistExclusionQualifier,
qualifier,
nil] autorelease];
}
if (qualifier)
fetchSpec = [EOFetchSpecification
fetchSpecificationWithEntityName: [folder folderName]
+45
View File
@@ -0,0 +1,45 @@
/* SOGoMobileProvision.h - this file is part of SOGo
*
* Copyright (C) 2023 Alinto
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef SOGOMOBILEPROVISION_H
#define SOGOMOBILEPROVISION_H
#include <SOGo/SOGoObject.h>
@class SOGoMobileProvision;
@class NSString;
typedef enum
{
ProvisioningTypeCalendar = 0,
ProvisioningTypeContact = 1
} ProvisioningType;
@interface SOGoMobileProvision : SOGoObject
{
}
+ (NSString *)plistForCalendarsWithContext:(WOContext *)context andPath:(NSString *)path andName:(NSString *)name;
+ (NSString *)plistForContactsWithContext:(WOContext *)context andPath:(NSString *)path andName:(NSString *)name;
@end
#endif /* SOGOMOBILEPROVISION_H */
+131
View File
@@ -0,0 +1,131 @@
/* SOGoMobileProvision.m - this file is part of SOGo
*
* Copyright (C) 2023 Alinto
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#import "SOGoMobileProvision.h"
#import "SOGoUser.h"
#import "NSString+Crypto.h"
@implementation SOGoMobileProvision
+ (NSString *)_plistWithContext:(WOContext *)context andPath:(NSString *)path andType:(ProvisioningType) provisioningType andName:(NSString *)name
{
NSData *plistData;
SOGoUser *activeUser;
NSURL *serverURL;
NSDictionary *provisioning;
NSError *error;
NSString *payloadType, *prefix, *type;
NSNumber *port;
activeUser = [context activeUser];
serverURL = [context serverURL];
if (!context || !path) {
[self errorWithFormat: @"Invalid provisioning profile - no parameters"];
return nil;
}
switch(provisioningType) {
case ProvisioningTypeCalendar:
payloadType = @"com.apple.caldav.account";
prefix = @"CalDAV";
type = @"Calendar";
break;
case ProvisioningTypeContact:
payloadType = @"com.apple.carddav.account";
prefix = @"CardDAV";
type = @"Contact";
break;
}
port = [serverURL port];
if (!port) {
if ([[serverURL scheme] isEqualToString:@"https"]) {
port = [NSNumber numberWithInt: 443];
} else {
port = [NSNumber numberWithInt: 80];
}
}
provisioning = [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObject: [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:@"%@ %@", type, name], [NSString stringWithFormat:@"%@%@", prefix, @"AccountDescription"],
[serverURL host], [NSString stringWithFormat:@"%@%@", prefix, @"HostName"],
port, [NSString stringWithFormat:@"%@%@", prefix, @"Port"],
path, [NSString stringWithFormat:@"%@%@", prefix, @"PrincipalURL"],
[NSNumber numberWithBool:[[serverURL scheme] isEqualToString:@"https"]], [NSString stringWithFormat:@"%@%@", prefix, @"UseSSL"],
[activeUser login], [NSString stringWithFormat:@"%@%@", prefix, @"Username"],
[NSString stringWithFormat: @"SOGo %@ provisioning", prefix], @"PayloadDescription",
[NSString stringWithFormat:@"%@ %@", type, name], @"PayloadDisplayName",
[NSString stringWithFormat:@"%@.%@.apple.%@", [serverURL host], [name asMD5String], [prefix lowercaseString]], @"PayloadIdentifier",
[serverURL host], @"PayloadOrganization",
payloadType, @"PayloadType",
[SOGoObject globallyUniqueObjectId], @"PayloadUUID",
[NSNumber numberWithInt: 1], @"PayloadVersion",
nil]], @"PayloadContent",
[NSString stringWithFormat:@"SOGo %@ provisioning", prefix], @"PayloadDescription",
[NSString stringWithFormat: @"%@ (%@)", name, type], @"PayloadDisplayName",
[NSString stringWithFormat:@"%@.%@.%@.apple", [prefix lowercaseString], [serverURL host], [name asMD5String]], @"PayloadIdentifier",
@"SOGo", @"PayloadOrganization",
[NSNumber numberWithBool: NO], @"PayloadRemovalDisallowed",
@"Configuration", @"PayloadType",
[SOGoObject globallyUniqueObjectId], @"PayloadUUID",
[NSNumber numberWithInt: 1], @"PayloadVersion",
nil];
return [NSPropertyListSerialization dataWithPropertyList: provisioning
format: NSPropertyListXMLFormat_v1_0
options: 0
error: &error];
}
+ (NSString *)plistForCalendarsWithContext:(WOContext *)context andPath:(NSString *)path andName:(NSString *)name
{
NSData *plistData;
SOGoUser *activeUser;
plistData = [self _plistWithContext: context andPath: path andType: ProvisioningTypeCalendar andName: name];
if (nil != plistData) {
return [[[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding] autorelease];
} else {
[self errorWithFormat: @"Invalid provisioning profile plist for account : %@", [activeUser login]];
return nil;
}
}
+ (NSString *)plistForContactsWithContext:(WOContext *)context andPath:(NSString *)path andName:(NSString *)name
{
NSData *plistData;
SOGoUser *activeUser;
plistData = [self _plistWithContext: context andPath: path andType: ProvisioningTypeContact andName: name];
if (nil != plistData) {
return [[[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding] autorelease];
} else {
[self errorWithFormat: @"Invalid provisioning profile plist for account : %@", [activeUser login]];
return nil;
}
}
@end
+2 -1
View File
@@ -103,7 +103,8 @@
/* ownership */
- (void) setOwner: (NSString *) newOwner;
- (NSString *)owner;
- (void)setOwner:(NSString *)newOwner;
- (NSString *) ownerInContext: (id) _ctx;
/* a helper that determines whether access rights can be ignored on
+7 -2
View File
@@ -211,6 +211,11 @@
/* ownership */
- (NSString *) owner
{
return owner;
}
- (void) setOwner: (NSString *) newOwner
{
NSString *uid;
@@ -360,7 +365,7 @@
NSString *usersUrl;
usersUrl = [NSString stringWithFormat: @"%@%@/",
[[WOApplication application] davURLAsString], owner];
[[WOApplication application] davURLAsString], [SOGoUser getEncryptedUsernameIfNeeded: owner withContext: context]];
ownerHREF = davElementWithContent (@"href", XMLNS_WEBDAV, usersUrl);
return [davElementWithContent (@"owner", XMLNS_WEBDAV, ownerHREF)
@@ -1222,7 +1227,7 @@
davCurrentUserPrincipal = nil;
else
{
s = [NSString stringWithFormat: @"/SOGo/dav/%@", login];
s = [NSString stringWithFormat: @"/SOGo/dav/%@", [SOGoUser getEncryptedUsernameIfNeeded:login withContext:[self context]]];
userHREF = davElementWithContent (@"href", XMLNS_WEBDAV, s);
davCurrentUserPrincipal
= [davElementWithContent (@"current-user-principal",
+4 -12
View File
@@ -144,7 +144,7 @@ static SoSecurityManager *sm = nil;
- (NSString *) defaultFolderName
{
return [[context request] isMacOSXVenturaCalendarApp] ? @"PERSONAL" : @"Personal";
return @"Personal";
}
- (NSString *) collectedFolderName
@@ -173,7 +173,7 @@ static SoSecurityManager *sm = nil;
{
if (folderType == SOGoPersonalFolder)
{
folderName = [[context request] isMacOSXVenturaCalendarApp] ? @"PERSONAL" : @"personal";
folderName = @"personal";
folder = [subFolderClass objectWithName: folderName inContainer: self];
[folder setDisplayName: [self defaultFolderName]];
[folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]];
@@ -221,15 +221,7 @@ static SoSecurityManager *sm = nil;
while ((row = [fc fetchAttributes: attrs withZone: NULL]))
{
key = [row objectForKey: @"c_path4"];
// FIXME: Improve MacOSX Ventura support
// Check if the problem will be fixed by Apple or if this fix should be kept in the future
// Ticket #5639
if ([[context request] isMacOSXVenturaCalendarApp]) {
if ([key isEqualToString:@"personal"]) {
key = @"PERSONAL";
}
}
if ([key isKindOfClass: [NSString class]])
{
folder = [subFolderClass objectWithName: key inContainer: self];
@@ -241,7 +233,7 @@ static SoSecurityManager *sm = nil;
}
if (folderType == SOGoPersonalFolder)
{
if (![subFolders objectForKey: @"personal"] || ![subFolders objectForKey: @"Personal"] || ![subFolders objectForKey: @"PERSONAL"])
if (![subFolders objectForKey: @"personal"])
[self createSpecialFolder: SOGoPersonalFolder];
}
else if (folderType == SOGoCollectedFolder)
+12 -6
View File
@@ -33,6 +33,8 @@ static const NSString *POLICY_MIN_DIGIT = @"POLICY_MIN_DIGIT";
static const NSString *POLICY_MIN_SPECIAL_SYMBOLS = @"POLICY_MIN_SPECIAL_SYMBOLS";
static const NSString *POLICY_MIN_LENGTH = @"POLICY_MIN_LENGTH";
static const NSString *SPECIAL_SYMBOL_ALLOWED = @"%$&*(){}!?\\@#.,:;+=\\[\\]\\|<>\\/-_";
@implementation SOGoPasswordPolicy
- (id) init
@@ -58,7 +60,7 @@ static const NSString *POLICY_MIN_LENGTH = @"POLICY_MIN_LENGTH";
return [NSArray arrayWithObjects: [NSString stringWithFormat:@"(.*[a-z].*){%i}", [count intValue]],
[NSString stringWithFormat:@"(.*[A-Z].*){%i}", [count intValue]],
[NSString stringWithFormat:@"(.*[0-9].*){%i}", [count intValue]],
[NSString stringWithFormat:@"([%%$&*(){}!?\\@#.,:;+=].*){%i,}", [count intValue]],
[NSString stringWithFormat:@"([%@].*){%i,}", SPECIAL_SYMBOL_ALLOWED, [count intValue]],
[NSString stringWithFormat:@".{%i,}", [count intValue]],
nil];
}
@@ -98,12 +100,16 @@ static const NSString *POLICY_MIN_LENGTH = @"POLICY_MIN_LENGTH";
NSNumber *value = [policy objectForKey:@"value"];
if (0 < value) {
NSString *newLabel = [[translations objectForKey: label]
stringByReplacingOccurrencesOfString: @"%{0}"
withString: [value stringValue]];
stringByReplacingOccurrencesOfString: @"%{0}"
withString: [value stringValue]];
if ([POLICY_MIN_SPECIAL_SYMBOLS isEqualToString: label]) {
newLabel = [[newLabel stringByAppendingString: @" "]
stringByAppendingString: SPECIAL_SYMBOL_ALLOWED];
}
[userTranslatedPasswordPolicy addObject:[NSDictionary dictionaryWithObjectsAndKeys:
newLabel, @"label",
[policy objectForKey:@"regex"], @"regex",
nil]];
newLabel, @"label",
[policy objectForKey:@"regex"], @"regex",
nil]];
} else {
// Do nothing
}
+4 -2
View File
@@ -32,9 +32,11 @@
acquire: (BOOL) acquire
{
id userFolder;
NSString *login;
if ([key length] > 0 && [SOGoUser userWithLogin: key roles: nil])
userFolder = [SOGoUserFolder objectWithName: key inContainer: self];
login = [SOGoUser getDecryptedUsernameIfNeeded: key withContext: localContext];
if ([key length] > 0 && [SOGoUser userWithLogin: login roles: nil])
userFolder = [SOGoUserFolder objectWithName: login inContainer: self];
else
userFolder = nil;
+1
View File
@@ -31,6 +31,7 @@
NSString *fieldName;
}
- (unsigned long long)getCDefaultsSize;
@end
+56 -2
View File
@@ -27,6 +27,7 @@
#import <GDLAccess/EOAdaptorChannel.h>
#import <GDLAccess/EOAdaptorContext.h>
#import <GDLAccess/EOAttribute.h>
#import <SOGo/SOGoCache.h>
#import "SOGoSystemDefaults.h"
@@ -35,6 +36,7 @@
static NSURL *tableURL = nil;
static NSString *uidColumnName = @"c_uid";
static EOAttribute *textColumn = nil;
static const NSString *kCDefaultsLenKey = @"kCDefaultsLenKey";
@implementation SOGoSQLUserProfile
@@ -100,10 +102,12 @@ static EOAttribute *textColumn = nil;
EOAdaptorChannel *channel;
NSDictionary *row;
NSException *ex;
NSString *sql, *value;
NSString *sql, *result;
id value;
NSArray *attrs;
value = nil;
result = nil;
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: tableURL];
@@ -134,6 +138,12 @@ static EOAttribute *textColumn = nil;
value = [row objectForKey: fieldName];
if (![value isNotNull])
value = nil; /* we discard any NSNull instance */
if (value && [value isKindOfClass: [NSData class]]) {
result = [NSString stringWithUTF8String: [value bytes]];
} else if (value && [value isKindOfClass: [NSString class]]) {
result = value;
}
}
[cm releaseChannel: channel];
@@ -145,7 +155,7 @@ static EOAttribute *textColumn = nil;
tableURL];
}
return value;
return result;
}
- (NSString *) generateSQLForInsert: (NSString *) jsonRepresentation
@@ -239,4 +249,48 @@ static EOAttribute *textColumn = nil;
return rc;
}
- (unsigned long long) getCDefaultsSize {
unsigned long long r;
NSString *sql;
NSException *ex;
GCSChannelManager *cm;
EOAdaptorChannel *channel;
EOAdaptorContext *context;
NSArray *attrs;
NSDictionary *infos;
SOGoCache *cache;
NSNumberFormatter *formatter;
r = 65535;
cache = [SOGoCache sharedCache];
if ([cache valueForKey:kCDefaultsLenKey]) {
formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
r = [[formatter numberFromString: [cache valueForKey:kCDefaultsLenKey]] unsignedLongLongValue];
[formatter release];
} else {
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: tableURL];
sql = [NSString stringWithFormat: @"select character_octet_length as CHARACTER_MAXIMUM_LENGTH from information_schema.columns where table_name = '%@' AND column_name = 'c_defaults'", [tableURL gcsTableName]];
ex = [channel evaluateExpressionX: sql];
if (!ex) {
attrs = [channel describeResults: NO];
infos = [channel fetchAttributes: attrs withZone: NULL];
[cm releaseChannel: channel immediately: YES];
if (infos && [infos objectForKey:@"CHARACTER_MAXIMUM_LENGTH"]) {
r = [[infos objectForKey:@"CHARACTER_MAXIMUM_LENGTH"] longLongValue];
} else if (infos && [infos objectForKey:@"character_maximum_length"]) { // PGSQL case
r = [[infos objectForKey:@"character_maximum_length"] longLongValue];
}
}
[cache setValue: [[NSNumber numberWithUnsignedLongLong: r] stringValue] forKey: kCDefaultsLenKey];
}
return r;
}
@end
+49 -2
View File
@@ -206,7 +206,7 @@ static NSString *sieveScriptName = @"sogo";
@"imapflags", @"removeflag",
@"imapflags", @"flag",
@"vacation", @"vacation",
@"notify", @"notify",
@"notify", @"enotify",
@"fileinto", @"fileinto",
@"reject", @"reject",
@"regex", @"regex",
@@ -543,6 +543,12 @@ static NSString *sieveScriptName = @"sogo";
else if ([method isEqualToString: @"redirect"])
sieveAction = [NSString stringWithFormat: @"%@ %@",
method, [argument asSieveQuotedString]];
else if ([method isEqualToString: @"notify"])
{
argument = [NSString stringWithFormat: @"mailto:%@", argument];
sieveAction = [NSString stringWithFormat: @"%@ %@",
method, [argument asSieveQuotedString]];
}
else if ([method isEqualToString: @"reject"])
sieveAction = [NSString stringWithFormat: @"%@ %@",
method, [argument asSieveQuotedString]];
@@ -863,7 +869,7 @@ static NSString *sieveScriptName = @"sogo";
error = nil;
dd = [user domainDefaults];
if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled] || [dd notificationEnabled]))
return error;
req = [NSMutableArray arrayWithCapacity: 15];
@@ -1207,6 +1213,47 @@ static NSString *sieveScriptName = @"sogo";
}
}
// We handle mail notification
values = [ud notificationOptions];
if (values && [[values objectForKey: @"enabled"] boolValue])
{
// BOOL alwaysSend;
NSString *notify;
NSString *message, *notificationTranslated;
id addresses;
int i;
// alwaysSend = [[values objectForKey: @"alwaysSend"] boolValue];
b = YES;
[req addObjectUniquely: @"enotify"];
[req addObjectUniquely: @"variables"];
addresses = [values objectForKey: @"notificationAddress"];
if ([addresses isKindOfClass: [NSString class]])
addresses = [addresses componentsSeparatedByString: @","];
message = [values objectForKey: @"notificationMessage"];
notificationTranslated = [values objectForKey: @"notificationTranslated"];
for (i = 0; i < [addresses count]; i++)
{
v = [addresses objectAtIndex: i];
if (v && [v length] > 0)
{
notify = @"if header :matches \"subject\" \"*\" {\r\n set \"subject\" \"${1}\";\r\n}\r\n";
notify = [notify stringByAppendingFormat: @"set :encodeurl \"body_param\" \"%@\";\r\n", message];
notify = [notify stringByAppendingFormat: @"notify :message \"%@: ${subject}\" \"mailto:%@?body=${body_param}\";\r\n", notificationTranslated, v];
// if (alwaysSend)
// [script insertString: notify atIndex: 0];
// else
[script appendString: notify];
}
}
}
// We handle header/footer Sieve scripts
if ((v = [dd sieveScriptHeaderTemplateFile]))
{
+5
View File
@@ -75,6 +75,11 @@
- (NSArray *) fetchContactsMatching: (NSString *) filter
withCriteria: (NSArray *) criteria
inDomain: (NSString *) domain;
- (NSArray *)fetchContactsMatching:(NSString *)filter
withCriteria:(NSArray *)criteria
inDomain:(NSString *)domain
limit:(int)limit;
- (NSArray *) lookupContactsWithQualifier: (EOQualifier *) qualifier
andSortOrdering: (EOSortOrdering *) ordering
inDomain: (NSString *) domain;
+10
View File
@@ -78,6 +78,8 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
- (NSString *) pageTitle;
- (NSString *) helpURL;
NSComparisonResult languageSort(id el1, id el2, void *context);
- (NSArray *) supportedLanguages;
- (NSString *) loginSuffix;
@@ -128,6 +130,14 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
- (NSString *) JWTSecret;
- (NSArray *) disableSharing;
- (NSArray *) disableSharingAnyAuthUser;
- (NSArray *) disableExport;
- (BOOL) enableGlobalAddressBookFirstEntries;
- (int) globalAddressBookFirstEntriesCount;
- (BOOL)isURLEncryptionEnabled;
- (NSString *)urlEncryptionPassphrase;
@end
+68
View File
@@ -467,6 +467,16 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return [self stringForKey: @"SOGoHelpURL"];
}
NSComparisonResult languageSort(id el1, id el2, void *context)
{
NSString *t1, *t2;
t1 = [context labelForKey: el1];
t2 = [context labelForKey: el2];
return [t1 compare: t2 options: NSCaseInsensitiveSearch];
}
- (NSArray *) supportedLanguages
{
static NSArray *supportedLanguages = nil;
@@ -809,5 +819,63 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return disableSharing;
}
- (BOOL)isURLEncryptionEnabled {
return [self boolForKey: @"SOGoURLEncryptionEnabled"];
}
- (NSString *) urlEncryptionPassphrase
{
NSString *passphrase;
passphrase = [self stringForKey: @"SOGoURLEncryptionPassphrase"];
if (!passphrase)
passphrase = @"SOGoSuperSecret0"; // Default passphrase
return passphrase;
}
- (NSArray *) disableSharingAnyAuthUser
{
static NSArray *disableSharingAnyAuthUser = nil;
if (!disableSharingAnyAuthUser)
{
disableSharingAnyAuthUser = [self stringArrayForKey: @"SOGoDisableSharingAnyAuthUser"];
[disableSharingAnyAuthUser retain];
}
return disableSharingAnyAuthUser;
}
- (NSArray *) disableExport
{
static NSArray *disableExport = nil;
if (!disableExport)
{
disableExport = [self stringArrayForKey: @"SOGoDisableExport"];
[disableExport retain];
}
return disableExport;
}
- (BOOL) globalAddressBookFirstEntriesEnabled
{
return [self boolForKey: @"SOGoGlobalAddressBookFirstEntries"];
}
- (int) globalAddressBookFirstEntriesCount
{
int v;
v = [self integerForKey: @"SOGoGlobalAddressBookFirstEntriesCount"];
if (!v)
v = 100;
return v;
}
@end
+4
View File
@@ -135,6 +135,10 @@
- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context;
- (SOGoContactFolder *) personalContactsFolderInContext: (WOContext *) context;
/* Encryption */
+ (NSString *)getEncryptedUsernameIfNeeded:(NSString *)username withContext:(WOContext *)context;
+ (NSString *)getDecryptedUsernameIfNeeded:(NSString *)username withContext:(WOContext *)context;
@end
#endif /* __SOGoUser_H__ */
+85 -1
View File
@@ -39,6 +39,7 @@
#import "SOGoUserManager.h"
#import "SOGoUserSettings.h"
#import "WOResourceManager+SOGo.h"
#import "NSString+Crypto.h"
#if defined(MFA_CONFIG)
#include <liboath/oath.h>
@@ -46,6 +47,8 @@
#import "SOGoUser.h"
static const NSString *kEncryptedUserNamePrefix = @"uenc";
@implementation SoUser (SOGoExtension)
- (SOGoDomainDefaults *) userDefaults
@@ -833,6 +836,7 @@
{
// This additional identity should not appear in the identity manager of the Preferences
[identity setObject: [NSNumber numberWithBool: YES] forKey: @"isReadOnly"];
[identity setObject: [NSNumber numberWithBool: NO] forKey: @"isDefault"];
}
[identities addObject: identity];
}
@@ -1059,7 +1063,7 @@
- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context
{
return [[self calendarsFolderInContext: context] lookupPersonalFolder: [[context request] isMacOSXVenturaCalendarApp] ? @"PERSONAL" : @"personal"
return [[self calendarsFolderInContext: context] lookupPersonalFolder: @"personal"
ignoringRights: YES];
}
@@ -1196,4 +1200,84 @@
return [accessValue boolValue];
}
/* Encryption */
+ (NSString *) getEncryptedUsernameIfNeeded:(NSString *)username withContext:(WOContext *)context
{
NSException *exception;
NSString *tmp, *cacheKey;
SOGoCache *cache;
WORequest *request;
if (![[SOGoSystemDefaults sharedSystemDefaults] isURLEncryptionEnabled])
return username;
request = [context request];
cache = [SOGoCache sharedCache];
cacheKey = [NSString stringWithFormat: @"%@_%@_%@", kEncryptedUserNamePrefix, username, [request requestHandlerKey]];
exception = nil;
tmp = nil;
tmp = [cache valueForKey: cacheKey];
if (tmp) {
return tmp;
} else {
if (username && [username length] > 0) {
tmp = [username encodeAES128ECBBase64: [[SOGoSystemDefaults sharedSystemDefaults] urlEncryptionPassphrase] encodedURL:YES exception: &exception];
if (!exception) {
[cache setValue:tmp forKey:cacheKey];
return tmp;
} else {
[self errorWithFormat: @"URL Encryption error : %@", [exception reason]];
return username;
}
} else {
[self logWithFormat: @"Empty username for encryption"];
return username;
}
}
}
+ (NSString *) getDecryptedUsernameIfNeeded:(NSString *)username withContext:(WOContext *)context
{
NSException *exception;
NSString *tmp, *cacheKey;
SOGoCache *cache;
WORequest *request;
if (![[SOGoSystemDefaults sharedSystemDefaults] isURLEncryptionEnabled])
return username;
request = [context request];
cache = [SOGoCache sharedCache];
cacheKey = [NSString stringWithFormat: @"%@_%@_%@", kEncryptedUserNamePrefix, username, [request requestHandlerKey]];
exception = nil;
tmp = nil;
tmp = [cache valueForKey: cacheKey];
if (tmp) {
return tmp;
} else {
if (username && [username length] > 0) {
tmp = [username decodeAES128ECBBase64: [[SOGoSystemDefaults sharedSystemDefaults] urlEncryptionPassphrase] encodedURL:YES exception: &exception];
if (tmp) {
[cache setValue:tmp forKey:cacheKey];
} else {
[cache setValue:username forKey:cacheKey];
}
if (!exception) {
return tmp;
} else {
[self errorWithFormat: @"URL Decryption error : %@", [exception reason]];
return username;
}
} else {
[self errorWithFormat: @"Empty username for decryption"];
return username;
}
}
}
@end /* SOGoUser */
+17 -1
View File
@@ -156,6 +156,9 @@ extern NSString *SOGoPasswordRecoverySecondaryEmail;
- (void) setMailComposeFontSize: (int) newValue;
- (int) mailComposeFontSize;
- (void) setMailDisplayFullEmail: (BOOL *) newValue;
- (BOOL *) mailDisplayFullEmail;
- (void) setMailDisplayRemoteInlineImages: (NSString *) newValue;
- (NSString *) mailDisplayRemoteInlineImages;
@@ -175,6 +178,13 @@ extern NSString *SOGoPasswordRecoverySecondaryEmail;
- (void) setMailSignaturePlacement: (NSString *) newValue;
- (NSString *) mailSignaturePlacement;
- (BOOL)mailUseSignatureOnNew;
- (void)setMailUseSignatureOnNew:(BOOL)newValue;
- (BOOL)mailUseSignatureOnReply;
- (void)setMailUseSignatureOnReply:(BOOL)newValue;
- (BOOL)mailUseSignatureOnForward;
- (void)setMailUseSignatureOnForward:(BOOL)newValue;
- (void) setAllowUserReceipt: (BOOL) allow;
- (BOOL) allowUserReceipt;
- (void) setUserReceiptNonRecipientAction: (NSString *) action;
@@ -215,11 +225,17 @@ extern NSString *SOGoPasswordRecoverySecondaryEmail;
- (void) setForwardOptions: (NSDictionary *) newValue;
- (NSDictionary *) forwardOptions;
- (void) setNotificationOptions: (NSDictionary *) newValue;
- (NSDictionary *) notificationOptions;
- (void) setMailLabelsColors: (NSDictionary *) newValues;
- (NSDictionary *) mailLabelsColors;
- (void)setHideInlineAttachments:(BOOL)newValue;
- (BOOL)hideInlineAttachments;
/* calendar */
- (void) setCalendarCategories: (NSArray *) newValues;
- (void)setCalendarCategories : (NSArray *)newValues;
- (NSArray *) calendarCategories;
- (void) setCalendarCategoriesColors: (NSDictionary *) newValues;
+63 -1
View File
@@ -86,6 +86,8 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
[ud setObject: @"text" forKey: @"SOGoMailComposeMessageType"];
}
[ud setObject: [NSNumber numberWithUnsignedLongLong: [up getCDefaultsSize]] forKey: @"CDefaultsSize"]; // Add c_defaults field size
return ud;
}
@@ -629,6 +631,16 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
return [self integerForKey: @"SOGoMailComposeFontSize"];
}
- (void) setMailDisplayFullEmail: (BOOL *) newValue
{
[self setBool: newValue forKey: @"SOGoMailDisplayFullEmail"];
}
- (BOOL *) mailDisplayFullEmail;
{
return [self boolForKey: @"SOGoMailDisplayFullEmail"];
}
- (void) setMailDisplayRemoteInlineImages: (NSString *) newValue
{
[self setObject: newValue forKey: @"SOGoMailDisplayRemoteInlineImages"];
@@ -705,6 +717,36 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
return signaturePlacement;
}
- (BOOL) mailUseSignatureOnNew
{
return [self boolForKey: @"SOGoMailUseSignatureOnNew"];
}
- (void) setMailUseSignatureOnNew: (BOOL) newValue
{
[self setBool: newValue forKey: @"SOGoMailUseSignatureOnNew"];
}
- (BOOL) mailUseSignatureOnReply
{
return [self boolForKey: @"SOGoMailUseSignatureOnReply"];
}
- (void) setMailUseSignatureOnReply: (BOOL) newValue
{
[self setBool: newValue forKey: @"SOGoMailUseSignatureOnReply"];
}
- (BOOL) mailUseSignatureOnForward
{
return [self boolForKey: @"SOGoMailUseSignatureOnForward"];
}
- (void) setMailUseSignatureOnForward: (BOOL) newValue
{
[self setBool: newValue forKey: @"SOGoMailUseSignatureOnForward"];
}
- (void) setAllowUserReceipt: (BOOL) allow
{
[self setBool: allow forKey: @"SOGoMailReceiptAllow"];
@@ -804,7 +846,7 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
// Remove possible XSS injection
mailIdentities = [NSMutableArray arrayWithArray: [self arrayForKey: @"SOGoMailIdentities"]];
for (i = 0 ; i < [mailIdentities length] ; i++) {
for (i = 0 ; i < [mailIdentities count] ; i++) {
mailIdentity = [NSMutableDictionary dictionaryWithDictionary: [mailIdentities objectAtIndex: i]];
if (mailIdentity && [mailIdentity objectForKey: @"fullName"]) {
fullName = [NSString stringWithString: [mailIdentity objectForKey: @"fullName"]];
@@ -927,6 +969,16 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
return [self objectForKey: @"SOGoMailLabelsColors"];
}
- (void) setHideInlineAttachments: (BOOL) newValue
{
[self setBool: newValue forKey: @"SOGoMailHideInlineAttachments"];
}
- (BOOL) hideInlineAttachments
{
return [self boolForKey: @"SOGoMailHideInlineAttachments"];
}
- (void) setSieveFilters: (NSArray *) newValue
{
[self setObject: newValue forKey: @"SOGoSieveFilters"];
@@ -957,6 +1009,16 @@ NSString *SOGoPasswordRecoverySecondaryEmail = @"SecondaryEmail";
return [self dictionaryForKey: @"Forward"];
}
- (void) setNotificationOptions: (NSDictionary *) newValue
{
[self setObject: newValue forKey: @"Notification"];
}
- (NSDictionary *) notificationOptions
{
return [self dictionaryForKey: @"Notification"];
}
- (void) setContactsCategories: (NSArray *) newValues
{
[self setObject: newValues forKey: @"SOGoContactsCategories"];
+13
View File
@@ -24,6 +24,8 @@
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest+So.h>
#import <SaxObjC/XMLNamespaces.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <DOM/DOMNode.h>
@@ -36,6 +38,7 @@
#import "NSArray+Utilities.h"
#import "NSDictionary+Utilities.h"
#import "NSString+Utilities.h"
#import "NSString+Crypto.h"
#import "SOGoUserManager.h"
#import "SOGoPermissions.h"
#import "SOGoSystemDefaults.h"
@@ -83,6 +86,16 @@
return children;
}
- (NSString *) nameInContainer {
return [SOGoUser getEncryptedUsernameIfNeeded: [super nameInContainer] withContext: context];
}
- (NSString *) davURLAsString
{
return [[container davURLAsString]
stringByAppendingFormat: @"%@/", [self nameInContainer]];
}
/* ownership */
- (NSString *) ownerInContext: (WOContext *) _ctx
+3
View File
@@ -26,6 +26,7 @@
@class NSArray;
@class NSMutableDictionary;
@class NSString;
@class NSNumber;
@interface SOGoUserSettings : SOGoDefaultsSource
@@ -35,6 +36,8 @@
- (NSArray *) subscribedAddressBooks;
- (NSString *) userPrivateSalt;
- (NSString *) userPublicSalt;
- (void)enableForceResetPassword;
- (void)disableForceResetPassword;
@end
+15
View File
@@ -116,4 +116,19 @@ static Class SOGoUserProfileKlass = Nil;
return salt;
}
- (void) enableForceResetPassword
{
[self setObject: [NSNumber numberWithInt:1] forKey: @"ForceResetPassword"];
[self synchronize];
}
- (void) disableForceResetPassword
{
if ([self objectForKey: @"ForceResetPassword"]) {
[self removeObjectForKey: @"ForceResetPassword"];
[self synchronize];
}
}
@end
+18
View File
@@ -924,6 +924,22 @@
- (NSArray *) fetchContactsMatching: (NSString *) filter
withCriteria: (NSArray *) criteria
inDomain: (NSString *) domain
{
if ([filter length] > 0) {
return [self fetchContactsMatching: (NSString *) filter
withCriteria: (NSArray *) criteria
inDomain: (NSString *) domain
limit: -1];
} else {
return [NSMutableArray array];
}
}
- (NSArray *)fetchContactsMatching: (NSString *)filter
withCriteria: (NSArray *)criteria
inDomain: (NSString *)domain
limit: (int)limit
{
EOAdaptorChannel *channel;
NSEnumerator *criteriaList;
@@ -994,6 +1010,8 @@
[sql appendFormat: @" AND %@ IS NULL", _domainField];
}
if (limit > 0)
[sql appendFormat: @" ORDER BY c_cn ASC LIMIT %i", limit];
ex = [channel evaluateExpressionX: sql];
if (!ex)
{
+3 -2
View File
@@ -33,9 +33,10 @@
- (BOOL) isICal;
- (BOOL) isICal4;
- (BOOL) isMacOSXAddressBookApp;
- (BOOL)isMacOSXCalendarApp;
- (BOOL)isIPhoneAddressBookApp;
- (BOOL) isMacOSXCalendarApp;
- (BOOL) isIPhoneAddressBookApp;
- (BOOL) isAndroid;
- (BOOL) isMacOSXCalendar;
@end
+10 -2
View File
@@ -242,7 +242,7 @@
return ([[cc userAgent] rangeOfString: @"Android"].location != NSNotFound);
}
- (BOOL) isMacOSXVenturaCalendarApp
- (BOOL) isMacOSXCalendar
{
WEClientCapabilities *cc;
BOOL b;
@@ -251,7 +251,8 @@
b = (
(
nil != [cc userAgent] && [[cc userAgent] rangeOfString: @"macOS/13"].location != NSNotFound
nil != [cc userAgent]
&& [[cc userAgent] rangeOfString: @"macOS"].location != NSNotFound
&& [[cc userAgent] rangeOfString: @"dataaccessd"].location != NSNotFound
)
);
@@ -259,4 +260,11 @@
return b;
}
- (BOOL) isThunderbird
{
BOOL ret;
ret = ([[self headerForKey:@"user-agent"] rangeOfString: @"Thunderbird"].location != NSNotFound);
return ret;
}
@end
+7
View File
@@ -105,10 +105,17 @@
testEquals([[NSString stringWithString:@"<img vbscript:test"] stringWithoutHTMLInjection: NO], @"<img test");
testEquals([[NSString stringWithString:@"<img javascript:test"] stringWithoutHTMLInjection: NO], @"<img test");
testEquals([[NSString stringWithString:@"<img livescript:test"] stringWithoutHTMLInjection: NO], @"<img test");
testEquals([[NSString stringWithString:@"foobar <form action=\"\">bar</form>"] stringWithoutHTMLInjection: NO], @"foobar <for* action=\"\">bar</for*>");
testEquals([[NSString stringWithString:@"foobar <iframe src=\"\">bar</iframe>"] stringWithoutHTMLInjection: NO], @"foobar <ifr*** src=\"\">bar</iframe>");
testEquals([[NSString stringWithString:@"foobar <img onload=foo bar"] stringWithoutHTMLInjection: NO], @"foobar <img onl***=foo bar");
testEquals([[NSString stringWithString:@"foobar <img onmouseover=foo bar"] stringWithoutHTMLInjection: NO], @"foobar <img onmouseo***=foo bar");
}
- (void) test_stringCleanInvalidHTMLTags
{
testEquals([[NSString stringWithString:@"<div>Test<!--></div>"] cleanInvalidHTMLTags], @"<div>Test</div>");
testEquals([[NSString stringWithString:@"<div><!--[if !mso]><span>Test</span><!--<![endif]--></div>"] cleanInvalidHTMLTags], @"<div><!--[if !mso]><span>Test</span><!--[endif]--></div>");
}
@end
+1
View File
@@ -45,6 +45,7 @@ class ManageSieve {
base64 = buff.toString('base64')
response = await this.connection.send(`AUTHENTICATE "PLAIN" {${base64.length}+}\r\n${base64}`, { waitfor: /\b(OK|NO)/ })
// console.debug(`ManageSieve.authenticate => ${response}`)
// console.log(`AUTHENTICATE "PLAIN" {${base64.length}+}\r\n${base64}`)
parsedResponse = this.parseResponse(response)
if (!Object.keys(parsedResponse).includes('OK')) {
throw new Error(`Authentication failed: ${parsedResponse['NO']}`)
+18 -35
View File
@@ -1,45 +1,28 @@
export default {
// setup: 4 user are needed: username, superuser, attendee1, attendee1_delegate
// superuser must be a sogo superuser...
hostname: "localhost",
port: "80",
username: "myuser",
password: "mypass",
superuser: "super",
superuser_password: "pass",
// 'subscriber_username' and 'attendee1' must be the same user
subscriber_username: "otheruser",
subscriber_password: "otherpass",
attendee1: "user@domain.com",
attendee1_username: "user",
attendee1_password: "pass",
attendee1_delegate: "user2@domain.com",
attendee1_delegate_username: "sogo2",
hostname: "127.0.0.1",
port: "50001",
username: "sogo-tests1",
password: "sogo",
superuser: "sogo-tests-super",
superuser_password: "sogo",
subscriber_username: "sogo-tests2",
subscriber_password: "sogo",
attendee1: "sogo-tests2@example.com",
attendee1_username: "sogo-tests2",
attendee1_password: "sogo",
attendee1_delegate: "sogo-tests3@example.com",
attendee1_delegate_username: "sogo-tests3",
attendee1_delegate_password: "sogo",
resource_no_overbook: "res",
resource_can_overbook: "res-nolimit",
// must match attendee1
white_listed_attendee: {
"sogo1": "John Doe <sogo1@example.com>"
"sogo-tests1": "John Doe <sogo-tests1@example.com>"
},
mailserver: "imaphost",
mailserver: "127.0.0.1",
testput_nbrdays: 30,
sieve_server: "localhost",
sieve_server: "127.0.0.1",
sieve_port: 4190,
sogo_user: "sogo",
sogo_tool_path: "/usr/local/sbin/sogo-tool",
webCalendarURL: "http://inverse.ca/sogo-integration-tests/CanadaHolidays.ics"
sogo_tool_path: "/usr/sbin/sogo-tool",
webCalendarURL: "http://127.0.0.1/sogo-integration-tests/CanadaHolidays.ics"
}
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
Loaded 100 of 550 files, more files were not shown because too many files have changed in this diff. Show more