Compare commits
276
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f96905c3a | ||
|
|
10d789fb1f | ||
|
|
d505b874f7 | ||
|
|
cbeaa4cc11 | ||
|
|
427f7f4b4f | ||
|
|
d68bbc836c | ||
|
|
51b6c0d9a3 | ||
|
|
5f2e4889f3 | ||
|
|
37379ba416 | ||
|
|
17eca6f96a | ||
|
|
26c7462eb0 | ||
|
|
0d471efe97 | ||
|
|
8375c9352e | ||
|
|
f85c53190c | ||
|
|
3cd860838c | ||
|
|
377cff935f | ||
|
|
06a6ed7768 | ||
|
|
0e50d37531 | ||
|
|
8d1fc1e306 | ||
|
|
10369aa654 | ||
|
|
057a1ba105 | ||
|
|
527fded19c | ||
|
|
ca21bf4ce1 | ||
|
|
cd7e06c2ed | ||
|
|
9126cae27d | ||
|
|
ed444fdc22 | ||
|
|
3809ec3904 | ||
|
|
d9c716c1b6 | ||
|
|
e4f07c466e | ||
|
|
6d270fe7a4 | ||
|
|
3d5f352111 | ||
|
|
fe7369e427 | ||
|
|
d6c9ecdeb0 | ||
|
|
a9ecaed5fa | ||
|
|
21741954dd | ||
|
|
41fb85e6a3 | ||
|
|
53036b323f | ||
|
|
a16d29b70b | ||
|
|
2de6241220 | ||
|
|
0b22fa6cd5 | ||
|
|
694c21de99 | ||
|
|
5aa7ebf2ae | ||
|
|
dba2332864 | ||
|
|
402c3d0a1f | ||
|
|
76911fd445 | ||
|
|
029a82143f | ||
|
|
715d25d40a | ||
|
|
fc2a82c3b0 | ||
|
|
cf87022348 | ||
|
|
3040543c79 | ||
|
|
3815bc1c5e | ||
|
|
2afbcbeaed | ||
|
|
33f3a1b716 | ||
|
|
28d883dac1 | ||
|
|
c75f210864 | ||
|
|
cfab85624b | ||
|
|
6e9e7f98ef | ||
|
|
8a4bc2a716 | ||
|
|
fd5e28ec77 | ||
|
|
1961f1a914 | ||
|
|
0704949056 | ||
|
|
85870d6b16 | ||
|
|
669dcaa242 | ||
|
|
21571ab1e0 | ||
|
|
7d66699219 | ||
|
|
cd50ee0176 | ||
|
|
da24c0d0a7 | ||
|
|
7b15327b19 | ||
|
|
6eae3f2b16 | ||
|
|
3cd9910638 | ||
|
|
fb542e6e26 | ||
|
|
60e6bb5b3b | ||
|
|
86c9b4ec07 | ||
|
|
2e670f7e62 | ||
|
|
8babb23c58 | ||
|
|
1ae02ebd70 | ||
|
|
aa53524751 | ||
|
|
6734b4ea16 | ||
|
|
bb943e414d | ||
|
|
ca2c5c188e | ||
|
|
0023a9ce3c | ||
|
|
1e8127b461 | ||
|
|
6b08f2e4de | ||
|
|
605d8b1169 | ||
|
|
c872fb4d9c | ||
|
|
11ad203388 | ||
|
|
0b1f2b0405 | ||
|
|
0560efd1f6 | ||
|
|
d1634054d6 | ||
|
|
7481ccf370 | ||
|
|
65374e44fc | ||
|
|
f8b7986501 | ||
|
|
8da3d06117 | ||
|
|
d0b4b50456 | ||
|
|
e0ef59d5f2 | ||
|
|
6e60b10755 | ||
|
|
0df0e34073 | ||
|
|
f41ab2ed0c | ||
|
|
abce5f42cf | ||
|
|
277b73c663 | ||
|
|
f35a37bbdd | ||
|
|
9c0948518b | ||
|
|
4fe01f96d7 | ||
|
|
a273f7aca5 | ||
|
|
8f635d4856 | ||
|
|
068126cd22 | ||
|
|
9ef49bb56f | ||
|
|
54a1f922f9 | ||
|
|
65f8336255 | ||
|
|
c67ad819ab | ||
|
|
64a6319635 | ||
|
|
31c6f78913 | ||
|
|
4b45dc7584 | ||
|
|
063fb2be3d | ||
|
|
ee9899fe3d | ||
|
|
81f2d88db3 | ||
|
|
baf6bdfedf | ||
|
|
c569ed2d77 | ||
|
|
87e1240ace | ||
|
|
b396a277f0 | ||
|
|
e460e5377c | ||
|
|
1d3da92bab | ||
|
|
b1d8475520 | ||
|
|
d38cc86d29 | ||
|
|
df2f73f1f6 | ||
|
|
19915e3dff | ||
|
|
6d16ee7988 | ||
|
|
5b73a60aa6 | ||
|
|
487ad1a849 | ||
|
|
0e2deac164 | ||
|
|
5ee12a4507 | ||
|
|
dfc1ab7e0e | ||
|
|
d5c1b742f4 | ||
|
|
ea8f04ac7c | ||
|
|
64865aa85a | ||
|
|
b08b5dd79a | ||
|
|
8a10213b75 | ||
|
|
1710112c4e | ||
|
|
2242c21323 | ||
|
|
da609c448e | ||
|
|
9aebf9bf02 | ||
|
|
46d5779088 | ||
|
|
92d38c108e | ||
|
|
02d32d5f6c | ||
|
|
0368bad3c5 | ||
|
|
865aba3248 | ||
|
|
bbb09555f0 | ||
|
|
493f5f625f | ||
|
|
ed5199aee7 | ||
|
|
2fee2f8782 | ||
|
|
e50b66b62e | ||
|
|
ee3ed0779c | ||
|
|
e3d392b12a | ||
|
|
41842e2d39 | ||
|
|
86e5872dc1 | ||
|
|
7ff0699648 | ||
|
|
03d43079d3 | ||
|
|
9ef58e7c6d | ||
|
|
1a5408c2b3 | ||
|
|
b1df5d4d14 | ||
|
|
e330c552c3 | ||
|
|
300cfe4e27 | ||
|
|
c82a48f96f | ||
|
|
6606f9865a | ||
|
|
ce6efafe54 | ||
|
|
fc93272d70 | ||
|
|
6293f62d83 | ||
|
|
132912b566 | ||
|
|
97d01fb14e | ||
|
|
4974648c33 | ||
|
|
0f0ecf71c3 | ||
|
|
ff263b99fe | ||
|
|
8604389152 | ||
|
|
bba1da1b2b | ||
|
|
bafb1ffc69 | ||
|
|
5534d4b6ee | ||
|
|
8efdee4ef9 | ||
|
|
320938445c | ||
|
|
dc23721b75 | ||
|
|
4abf12b4bb | ||
|
|
1a257c7768 | ||
|
|
a1528bcc03 | ||
|
|
041bb98e9d | ||
|
|
f2df405616 | ||
|
|
68b1c1e5df | ||
|
|
f0719ee409 | ||
|
|
87b89ed094 | ||
|
|
402e54a6fc | ||
|
|
16af360006 | ||
|
|
27c9dd36e5 | ||
|
|
3309346b46 | ||
|
|
d133961302 | ||
|
|
6b647050fc | ||
|
|
acaddf5d4f | ||
|
|
82b69d1414 | ||
|
|
4f7c73143f | ||
|
|
1c93796ae1 | ||
|
|
fce02db7d1 | ||
|
|
c5814d60a4 | ||
|
|
f3ddc867cf | ||
|
|
5e471c5d66 | ||
|
|
a1a07ddcee | ||
|
|
cab5bc011e | ||
|
|
4d2c77195e | ||
|
|
e959583390 | ||
|
|
82178eeba9 | ||
|
|
382b8a0a1a | ||
|
|
f3981c4276 | ||
|
|
f5f74f751a | ||
|
|
528d178256 | ||
|
|
f3dce5d50b | ||
|
|
b7e529dd49 | ||
|
|
68cef9ae03 | ||
|
|
87bbba0c8d | ||
|
|
e0d76fba65 | ||
|
|
5b352635b8 | ||
|
|
8582e423bd | ||
|
|
577d991d42 | ||
|
|
5d315ac180 | ||
|
|
326bc68f4c | ||
|
|
ec27f7e714 | ||
|
|
e731303439 | ||
|
|
0cb896d4fe | ||
|
|
ba4c7a5300 | ||
|
|
40512f6b06 | ||
|
|
d1a51e8340 | ||
|
|
bd8b49d2c9 | ||
|
|
57ba5397e5 | ||
|
|
d55030323b | ||
|
|
9918f75580 | ||
|
|
7600e9e179 | ||
|
|
a48440754c | ||
|
|
e53e5493c6 | ||
|
|
6eee167876 | ||
|
|
8e4d801df0 | ||
|
|
b666137a70 | ||
|
|
a95f6c0799 | ||
|
|
09d03261af | ||
|
|
8a8dcae029 | ||
|
|
fccc910791 | ||
|
|
e42f28cf22 | ||
|
|
eb42e1787c | ||
|
|
8baa770681 | ||
|
|
af59e57fc0 | ||
|
|
22372a7598 | ||
|
|
117540dbb1 | ||
|
|
665861d830 | ||
|
|
f9fdad251d | ||
|
|
f22672cef8 | ||
|
|
fa5264e822 | ||
|
|
b68a215ea2 | ||
|
|
e87d511e79 | ||
|
|
854e83c22c | ||
|
|
24200e7d34 | ||
|
|
9da6521cfb | ||
|
|
c64fabd09b | ||
|
|
a44e96755a | ||
|
|
1a06996381 | ||
|
|
a7ecb5816d | ||
|
|
e3a7acd329 | ||
|
|
23da4cca7b | ||
|
|
ab954a150e | ||
|
|
7b13006390 | ||
|
|
139ef15284 | ||
|
|
d436afb054 | ||
|
|
e076d0db73 | ||
|
|
5c4d467d06 | ||
|
|
6050c4ee61 | ||
|
|
9ab59e7420 | ||
|
|
572abfb589 | ||
|
|
32a04998e5 | ||
|
|
158a5becc9 | ||
|
|
9c7ef089c2 | ||
|
|
1df2586745 | ||
|
|
807622dd53 | ||
|
|
650ca16ff7 |
No files matched your search
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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)
|
||||
|
||||
Executable
+26
@@ -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";
|
||||
@@ -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 */
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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ą";
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
|
||||
- (BOOL) hasPhoto
|
||||
{
|
||||
return ([ldifEntry objectForKey: @"photo"] != nil);
|
||||
return ([ldifEntry objectForKey: @"photo"] != nil && [ldifEntry objectForKey: @"photo"] != [NSNull null]);
|
||||
}
|
||||
|
||||
- (NSString *) davEntityTag
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ? @" " : @" ");
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */];
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
@interface SOGoMailFrenchForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
@interface SOGoMailGalicianForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
@interface SOGoMailGermanForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
|
||||
@@ -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 ? @" " : @" ");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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]; */
|
||||
|
||||
@@ -97,6 +97,7 @@ extern NSArray *SOGoMailCoreInfoKeys;
|
||||
- (NSString *) cc;
|
||||
- (NSString *) from;
|
||||
- (NSString *) inReplyTo;
|
||||
- (NSString *) references;
|
||||
- (NSString *) messageId;
|
||||
- (NSString *) received;
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
@interface SOGoMailFrenchReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
@interface SOGoMailGalicianReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
@interface SOGoMailGermanReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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	

\\\\0]*f[\\s\\u200B	

\\\\0]*o[\\s\\u200B	

\\\\0]*r[\\s\\u200B	

\\\\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	

\\\\0]*/[\\s\\u200B	

\\\\0]*f[\\s\\u200B	

\\\\0]*o[\\s\\u200B	

\\\\0]*r[\\s\\u200B	

\\\\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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
//
|
||||
// CAS support
|
||||
//
|
||||
- (NSString *) sha512HashTicket: (NSString* ) ticket;
|
||||
- (NSString *) CASTicketFromIdentifier: (NSString *) identifier;
|
||||
- (NSString *) CASSessionWithTicket: (NSString *) ticket;
|
||||
- (void) setCASSession: (NSString *) casSession
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
@interface SOGoDAVAuthenticator : SoHTTPAuthenticator <SOGoAuthenticator>
|
||||
|
||||
WOContext *context;
|
||||
|
||||
+ (id) sharedSOGoDAVAuthenticator;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
- (BOOL) forwardEnabled;
|
||||
- (int) forwardConstraints;
|
||||
- (NSArray *) forwardConstraintsDomains;
|
||||
- (BOOL) notificationEnabled;
|
||||
- (BOOL) vacationEnabled;
|
||||
- (BOOL) vacationPeriodEnabled;
|
||||
- (NSString *) vacationDefaultSubject;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
NSString *fieldName;
|
||||
}
|
||||
|
||||
- (unsigned long long)getCDefaultsSize;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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]))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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__ */
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -33,9 +33,10 @@
|
||||
- (BOOL) isICal;
|
||||
- (BOOL) isICal4;
|
||||
- (BOOL) isMacOSXAddressBookApp;
|
||||
- (BOOL)isMacOSXCalendarApp;
|
||||
- (BOOL)isIPhoneAddressBookApp;
|
||||
- (BOOL) isMacOSXCalendarApp;
|
||||
- (BOOL) isIPhoneAddressBookApp;
|
||||
- (BOOL) isAndroid;
|
||||
- (BOOL) isMacOSXCalendar;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
Reference in new issue
Block a user