Compare commits
163
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56598d1e37 | ||
|
|
4ee6d5af9d | ||
|
|
594b0249c3 | ||
|
|
64f17880c7 | ||
|
|
59d48cb382 | ||
|
|
2da4aa8cb8 | ||
|
|
1c20c1ead3 | ||
|
|
a7023bce16 | ||
|
|
84fe91c29c | ||
|
|
68f4fc1343 | ||
|
|
2a1744998c | ||
|
|
196e10f44c | ||
|
|
01607b151d | ||
|
|
0cbd082c1d | ||
|
|
41a0f8d9af | ||
|
|
2dd10970a4 | ||
|
|
2f75a7fb1b | ||
|
|
c1e4dc5a0e | ||
|
|
fa2caf9026 | ||
|
|
f42f94da84 | ||
|
|
1f6bbc1044 | ||
|
|
a24d59dfaf | ||
|
|
91a249009b | ||
|
|
16f371dba7 | ||
|
|
86446a059c | ||
|
|
b602b2b188 | ||
|
|
fad8c21473 | ||
|
|
6537d961e1 | ||
|
|
56012c65e1 | ||
|
|
dd1a6607fe | ||
|
|
94d32c419c | ||
|
|
7b7cfdb499 | ||
|
|
a4f03978cf | ||
|
|
7de9dc8f9b | ||
|
|
33df476db5 | ||
|
|
b889064dbc | ||
|
|
9a0e03bf9a | ||
|
|
84c0fb496d | ||
|
|
65f29f21dc | ||
|
|
8d4597d361 | ||
|
|
5bb025323f | ||
|
|
a84ab7655d | ||
|
|
88c9074aea | ||
|
|
c4b58a6ae7 | ||
|
|
0c2c9d7682 | ||
|
|
227b92b743 | ||
|
|
de289d9b4c | ||
|
|
2fabfdfb9f | ||
|
|
b82fa1d077 | ||
|
|
8bf1a77efc | ||
|
|
37f2b1dc5c | ||
|
|
ed3e790b47 | ||
|
|
85c1cc7fa3 | ||
|
|
fa2312470a | ||
|
|
1136c18288 | ||
|
|
0e5657e635 | ||
|
|
ca0b00107a | ||
|
|
be6a69a4d6 | ||
|
|
0b530f9e36 | ||
|
|
55563d2113 | ||
|
|
1aa59dcd7e | ||
|
|
943e96f459 | ||
|
|
6b6dc6b3b8 | ||
|
|
bcb2410b12 | ||
|
|
af630f033d | ||
|
|
6e11cdf289 | ||
|
|
68b17a5c4a | ||
|
|
5ee190468e | ||
|
|
b8f2e00306 | ||
|
|
990f1e8e8f | ||
|
|
58db9c980e | ||
|
|
980b5e28dd | ||
|
|
eb63e6d047 | ||
|
|
59e76d6b26 | ||
|
|
99da6e0f81 | ||
|
|
9d71be9c45 | ||
|
|
ef88039a4a | ||
|
|
a06331eb86 | ||
|
|
ed951667ec | ||
|
|
f621d97289 | ||
|
|
1aa7d81a87 | ||
|
|
0cbcad9d91 | ||
|
|
43388ac42c | ||
|
|
eee160e5bf | ||
|
|
2637434436 | ||
|
|
4b343f7070 | ||
|
|
591d283425 | ||
|
|
f96eb4d3ca | ||
|
|
0168ca56b2 | ||
|
|
703f0ff1b6 | ||
|
|
c5582ba819 | ||
|
|
2dd45dfb93 | ||
|
|
67e1a50c80 | ||
|
|
cbb0e85a19 | ||
|
|
2756176503 | ||
|
|
1a47fdb7d6 | ||
|
|
910effd664 | ||
|
|
7ec7a7d6ae | ||
|
|
53d77f3105 | ||
|
|
45fd999010 | ||
|
|
31c0473c48 | ||
|
|
ebca25ca62 | ||
|
|
6b514bb159 | ||
|
|
314b39dd1c | ||
|
|
6ac8525bfe | ||
|
|
9eb8039698 | ||
|
|
863280d6ac | ||
|
|
19ee54fe05 | ||
|
|
229596da2b | ||
|
|
c2d9050539 | ||
|
|
9b5a8cb6fc | ||
|
|
2e2e36e0a2 | ||
|
|
880eb89719 | ||
|
|
5830053026 | ||
|
|
f815848a86 | ||
|
|
f8c354c302 | ||
|
|
d9ee884459 | ||
|
|
526aa281d9 | ||
|
|
49e9988b82 | ||
|
|
20678b43ad | ||
|
|
d4046673ee | ||
|
|
2f4c125bdd | ||
|
|
98e00d09a3 | ||
|
|
a9e02d94a2 | ||
|
|
2e37e59ed1 | ||
|
|
ae01ed077d | ||
|
|
cb55413e91 | ||
|
|
0fafda55d6 | ||
|
|
bc804ae823 | ||
|
|
127df39689 | ||
|
|
711403f024 | ||
|
|
049a569d7f | ||
|
|
e6ff9a408d | ||
|
|
06ea167ee5 | ||
|
|
7c0699b96f | ||
|
|
adf1117046 | ||
|
|
5215aa4f41 | ||
|
|
599c40b9f6 | ||
|
|
e39c104436 | ||
|
|
7b233422f5 | ||
|
|
0cfaa7740a | ||
|
|
65ab6962d8 | ||
|
|
96331cac90 | ||
|
|
815a87de0b | ||
|
|
e4cb0a35ef | ||
|
|
f609c31a32 | ||
|
|
f51c756cda | ||
|
|
dd965baae1 | ||
|
|
1719baea7a | ||
|
|
e131cd7961 | ||
|
|
b7a71140f2 | ||
|
|
2b23ff3eb8 | ||
|
|
cc061582f4 | ||
|
|
185770a35e | ||
|
|
fd9b8dcfcd | ||
|
|
8267b5afd3 | ||
|
|
e92fb8f52d | ||
|
|
b8a6d576e1 | ||
|
|
4dba56f70d | ||
|
|
776a798e17 | ||
|
|
5710c4d97e | ||
|
|
f04c73d495 | ||
|
|
5c944617d0 |
No files matched your search
@@ -34,7 +34,7 @@ ActiveSync_RESOURCE_FILES += \
|
||||
ADDITIONAL_OBJCFLAGS += -Wno-deprecated-declarations
|
||||
ADDITIONAL_INCLUDE_DIRS += -I../SOPE/ -I../SoObjects/
|
||||
ADDITIONAL_LIB_DIRS += -L../SOPE/GDLContentStore/obj/ -L../SOPE/NGCards/obj/
|
||||
ADDITIONAL_INCLUDE_DIRS += -I/usr/include/libwbxml-1.0/
|
||||
ADDITIONAL_INCLUDE_DIRS += $(shell pkg-config --cflags libwbxml2)
|
||||
ADDITIONAL_LDFLAGS += -Wl,--no-as-needed -lwbxml2
|
||||
|
||||
-include GNUmakefile.preamble
|
||||
|
||||
+116
@@ -2,6 +2,122 @@
|
||||
|
||||
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.11.0](https://github.com/Alinto/sogo/compare/SOGo-5.10.0...SOGo-5.11.0) (2024-08-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Add MySQL/MariaDB SSL connection support ([0168ca5](https://github.com/Alinto/sogo/commit/0168ca56b2e5f51ac98be2a78ddfcea4a688b74f))
|
||||
* **core:** Update to ckeditor 5 ([0cfaa77](https://github.com/Alinto/sogo/commit/0cfaa7740af509389e43c395649ea82a3fb432be) [e92fb8f](https://github.com/Alinto/sogo/commit/e92fb8f52d615b72a6945760aecdf95039e1cbf2) [b7a7114](https://github.com/Alinto/sogo/commit/b7a71140f2b3eefb494a3acc151fe556ce1d3ce4) [a9e02d9](https://github.com/Alinto/sogo/commit/a9e02d94a26cd30c9e56196a1ec24ddec0c4db9c) [98e00d0](https://github.com/Alinto/sogo/commit/98e00d09a3e31b388592e2d1068d89bc57ff1d99) [2b23ff3](https://github.com/Alinto/sogo/commit/2b23ff3eb8dac6acc570b1aaa6e0baff72dd17ea) [65ab696](https://github.com/Alinto/sogo/commit/65ab6962d870baeba7cb4b3c1b66eb6447174e1b) [dd965ba](https://github.com/Alinto/sogo/commit/dd965baae110176c6144ccd64cbade3ef757966a))
|
||||
* **mail:** Improve mail search (advanced search) ([ed95166](https://github.com/Alinto/sogo/commit/ed951667ec610b8ad48a49a606b6b1c33c60914d) [f621d97](https://github.com/Alinto/sogo/commit/f621d972893c94977cda432efe3dcc837ec8bf58) [0cbcad9](https://github.com/Alinto/sogo/commit/0cbcad9d91cd52afcfd14034786457a106013182) [9eb8039](https://github.com/Alinto/sogo/commit/9eb80396988bd7c8ac3a1f44dbc48147ced20750))
|
||||
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **calendar:** Add option to disable email appointmoint notifications when using caldav ([2637434](https://github.com/Alinto/sogo/commit/2637434436c19157ce6255fb071dcb99cf57ad89))
|
||||
* **mail:** Add 'View raw message' mail contextual option. This option open message in a popup without HTML mail CSS content modification. ([6ac8525](https://github.com/Alinto/sogo/commit/6ac8525bfe161409c0565e3dca1e9e92f87c1c08))
|
||||
* **mail:** Add location in email ics representation template ([591d283](https://github.com/Alinto/sogo/commit/591d2834252441a9593f27012f7b148d38b9a511))
|
||||
* **mail:** add options to not add X-Foward header in mail ([96331ca](https://github.com/Alinto/sogo/commit/96331cac905ee7b6dc1adfda083dcefffe0f8795))
|
||||
* **ytnef:** add possibility to add event to calendar from winmail.dat ([f609c31](https://github.com/Alinto/sogo/commit/f609c31a324683dd4c210284f672618cfffb9908))
|
||||
* **calendar:** Optimization of date computation when handle attendees. Report of 77b2d9f8. ([53d77f3](https://github.com/Alinto/sogo/commit/53d77f3105c2d7f6aa2be1cac691dd19a4754df8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **addressbook:** Removed SOGoGlobalAddressBookFirstEntries (use listRequiresDot instead) and move SOGoGlobalAddressBookFirstEntriesCount to globalAddressBookFirstEntriesCount in source scope. This fixed the broken listRequiresDot in 5.10 ([4dba56f](https://github.com/Alinto/sogo/commit/4dba56f70dbda81822b3fd26e1420c367c1b7298))
|
||||
* **attachment:** properly download openxmlformats in attachments ([2e2e36e](https://github.com/Alinto/sogo/commit/2e2e36e0a2b759d5d734bdefc274f127937e9b89))
|
||||
* **calendar(js):** escape CSS selector names ([5c94461](https://github.com/Alinto/sogo/commit/5c944617d0efdc03a72a677e9f60922cb3ab90bd))
|
||||
* **calendar(js):** prevent duplicate event creation in case of an event conflict ([f04c73d](https://github.com/Alinto/sogo/commit/f04c73d495dfce6e93d33ceb0964f1ee6aeb51a5))
|
||||
* **calendar:** Fix all day recurring event displayed on 2 days when a DST change occurs at the endDate ([980b5e2](https://github.com/Alinto/sogo/commit/980b5e28dd0c4cfd42619c64872bb08674d64765))
|
||||
* **calendar:** Fix assertion when is_cycle is set but without c_cycleinfo ([d404667](https://github.com/Alinto/sogo/commit/d4046673eebf21180f494e516a39a7b04243f669) [49e9988](https://github.com/Alinto/sogo/commit/49e9988b82b206ae751feb10979f5e0859d2ec77) [d9ee884](https://github.com/Alinto/sogo/commit/d9ee88445997a21623b33129fe28466042229696))
|
||||
* **calendar:** fix crash when using a 2*16bits chars at the 75th char of ics file ([31c0473](https://github.com/Alinto/sogo/commit/31c0473c48238de2288e5a5c0c8295fc67710d32))
|
||||
* **calendar:** fix disabled change view buttons ([910effd](https://github.com/Alinto/sogo/commit/910effd664e24a3fc5343145f24a2e2d9cf58027))
|
||||
* **calendar:** Fix empty recurence list when creating event from mail ([2756176](https://github.com/Alinto/sogo/commit/27561765038358243a628853b47b20c2db256dc2))
|
||||
* **calendar:** Fix issue on template introduced in commit [#275617650](https://bugs.sogo.nu/view.php?id=275617650) ([eee160e](https://github.com/Alinto/sogo/commit/eee160e5bf60b2e59b96adabae805d61c5d85292))
|
||||
* **calendar:** Fix issue when doubleclick creates an event of 2 days after creating an event > 1 days ([7b23342](https://github.com/Alinto/sogo/commit/7b233422f5790147dd2522261c3b79dac85bfd6c))
|
||||
* **calendar:** Fix issue where a calendar's subscribed user can give acl to another user ([ae01ed0](https://github.com/Alinto/sogo/commit/ae01ed077dcca8d3c3778cde6645075dada9b92d) [cb55413](https://github.com/Alinto/sogo/commit/cb55413e91ac0f10b649abaaeaa5fdab141f0958) [0fafda5](https://github.com/Alinto/sogo/commit/0fafda55d633401c1eeadcbfafd9ae251234787a))
|
||||
* **calendar:** Fix NSData returned instead of NSString on c_cycleinfo depending on database issue. CLoses [#5940](https://bugs.sogo.nu/view.php?id=5940). ([5215aa4](https://github.com/Alinto/sogo/commit/5215aa4f4114acc09bc923542e4fe23fa2779d3b))
|
||||
* **calendar:** Memory optimization on _appendCycleException ([45fd999](https://github.com/Alinto/sogo/commit/45fd999010a80ee39c7fa26f96c1460ddf0d3af9))
|
||||
* **calendar:** resource was busy even if they have delegated ([880eb89](https://github.com/Alinto/sogo/commit/880eb897191c906d141afafe628d66f9a5025640) [5830053](https://github.com/Alinto/sogo/commit/58300530260469594f40db7ee3b67e34ba60d1ce))
|
||||
* **chore:** Remove check on -p option to sogo-ealarms-notify as the SMTP Master account has been implemented ([f96eb4d](https://github.com/Alinto/sogo/commit/f96eb4d3ca32a916ecc63221d022014f736cd94b))
|
||||
* **contacts:** can now reset contacts categories more than once ([526aa28](https://github.com/Alinto/sogo/commit/526aa281d9a92e9fd29046b8a1551aea16c58772))
|
||||
* **core:** Add disableSubgroups option in SOGoUserSources LDAP settings to avoid infinite recursive loop when using a group name with the same name as a member. Fixes [#5913](https://bugs.sogo.nu/view.php?id=5913) ([fd9b8dc](https://github.com/Alinto/sogo/commit/fd9b8dcfcd07217e7456a67c2e69f13fca743874))
|
||||
* **core:** Add timer to avoid spamming onEditorChanged for CKEditor 5. Fixes [#5944](https://bugs.sogo.nu/view.php?id=5944). ([bc804ae](https://github.com/Alinto/sogo/commit/bc804ae823a8a61e6cb02495759f75a104c77753) [2f4c125](https://github.com/Alinto/sogo/commit/2f4c125bdd9b5eebbba41e2204e2fc9c93a3eebd))
|
||||
* **core:** Clean html parasite code to avoid ckeditor hangs. Fixes [#5944](https://bugs.sogo.nu/view.php?id=5944). ([127df39](https://github.com/Alinto/sogo/commit/127df3968998c96d5349f7368adba1e5e371ed4c))
|
||||
* **core:** Fix int overflow on vms and rss computation ([4b343f7](https://github.com/Alinto/sogo/commit/4b343f707016f2b45fbd71c6c2f039b50a076f30))
|
||||
* **core:** Improve error logging on url encryption ([b8a6d57](https://github.com/Alinto/sogo/commit/b8a6d576e19828ffddf6c8e3c3353efd03836b2e))
|
||||
* **core:** Notify sgCkeditor component when clicking the send button to update content. Fixes [#5952](https://bugs.sogo.nu/view.php?id=5952). ([c2d9050](https://github.com/Alinto/sogo/commit/c2d9050539c13f843f2a7395b0b9a24a4b8db3f3))
|
||||
* **core:** On some emails print, first page is blank. Closes [#5602](https://bugs.sogo.nu/view.php?id=5602). ([e39c104](https://github.com/Alinto/sogo/commit/e39c1044364d68df49ea6142072928d0b0e4c1eb))
|
||||
* **doc:** Fix typo with SOGoURLEncryptionEnabled in documentation ([a06331e](https://github.com/Alinto/sogo/commit/a06331eb86aad8ec9a30f30df55aa4282a78654c))
|
||||
* **event:** fix rdate timezones ([e4cb0a3](https://github.com/Alinto/sogo/commit/e4cb0a35ef34b496d8b7e0d6e2e10fe0844e37f8))
|
||||
* **event:** resource was considered busy for an event he has delegated ([adf1117](https://github.com/Alinto/sogo/commit/adf111704642126272301ab7961603fb0b787c9e))
|
||||
* **event:** user was considered busy for an event he has delegated ([815a87d](https://github.com/Alinto/sogo/commit/815a87de0b8bc8e8a7431b8a7b3aedc66d4dde2a))
|
||||
* **languages:** somme "array" strings were using the wrong comma delimiter ([7c0699b](https://github.com/Alinto/sogo/commit/7c0699b96ff1539011e04a1d60f8f4c7334e79b1))
|
||||
* **mail:** Add encoding fallback when there are ISO char in file name. Fixes [#5388](https://bugs.sogo.nu/view.php?id=5388). ([e6ff9a4](https://github.com/Alinto/sogo/commit/e6ff9a408d94d2817ce0d9b9f078811e19ea4a39))
|
||||
* **mail:** Fix advanced search no select/action on results. Fixes [#5978](https://bugs.sogo.nu/view.php?id=5978) ([1a47fdb](https://github.com/Alinto/sogo/commit/1a47fdb7d62242001c770ac28df84bf8c999c179))
|
||||
* **mail:** Fix errors while spamming arrow keys on mail list. The transition was not finished ([049a569](https://github.com/Alinto/sogo/commit/049a569d7faff5f3a507671d17bc0da309ec14ac))
|
||||
* **mail:** Fix Exception when using delegated identities. Closes [#5951](https://bugs.sogo.nu/view.php?id=5951) ([f8c354c](https://github.com/Alinto/sogo/commit/f8c354c302b34b1676764a869481f8a6160f6120))
|
||||
* **mail:** Fix invalid content when forwarding a text/plain mail. Fixes [#5983](https://bugs.sogo.nu/view.php?id=5983). ([ef88039](https://github.com/Alinto/sogo/commit/ef88039a4a3d590de09cf75da5668db9d6fd29f0) [43388ac](https://github.com/Alinto/sogo/commit/43388ac42c99a6fc47cc4191291f81d45faf9a8d))
|
||||
* **mail:** Fix not called draft expunge function when sending mail. ([7ec7a7d](https://github.com/Alinto/sogo/commit/7ec7a7d6aead1a7aee025324e2ae64696b2d087c))
|
||||
* **mail:** Fix template issues with Galician and Basque ([19ee54f](https://github.com/Alinto/sogo/commit/19ee54fe055bc6a45bae6e5f71dcdb60bad766fd))
|
||||
* **mail:** Fix ukrainian encoding issue. Fixes [#5702](https://bugs.sogo.nu/view.php?id=5702). ([776a798](https://github.com/Alinto/sogo/commit/776a798e173e6bacc29c8ab3d32bddabd383da55))
|
||||
* **mail:** Fix unsupported koi-u encoding ([5710c4d](https://github.com/Alinto/sogo/commit/5710c4d97e2fbcf3ff0b187f1100892d8efcacb2))
|
||||
* **mail:** Remove some css when generating UIxMailPartHTMLViewer. Sometimes, the mail contains SOGo mail template in the content, and broke mail display. ([863280d](https://github.com/Alinto/sogo/commit/863280d6ac54783a7fe6a8ce3fcdd8c3a4c4e4ff))
|
||||
* **mail:** Removed bracket display when inline text or inline html ([06ea167](https://github.com/Alinto/sogo/commit/06ea167ee58c65f54cf4474927d977513d380b3b))
|
||||
* **mail:** Restore scroll position when refreshing emails ([9d71be9](https://github.com/Alinto/sogo/commit/9d71be9c45eb9b9af0cb62be99a440ea7652248b))
|
||||
* **mail:** Update len value as data is modified in loop. This fixes the issue on sanitizedContentUsingVoidTags whenthere are meta html tags in mail content. ([99da6e0](https://github.com/Alinto/sogo/commit/99da6e0f81a99f94e453ece7fe0761fcf1186fb6))
|
||||
* **mail:** When the signature is empty, the -- seperator appears. ([58db9c9](https://github.com/Alinto/sogo/commit/58db9c980eb518dc726e908307988b98cf7a4d84))
|
||||
* **preferences:** Can not save preferences when there is a constraint on mail ([990f1e8](https://github.com/Alinto/sogo/commit/990f1e8e8fda67537a3a4ed3a067011e3b81e7dd))
|
||||
* **ui:** Fix invalid display when creating new appointment / mail on mobile. Initialize isFullscreen to if screen is in portrait. Fixes [#5970](https://bugs.sogo.nu/view.php?id=5970). ([314b39d](https://github.com/Alinto/sogo/commit/314b39dd1c65e321920fdc095915c2954a53b944))
|
||||
* **vulnerability:** prevent cross-site scripting when previewing attachments ([2e37e59](https://github.com/Alinto/sogo/commit/2e37e59ed140d4aee0ff2fba579ca5f83f2c5920))
|
||||
|
||||
|
||||
### Localization
|
||||
|
||||
* **ar:** Update Arabic translations ([1aa59dc](https://github.com/Alinto/sogo/commit/1aa59dcd7e5755873ac0a68c770ecb33efe50077))
|
||||
* **bg_BG:** Update Bulgarian translations ([be6a69a](https://github.com/Alinto/sogo/commit/be6a69a4d600da2ac3bccd00d10b185871295c2a))
|
||||
* **bs_BA:** Update Bosnian translations ([0b530f9](https://github.com/Alinto/sogo/commit/0b530f9e36a996f9910432b5410ce14e0c74a979))
|
||||
* **calendar:** Change Email Attendees to Email Attendees (internal users ([eb63e6d](https://github.com/Alinto/sogo/commit/eb63e6d047c4f3967b83f17c6457d353acf2d9ae))
|
||||
* **ca:** Update Catalan translations ([ca0b001](https://github.com/Alinto/sogo/commit/ca0b00107a65b2cd5b78d084d8591a99d6055d07))
|
||||
* **cs:** Update Czech translations ([5ee1904](https://github.com/Alinto/sogo/commit/5ee190468e7616e2fb05a90fd146dbb0a76ccb6a))
|
||||
* **cy:** Update Welsh translations ([fad8c21](https://github.com/Alinto/sogo/commit/fad8c21473bdb975826a37f728f7da76a21afd6f))
|
||||
* **da_DK:** Update Danish translations ([bcb2410](https://github.com/Alinto/sogo/commit/bcb2410b1224981f0fec618f2e073197d2539666))
|
||||
* **de:** Update German translations ([37f2b1d](https://github.com/Alinto/sogo/commit/37f2b1dc5c630ea58ad356c568d706bb81747cb7))
|
||||
* **es_AR:** Update SpanishArgentina translations ([94d32c4](https://github.com/Alinto/sogo/commit/94d32c419c525067415c8ff9c2aa44fd00c361ed))
|
||||
* **es_ES:** Update SpanishSpain translations ([6e11cdf](https://github.com/Alinto/sogo/commit/6e11cdf2893727ae77625cad1d0be4cd0e60e1bd))
|
||||
* **eu:** Update Basque translations ([55563d2](https://github.com/Alinto/sogo/commit/55563d21132c43841fe009ea93e71bae5058a37c))
|
||||
* **fi:** Update Finnish translations ([ed3e790](https://github.com/Alinto/sogo/commit/ed3e790b47f60b2b2548888c23eeb1945c7d8156))
|
||||
* **gl:** Update Galician translations ([6b6dc6b](https://github.com/Alinto/sogo/commit/6b6dc6b3b845357b2d9986abe3b84e91484f7c58))
|
||||
* **he:** Update Hebrew translations ([8bf1a77](https://github.com/Alinto/sogo/commit/8bf1a77efc12eeaf41b4f6d32aec6eddfe5f4f5c))
|
||||
* **hr_HR:** Update Croatian translations ([fa23124](https://github.com/Alinto/sogo/commit/fa2312470adff5026c1d8b75c3e32109b55e51a4))
|
||||
* **hu:** Update Hungarian translations ([b82fa1d](https://github.com/Alinto/sogo/commit/b82fa1d0771b082d07e3c654e41a3ef82888e525))
|
||||
* **id_ID:** Update Indonesian translations ([de289d9](https://github.com/Alinto/sogo/commit/de289d9b4cd7b0ed6050183acb020c8e1c4054ca))
|
||||
* **is:** Update Icelandic translations ([2fabfdf](https://github.com/Alinto/sogo/commit/2fabfdfb9f78df085531a4525707311004d6c4ed))
|
||||
* **it:** Update Italian translations ([227b92b](https://github.com/Alinto/sogo/commit/227b92b7434ded7f54affe9aad629b08a3e2badf))
|
||||
* **ja:** Update Japanese translations ([0c2c9d7](https://github.com/Alinto/sogo/commit/0c2c9d7682a9ca66ac4d3778ae0efc907f525aa8))
|
||||
* **kk:** Update Kazakh translations ([c4b58a6](https://github.com/Alinto/sogo/commit/c4b58a6ae75110482801b4aa2141b564b8943476))
|
||||
* **lt:** Update Lithuanian translations ([a84ab76](https://github.com/Alinto/sogo/commit/a84ab7655d05aec16c0d247d13f6da3b46a95221))
|
||||
* **lv:** Update Latvian translations ([88c9074](https://github.com/Alinto/sogo/commit/88c9074aeab8759d841ea79db2245520fe998543))
|
||||
* **mk_MK:** Update Macedonian translations ([5bb0253](https://github.com/Alinto/sogo/commit/5bb025323f63b93b984707ffa507d900846f6ec5))
|
||||
* **nb_NO:** Update NorwegianBokmal translations ([943e96f](https://github.com/Alinto/sogo/commit/943e96f459cc5a8fd93ec8da36f064f251d30dc2))
|
||||
* **nl:** Update Dutch translations ([85c1cc7](https://github.com/Alinto/sogo/commit/85c1cc7fa3f3e21b6a158cd51c3e640e94f0e81e))
|
||||
* **nn_NO:** Update NorwegianNynorsk translations ([65f29f2](https://github.com/Alinto/sogo/commit/65f29f21dc2d957e3bcabdedb8873e71f65461ee))
|
||||
* **pl:** Update Polish translations ([84c0fb4](https://github.com/Alinto/sogo/commit/84c0fb496d5727ccb4248b96b36d57a620ef440a))
|
||||
* **pt_BR:** Update BrazilianPortuguese translations ([af630f0](https://github.com/Alinto/sogo/commit/af630f033d3cc1188019e1f3cfe0ebb852f3560f))
|
||||
* **pt:** Update Portuguese translations ([9a0e03b](https://github.com/Alinto/sogo/commit/9a0e03bf9afdf049bcce0045411400931c6ce9f1))
|
||||
* **ro_RO:** Update Romanian translations ([b889064](https://github.com/Alinto/sogo/commit/b889064dbc7e137298ef8e70538f6a7196477596))
|
||||
* **ru:** Update Russian translations ([33df476](https://github.com/Alinto/sogo/commit/33df476db55890fcae1c40bd29e9e81017c86e72))
|
||||
* **sk:** Update Slovak translations ([7b7cfdb](https://github.com/Alinto/sogo/commit/7b7cfdb499766867430e49376185105cfbf2ab4f))
|
||||
* **sl_SI:** Update Slovenian translations ([68b17a5](https://github.com/Alinto/sogo/commit/68b17a5c4a6a2e015d32dc79c7b33d348be1a5f2))
|
||||
* **sr_ME@latin:** Update Montenegrin translations ([8d4597d](https://github.com/Alinto/sogo/commit/8d4597d3610928191313986490eafb44f3077ca5))
|
||||
* **sr@latin:** Update SerbianLatin translations ([a4f0397](https://github.com/Alinto/sogo/commit/a4f03978cfc815d3a337d94d5a46bbe10166306a))
|
||||
* **sr:** Update Serbian translations ([7de9dc8](https://github.com/Alinto/sogo/commit/7de9dc8f9ba3e62d082d91f4305c5be6c9d2a7c6))
|
||||
* **sv:** Update Swedish translations ([dd1a660](https://github.com/Alinto/sogo/commit/dd1a6607fe3ed9e65991d349588179acb7f2eb6c))
|
||||
* **tr_TR:** Update TurkishTurkey translations ([56012c6](https://github.com/Alinto/sogo/commit/56012c65e191937b5132c091f6366b6b24546a5f))
|
||||
* **uk:** Update Ukrainian translations ([6537d96](https://github.com/Alinto/sogo/commit/6537d961e13604ae373e787664f6fa91f9912ab5))
|
||||
* **zh_CN:** Update ChineseChina translations ([0e5657e](https://github.com/Alinto/sogo/commit/0e5657e635012cd2568e30060741e7114ef1a591))
|
||||
* **zh_TW:** Update ChineseTaiwan translations ([1136c18](https://github.com/Alinto/sogo/commit/1136c182885d716dc2613d58f59a58fdcf8d8168))
|
||||
|
||||
## [5.10.0](https://github.com/Alinto/sogo/compare/SOGo-5.9.1...SOGo-5.10.0) (2024-02-20)
|
||||
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ The following operating systems are currently supported by SOGo:
|
||||
* 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)
|
||||
* Ubuntu 14.04 (Trusty), 16.04 (Xenial), 18.04 (Bionic), 20.04 (Focal), 22.04 (Jammy) and 24.04 (Noble)
|
||||
|
||||
Make sure the required components are started automatically at boot time
|
||||
and that they are running before proceeding with the SOGo configuration.
|
||||
@@ -530,6 +530,10 @@ This could be `America/New_York`, `Europe/Berlin`, `Asia/Tokyo` or
|
||||
|
||||
In our example, we set the time zone to `America/Montreal`.
|
||||
|
||||
|D |SOGoMailDisableXForward
|
||||
|Parameter used to prevent sogo from adding the mail header X-Forward that
|
||||
contains the user ip. Set to 'YES' to disable this header. Default to 'NO'.
|
||||
|
||||
|D |SOGoMailDomain
|
||||
|Parameter used to set the default domain name used by SOGo. SOGo uses
|
||||
this parameter to build the list of valid email addresses for users.
|
||||
@@ -802,6 +806,16 @@ Default value is `YES`, or enabled.
|
||||
authentication and global address books. Multiple sources can be
|
||||
specified as an array of dictionaries.
|
||||
|
||||
|S |SOGoForbidUnknownDomainsAuth
|
||||
|Boolean. If set to `YES`, prevent user that try to connect with an unknown domain.
|
||||
The known domains are set in parameter `SOGoDomainAllowed` and/or the domains listed in a <<multi-domains-configuration, Multi-domains Configuration>>. Default value is `NO`.
|
||||
|
||||
Obiously, if your users can connect without specifying a domain, let this parameter to `NO`.
|
||||
|
||||
|S |SOGoDomainAllowed
|
||||
|Parameter used to define which domains SOGo should allowed during a connection request. This parameter is an array of strings.
|
||||
|
||||
|
||||
|S |SOGoPasswordRecoveryEnabled
|
||||
|Boolean enable password recovery with secret question or secondary e-mail. Default value is `NO`.
|
||||
|
||||
@@ -830,16 +844,13 @@ specified as an array of dictionaries.
|
||||
|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`.
|
||||
|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, if the value of thie parameter is changed, memcached server needs to be restarted. 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`.
|
||||
|D |SOGoForceRawHtmlSignature
|
||||
|Add `<div class="raw-html-embed sogo-raw-html-embed">` around signature to avoid CKEditor 5 modifying HTML code and breaking signature (https://bugs.sogo.nu/view.php?id=5920). Default value is `YES` (tag is added).
|
||||
|
||||
|=======================================================================
|
||||
|
||||
@@ -1273,6 +1284,13 @@ repository
|
||||
|If set to `YES`, listing of this LDAP source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitly typing a single dot.
|
||||
Defaults to `YES` when unset.
|
||||
|
||||
|globalAddressBookFirstEntriesCount (optional)
|
||||
|Number of entries displayed when `listRequiresDot` is enabled. Default value is `-1` (all records / equals to SOGoLDAPQueryLimit). This parameter must be less or equal to SOGoLDAPQueryLimit. If source is LDAP, the LDAP overlay sssvlv must be enabled on the system for server side sorting.
|
||||
|
||||
|disableSubgroups (optional)
|
||||
|If set to `YES`, disable recursive search. Consider this option when groups have the same name than a member (https://bugs.sogo.nu/view.php?id=5913).
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|ModulesConstraints (optional)
|
||||
|Limits the access of any module through a constraint based on an LDAP
|
||||
attribute; must be a dictionary with keys `Mail`, and/or `Calendar`,
|
||||
@@ -1722,6 +1740,19 @@ default-character-set=utf8
|
||||
default-character-set=utf8
|
||||
----
|
||||
|
||||
MySQL SSL Connection
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Add the following settings in the `sogo.conf` file to enable SSL connection between SOGo and MySQL / MariaDB :
|
||||
|
||||
----
|
||||
MySQL4SSLEnabled = YES;
|
||||
MySQL4SSLKeyPath = "/mysql_keys/client-key.pem";
|
||||
MySQL4SSLCertPath = "/mysql_keys/client-cert.pem";
|
||||
MySQL4SSLCaPath = "/mysql_keys/ca-cert.pem";
|
||||
----
|
||||
|
||||
|
||||
MySQL complete Unicode compliance
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -2803,6 +2834,8 @@ like this:
|
||||
}
|
||||
----
|
||||
|
||||
[[multi-domains-configuration]]
|
||||
|
||||
Multi-domains Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -3966,6 +3999,9 @@ current version of SOGo from the previous release.
|
||||
[cols="100a"]
|
||||
|=======================================================================
|
||||
|
||||
h|5.11
|
||||
|Parameters `SOGoGlobalAddressBookFirstEntries` and `SOGoGlobalAddressBookFirstEntriesCount` has been removed. Please use `listRequiresDot` and `globalAddressBookFirstEntriesCount` instead. Signature are now between a div tag to avoid CKEditor changing the content - set `SOGoForceRawHtmlSignature` to `NO` to leave signature (https://bugs.sogo.nu/view.php?id=5920).
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
+1
-1
@@ -483,7 +483,7 @@ static BOOL debugLeaks;
|
||||
|
||||
- (void) checkIfDaemonHasToBeShutdown
|
||||
{
|
||||
unsigned int vmem;
|
||||
unsigned long long vmem;
|
||||
|
||||
if (vMemSizeLimit > 0)
|
||||
{
|
||||
|
||||
@@ -45,19 +45,27 @@
|
||||
[foldedString appendString: self];
|
||||
else
|
||||
{
|
||||
subStringRange = NSMakeRange (0, 75);
|
||||
[foldedString appendFormat: @"%@\r\n",
|
||||
[self substringWithRange: subStringRange]];
|
||||
subStringRange = NSMakeRange (75, 74);
|
||||
//Check if the 75th chars is a 2*16bits chars, in this case, cut one char before
|
||||
unichar c = [self characterAtIndex: 74];
|
||||
int substringCut;
|
||||
if (c >= 0xD800 && c <= 0xDBFF)
|
||||
substringCut = 74;
|
||||
else
|
||||
substringCut = 75;
|
||||
subStringRange = NSMakeRange (0, substringCut);
|
||||
[foldedString appendFormat: @"%@\r\n", [self substringWithRange: subStringRange]];
|
||||
|
||||
subStringRange = NSMakeRange (substringCut, 74);
|
||||
while ((length - subStringRange.location) > 75)
|
||||
{
|
||||
[foldedString appendFormat: @" %@\r\n",
|
||||
[self substringWithRange: subStringRange]];
|
||||
subStringRange.location += 74;
|
||||
c = [self characterAtIndex: (subStringRange.location+subStringRange.length-1)];
|
||||
if (c >= 0xD800 && c <= 0xDBFF)
|
||||
subStringRange.length--;
|
||||
[foldedString appendFormat: @" %@\r\n", [self substringWithRange: subStringRange]];
|
||||
subStringRange.location += subStringRange.length;
|
||||
}
|
||||
subStringRange.length = length - subStringRange.location;
|
||||
[foldedString appendFormat: @" %@",
|
||||
[self substringWithRange: subStringRange]];
|
||||
[foldedString appendFormat: @" %@", [self substringWithRange: subStringRange]];
|
||||
}
|
||||
|
||||
return foldedString;
|
||||
|
||||
@@ -140,14 +140,25 @@
|
||||
rDate = [rDates objectAtIndex: i];
|
||||
|
||||
|
||||
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]])
|
||||
|
||||
/*
|
||||
VERY VERY STRANGE
|
||||
Sogo seems to handle the date and timezone very strangely
|
||||
The first event date will be in gmt BUT at the timezone hour
|
||||
Ex: the event is at 09h15 +0200, the date in sogo will be 09h15 +0000...
|
||||
So for the rdates, even if they are correct (meaning 07h15 +0000 which is 09h15 +0200)
|
||||
We will have to set them to have 09h15 +0000 (in thas case it will be 11h15 +0200)...
|
||||
*/
|
||||
//the code below was before the fix above
|
||||
// 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];
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ vtodo_class2 = "(Skrytý úkol)";
|
||||
"The event \"%{Summary}\" was created" = "Událost \"%{Summary}\" byla vytvořena";
|
||||
"The event \"%{Summary}\" was deleted" = "Událost \"%{Summary}\" byla smazána";
|
||||
"The event \"%{Summary}\" was updated" = "Událost \"%{Summary}\" byla upravena";
|
||||
"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" = "Úkol \"%{Summary}\" byl vytvořen";
|
||||
"The task \"%{Summary}\" was deleted" = "Úkol \"%{Summary}\" byl smazán";
|
||||
"The task \"%{Summary}\" was updated" = "Úkol \"%{Summary}\" byl upraven";
|
||||
"The following attendees(s) were notified" = "Následující účastníci byli upozorněni";
|
||||
"The following attendees(s) were added" = "Následující účastníci byli přidáni";
|
||||
"The following attendees(s) were removed" = "Následující účastníci byli odebráni";
|
||||
|
||||
@@ -1075,6 +1075,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
withTimeZone: (NSTimeZone *) tz
|
||||
toArray: (NSMutableArray *) ma
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
NGCalendarDateRange *recurrenceIdRange;
|
||||
NSCalendarDate *recurrenceId, *masterEndDate, *endDate;
|
||||
NSMutableDictionary *newRecord;
|
||||
@@ -1162,6 +1165,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
endDate: [newRecord objectForKey: @"endDate"]];
|
||||
if ([dateRange doesIntersectWithDateRange: newRecordRange])
|
||||
[ma addObject: newRecord];
|
||||
|
||||
else
|
||||
newRecord = nil;
|
||||
} else {
|
||||
@@ -1184,6 +1188,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
[self _fixExceptionRecord: newRecord fromRow: row];
|
||||
}
|
||||
|
||||
DESTROY(pool);
|
||||
|
||||
// We finally adjust the c_nextalarm
|
||||
[self _computeAlarmForRow: (id)row
|
||||
master: component];
|
||||
@@ -1255,7 +1261,13 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
NSInteger offset;
|
||||
id tz;
|
||||
|
||||
content = [theRecord objectForKey: @"c_cycleinfo"];
|
||||
content = nil;
|
||||
if ([theRecord objectForKey: @"c_cycleinfo"] && [[theRecord objectForKey: @"c_cycleinfo"] isKindOfClass: [NSData class]]) {
|
||||
content = [NSString stringWithUTF8String: [[theRecord objectForKey: @"c_cycleinfo"] bytes]];
|
||||
} else if ([theRecord objectForKey: @"c_cycleinfo"] && [[theRecord objectForKey: @"c_cycleinfo"] isKindOfClass: [NSString class]]) {
|
||||
content = [theRecord objectForKey: @"c_cycleinfo"];
|
||||
}
|
||||
|
||||
if (![content isNotNull])
|
||||
{
|
||||
// If c_iscycle is set but c_cycleinfo is null, that means we're dealing with a vcalendar that
|
||||
@@ -1283,16 +1295,22 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
return;
|
||||
}
|
||||
|
||||
cycleinfo = [content propertyList];
|
||||
if (!cycleinfo)
|
||||
if (nil != content)
|
||||
{
|
||||
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", theRecord];
|
||||
return;
|
||||
cycleinfo = [content propertyList];
|
||||
if (!cycleinfo)
|
||||
{
|
||||
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@, %@", theRecord, content];
|
||||
return;
|
||||
}
|
||||
rules = [cycleinfo objectForKey: @"rules"];
|
||||
exRules = [cycleinfo objectForKey: @"exRules"];
|
||||
rDates = [cycleinfo objectForKey: @"rDates"];
|
||||
exDates = [cycleinfo objectForKey: @"exDates"];
|
||||
} else {
|
||||
[self warnWithFormat:@"record has no valid content -> %@, %@", theRecord, content];
|
||||
}
|
||||
rules = [cycleinfo objectForKey: @"rules"];
|
||||
exRules = [cycleinfo objectForKey: @"exRules"];
|
||||
rDates = [cycleinfo objectForKey: @"rDates"];
|
||||
exDates = [cycleinfo objectForKey: @"exDates"];
|
||||
|
||||
eventTimeZone = nil;
|
||||
allDayTimeZone = nil;
|
||||
tz = nil;
|
||||
@@ -2962,7 +2980,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
if (!infos)
|
||||
infos = [[NSArray alloc] initWithObjects: @"c_content", @"c_partmails", @"c_partstates",
|
||||
@"c_isopaque", @"c_status", @"c_cycleinfo", @"c_orgmail", nil];
|
||||
@"c_isopaque", @"c_status", @"c_cycleinfo", @"c_orgmail", @"c_participants", nil];
|
||||
|
||||
// We MUST include the protected information when checking for freebusy info as
|
||||
// we rely on the c_partmails/c_partstates fields for many operations.
|
||||
|
||||
@@ -639,12 +639,17 @@
|
||||
NSEnumerator *enumerator;
|
||||
NSString *currentUID;
|
||||
SOGoUser *user, *currentUser;
|
||||
unsigned long long eventStartPeriod, eventEndPeriod;
|
||||
|
||||
_resourceHasAutoAccepted = NO;
|
||||
|
||||
// Build a list of the attendees uids
|
||||
attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]];
|
||||
enumerator = [theAttendees objectEnumerator];
|
||||
|
||||
eventStartPeriod = [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT];
|
||||
eventEndPeriod = [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT];
|
||||
|
||||
while ((currentAttendee = [enumerator nextObject]))
|
||||
{
|
||||
currentUID = [currentAttendee uidInContext: context];
|
||||
@@ -658,233 +663,318 @@
|
||||
// the owner is a resource
|
||||
currentUser = [context activeUser];
|
||||
if (!activeUserIsOwner && ![currentUser isSuperUser])
|
||||
{
|
||||
[attendees addObject: owner];
|
||||
}
|
||||
{
|
||||
[attendees addObject: owner];
|
||||
}
|
||||
|
||||
enumerator = [attendees objectEnumerator];
|
||||
while ((currentUID = [enumerator nextObject]))
|
||||
{
|
||||
NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate;
|
||||
SOGoAppointmentFolder *folder;
|
||||
SOGoFreeBusyObject *fb;
|
||||
NGCalendarDateRange *range;
|
||||
NSMutableArray *fbInfo;
|
||||
NSArray *allOccurences;
|
||||
|
||||
BOOL must_delete;
|
||||
int i, j, delta;
|
||||
|
||||
user = [SOGoUser userWithLogin: currentUID];
|
||||
|
||||
// We get the start/end date for our conflict range. If the event to be added is recurring, we
|
||||
// check for at least a year to start with.
|
||||
start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1];
|
||||
end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1];
|
||||
|
||||
folder = [user personalCalendarFolderInContext: context];
|
||||
|
||||
// Deny access to the resource if the ACLs don't allow the user
|
||||
if ([user isResource] && ![folder aclSQLListingFilter])
|
||||
{
|
||||
NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate;
|
||||
SOGoAppointmentFolder *folder;
|
||||
SOGoFreeBusyObject *fb;
|
||||
NGCalendarDateRange *range;
|
||||
NSMutableArray *fbInfo;
|
||||
NSArray *allOccurences;
|
||||
NSDictionary *values;
|
||||
NSString *reason;
|
||||
|
||||
BOOL must_delete;
|
||||
int i, j, delta;
|
||||
|
||||
user = [SOGoUser userWithLogin: currentUID];
|
||||
|
||||
// We get the start/end date for our conflict range. If the event to be added is recurring, we
|
||||
// check for at least a year to start with.
|
||||
start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1];
|
||||
end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1];
|
||||
|
||||
folder = [user personalCalendarFolderInContext: context];
|
||||
|
||||
// Deny access to the resource if the ACLs don't allow the user
|
||||
if ([user isResource] && ![folder aclSQLListingFilter])
|
||||
{
|
||||
NSDictionary *values;
|
||||
NSString *reason;
|
||||
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail", nil];
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]];
|
||||
return [self exceptionWithHTTPStatus:409 reason: reason];
|
||||
}
|
||||
|
||||
fb = [SOGoFreeBusyObject objectWithName: @"freebusy.ifb" inContainer: [user homeFolderInContext: context]];
|
||||
fbInfo = (NSMutableArray *)[fb fetchFreeBusyInfosFrom: start to: end];
|
||||
|
||||
//
|
||||
// We must also check here for repetitive events that don't overlap our event.
|
||||
// We remove all events that don't overlap. The events here are already
|
||||
// decomposed.
|
||||
//
|
||||
if ([theEvent isRecurrent])
|
||||
allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start
|
||||
endDate: end]
|
||||
firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate]
|
||||
endDate: [theEvent endDate]]];
|
||||
else
|
||||
allOccurences = nil;
|
||||
|
||||
for (i = [fbInfo count]-1; i >= 0; i--)
|
||||
{
|
||||
// We first remove any occurences in the freebusy that corresponds to the
|
||||
// current event. We do this to avoid raising a conflict if we move a 1 hour
|
||||
// meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise.
|
||||
if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame)
|
||||
{
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore transparent events
|
||||
if (![[[fbInfo objectAtIndex: i] objectForKey: @"c_isopaque"] boolValue])
|
||||
{
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// No need to check if the event isn't recurrent here as it's handled correctly
|
||||
// when we compute the "end" date.
|
||||
if ([allOccurences count])
|
||||
{
|
||||
must_delete = YES;
|
||||
|
||||
// We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been
|
||||
// modified in SOGoAppointmentFolder: -fixupCycleRecord: ....
|
||||
rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"];
|
||||
delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT];
|
||||
rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
|
||||
|
||||
rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"];
|
||||
delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent endDate]] secondsOffsetFromGMT];
|
||||
rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
|
||||
|
||||
range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate
|
||||
endDate: rangeEndDate];
|
||||
|
||||
for (j = 0; j < [allOccurences count]; j++)
|
||||
{
|
||||
if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]])
|
||||
{
|
||||
must_delete = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (must_delete)
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
}
|
||||
}
|
||||
|
||||
// Find the attendee associated to the current UID
|
||||
currentAttendee = nil;
|
||||
for (i = 0; i < [theAttendees count]; i++)
|
||||
{
|
||||
currentAttendee = [theAttendees objectAtIndex: i];
|
||||
if ([[currentAttendee uidInContext: context] isEqualToString: currentUID])
|
||||
break;
|
||||
else
|
||||
currentAttendee = nil;
|
||||
}
|
||||
|
||||
if ([fbInfo count])
|
||||
{
|
||||
SOGoDateFormatter *formatter;
|
||||
|
||||
formatter = [[context activeUser] dateFormatterInContext: context];
|
||||
|
||||
if ([user isResource])
|
||||
{
|
||||
// We always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit
|
||||
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
|
||||
// overlapping events.
|
||||
// When numberOfSimultaneousBookings is set to -1, only force the auto-accept
|
||||
// once the conflict has been raised and the action is forced by the user.
|
||||
if ([user numberOfSimultaneousBookings] <= 0 ||
|
||||
[user numberOfSimultaneousBookings] > [fbInfo count])
|
||||
{
|
||||
if (currentAttendee && ([user numberOfSimultaneousBookings] >= 0 || forceSave))
|
||||
{
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
NSDictionary *values, *info;
|
||||
NSString *reason;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]];
|
||||
event = [[calendar events] lastObject];
|
||||
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail",
|
||||
([event summary] ? [event summary] : @""), @"EventTitle",
|
||||
[formatter formattedDateAndTime: [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"]], @"StartDate",
|
||||
nil];
|
||||
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]];
|
||||
|
||||
info = [NSDictionary dictionaryWithObject: reason forKey: @"reject"];
|
||||
|
||||
return [self exceptionWithHTTPStatus: 409
|
||||
reason: [info jsonRepresentation]];
|
||||
}
|
||||
}
|
||||
//
|
||||
// We are dealing with a normal attendee. Lets check if we have conflicts, unless
|
||||
// we are being asked to force the save anyway
|
||||
//
|
||||
if (!forceSave && !_resourceHasAutoAccepted)
|
||||
{
|
||||
NSMutableDictionary *info;
|
||||
NSMutableArray *conflicts;
|
||||
NSString *formattedEnd;
|
||||
SOGoUser *ownerUser;
|
||||
id o;
|
||||
|
||||
info = [NSMutableDictionary dictionary];
|
||||
conflicts = [NSMutableArray array];
|
||||
|
||||
if (currentAttendee)
|
||||
{
|
||||
if ([currentAttendee cn])
|
||||
[info setObject: [currentAttendee cn] forKey: @"attendee_name"];
|
||||
if ([currentAttendee rfc822Email])
|
||||
[info setObject: [currentAttendee rfc822Email] forKey: @"attendee_email"];
|
||||
}
|
||||
else if ([owner isEqualToString: currentUID])
|
||||
{
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
if ([ownerUser cn])
|
||||
[info setObject: [ownerUser cn] forKey: @"attendee_name"];
|
||||
if ([ownerUser systemEmail])
|
||||
[info setObject: [ownerUser systemEmail] forKey: @"attendee_email"];
|
||||
}
|
||||
|
||||
for (i = 0; i < [fbInfo count]; i++)
|
||||
{
|
||||
o = [fbInfo objectAtIndex: i];
|
||||
end = [o objectForKey: @"endDate"];
|
||||
if ([[o objectForKey: @"startDate"] isDateOnSameDay: end])
|
||||
formattedEnd = [formatter formattedTime: end];
|
||||
else
|
||||
formattedEnd = [formatter formattedDateAndTime: end];
|
||||
|
||||
[conflicts addObject: [NSDictionary dictionaryWithObjectsAndKeys: [formatter formattedDateAndTime: [o objectForKey: @"startDate"]], @"startDate",
|
||||
formattedEnd, @"endDate", nil]];
|
||||
}
|
||||
|
||||
[info setObject: conflicts forKey: @"conflicts"];
|
||||
|
||||
// We immediately raise an exception, without processing the possible other attendees.
|
||||
return [self exceptionWithHTTPStatus: 409
|
||||
reason: [info jsonRepresentation]];
|
||||
}
|
||||
} // if ([fbInfo count]) ...
|
||||
else if (currentAttendee && [user isResource])
|
||||
{
|
||||
// No conflict, we auto-accept. We do this for resources automatically if no
|
||||
// double-booking is observed. If it's not the desired behavior, just don't
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail", nil];
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]];
|
||||
return [self exceptionWithHTTPStatus:409 reason: reason];
|
||||
}
|
||||
|
||||
fb = [SOGoFreeBusyObject objectWithName: @"freebusy.ifb" inContainer: [user homeFolderInContext: context]];
|
||||
fbInfo = (NSMutableArray *)[fb fetchFreeBusyInfosFrom: start to: end];
|
||||
|
||||
//
|
||||
// We must also check here for repetitive events that don't overlap our event.
|
||||
// We remove all events that don't overlap. The events here are already
|
||||
// decomposed.
|
||||
//
|
||||
if ([theEvent isRecurrent])
|
||||
allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start
|
||||
endDate: end]
|
||||
firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate]
|
||||
endDate: [theEvent endDate]]];
|
||||
else
|
||||
allOccurences = nil;
|
||||
|
||||
for (i = [fbInfo count]-1; i >= 0; i--)
|
||||
{
|
||||
// We first remove any occurences in the freebusy that corresponds to the
|
||||
// current event. We do this to avoid raising a conflict if we move a 1 hour
|
||||
// meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise.
|
||||
if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame)
|
||||
{
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore transparent events
|
||||
if (![[[fbInfo objectAtIndex: i] objectForKey: @"c_isopaque"] boolValue])
|
||||
{
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
continue;
|
||||
}
|
||||
|
||||
// No need to check if the event isn't recurrent here as it's handled correctly
|
||||
// when we compute the "end" date.
|
||||
if ([allOccurences count])
|
||||
{
|
||||
must_delete = YES;
|
||||
|
||||
// We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been
|
||||
// modified in SOGoAppointmentFolder: -fixupCycleRecord: ....
|
||||
rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"];
|
||||
delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - eventStartPeriod;
|
||||
rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
|
||||
|
||||
rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"];
|
||||
delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - eventEndPeriod;
|
||||
rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
|
||||
|
||||
range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate
|
||||
endDate: rangeEndDate];
|
||||
|
||||
for (j = 0; j < [allOccurences count]; j++)
|
||||
{
|
||||
if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]])
|
||||
{
|
||||
must_delete = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (must_delete)
|
||||
[fbInfo removeObjectAtIndex: i];
|
||||
}
|
||||
}
|
||||
|
||||
// Find the attendee associated to the current UID
|
||||
currentAttendee = nil;
|
||||
for (i = 0; i < [theAttendees count]; i++)
|
||||
{
|
||||
currentAttendee = [theAttendees objectAtIndex: i];
|
||||
if ([[currentAttendee uidInContext: context] isEqualToString: currentUID])
|
||||
break;
|
||||
else
|
||||
currentAttendee = nil;
|
||||
}
|
||||
|
||||
if ([fbInfo count])
|
||||
{
|
||||
SOGoDateFormatter *formatter;
|
||||
|
||||
formatter = [[context activeUser] dateFormatterInContext: context];
|
||||
|
||||
if ([user isResource])
|
||||
{
|
||||
// We always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit
|
||||
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
|
||||
// overlapping events.
|
||||
// When numberOfSimultaneousBookings is set to -1, only force the auto-accept
|
||||
// once the conflict has been raised and the action is forced by the user.
|
||||
if ([user numberOfSimultaneousBookings] <= 0 || [user numberOfSimultaneousBookings] > [fbInfo count])
|
||||
{
|
||||
if (currentAttendee && ([user numberOfSimultaneousBookings] >= 0 || forceSave))
|
||||
{
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(currentAttendee)
|
||||
{
|
||||
//Check if the ressource has delegated to another resource
|
||||
NSMutableDictionary *infoResource;
|
||||
NSArray *r_emails, *r_names, *r_partstates;
|
||||
int nbOfDelegation = 0;
|
||||
BOOL r_hasDelegated = NO;
|
||||
id r_o;
|
||||
|
||||
infoResource = [NSMutableDictionary dictionary];
|
||||
if ([currentAttendee cn])
|
||||
[infoResource setObject: [currentAttendee cn] forKey: @"resource_name"];
|
||||
if ([currentAttendee rfc822Email])
|
||||
[infoResource setObject: [currentAttendee rfc822Email] forKey: @"resource_email"];
|
||||
|
||||
for (i = 0; i < [fbInfo count]; i++)
|
||||
{
|
||||
r_o = [fbInfo objectAtIndex: i];
|
||||
//Check if the resource has delegated the event
|
||||
r_hasDelegated = NO;
|
||||
r_emails = [[r_o objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
|
||||
r_names = [[r_o objectForKey: @"c_participants"] componentsSeparatedByString: @"\n"];
|
||||
for (j = 0; j < [r_emails count]; j++)
|
||||
{
|
||||
if ([[infoResource objectForKey:@"resource_email"] isEqualToString: [r_emails objectAtIndex: j]] ||
|
||||
[[infoResource objectForKey:@"resource_name"] isEqualToString: [r_names objectAtIndex: j]])
|
||||
{
|
||||
// We now fetch the c_partstates array and get the participation status of the user for the event
|
||||
r_partstates = [[r_o objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
|
||||
if (i < [r_partstates count])
|
||||
{
|
||||
// 0: needs action (considered busy)
|
||||
// 1: accepted (busy)
|
||||
// 2: declined (free)
|
||||
// 3: tentative (free)
|
||||
// 4: delegated (free)
|
||||
r_hasDelegated = ([[r_partstates objectAtIndex: j] intValue] == 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(r_hasDelegated)
|
||||
nbOfDelegation++;
|
||||
}
|
||||
if([user numberOfSimultaneousBookings] > [fbInfo count] - nbOfDelegation)
|
||||
{
|
||||
//Resource can still accept reservation, continue the while statement
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
iCalCalendar *calendar;
|
||||
NSDictionary *values, *info;
|
||||
NSString *reason;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]];
|
||||
event = [[calendar events] lastObject];
|
||||
|
||||
values = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
|
||||
[user cn], @"Cn",
|
||||
[user systemEmail], @"SystemEmail",
|
||||
([event summary] ? [event summary] : @""), @"EventTitle",
|
||||
[formatter formattedDateAndTime: [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"]], @"StartDate",
|
||||
nil];
|
||||
|
||||
reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]];
|
||||
|
||||
info = [NSDictionary dictionaryWithObject: reason forKey: @"reject"];
|
||||
|
||||
return [self exceptionWithHTTPStatus: 409
|
||||
reason: [info jsonRepresentation]];
|
||||
}
|
||||
}
|
||||
//
|
||||
// We are dealing with a normal attendee. Lets check if we have conflicts, unless
|
||||
// we are being asked to force the save anyway
|
||||
//
|
||||
if (!forceSave && !_resourceHasAutoAccepted)
|
||||
{
|
||||
NSMutableDictionary *info;
|
||||
NSMutableArray *conflicts;
|
||||
NSArray *emails, *c_names, *partstates;
|
||||
NSString *formattedEnd;
|
||||
SOGoUser *ownerUser;
|
||||
id o;
|
||||
BOOL hasDelegated = NO;
|
||||
|
||||
info = [NSMutableDictionary dictionary];
|
||||
conflicts = [NSMutableArray array];
|
||||
|
||||
if (currentAttendee)
|
||||
{
|
||||
if ([currentAttendee cn])
|
||||
[info setObject: [currentAttendee cn] forKey: @"attendee_name"];
|
||||
if ([currentAttendee rfc822Email])
|
||||
[info setObject: [currentAttendee rfc822Email] forKey: @"attendee_email"];
|
||||
}
|
||||
else if ([owner isEqualToString: currentUID])
|
||||
{
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
if ([ownerUser cn])
|
||||
[info setObject: [ownerUser cn] forKey: @"attendee_name"];
|
||||
if ([ownerUser systemEmail])
|
||||
[info setObject: [ownerUser systemEmail] forKey: @"attendee_email"];
|
||||
}
|
||||
|
||||
for (i = 0; i < [fbInfo count]; i++)
|
||||
{
|
||||
o = [fbInfo objectAtIndex: i];
|
||||
//Check if the user has delegated the event
|
||||
hasDelegated = NO;
|
||||
emails = [[o objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
|
||||
c_names = [[o objectForKey: @"c_participants"] componentsSeparatedByString: @"\n"];
|
||||
for (j = 0; j < [emails count]; j++)
|
||||
{
|
||||
if ([[info objectForKey:@"attendee_email"] isEqualToString: [emails objectAtIndex: j]] ||
|
||||
[[info objectForKey:@"attendee_name"] isEqualToString: [c_names objectAtIndex: j]])
|
||||
{
|
||||
// We now fetch the c_partstates array and get the participation status of the user for the event
|
||||
partstates = [[o objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
|
||||
if (i < [partstates count])
|
||||
{
|
||||
// 0: needs action (considered busy)
|
||||
// 1: accepted (busy)
|
||||
// 2: declined (free)
|
||||
// 3: tentative (free)
|
||||
// 4: delegated (free)
|
||||
hasDelegated = ([[partstates objectAtIndex: j] intValue] == 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(hasDelegated)
|
||||
continue;
|
||||
|
||||
end = [o objectForKey: @"endDate"];
|
||||
if ([[o objectForKey: @"startDate"] isDateOnSameDay: end])
|
||||
formattedEnd = [formatter formattedTime: end];
|
||||
else
|
||||
formattedEnd = [formatter formattedDateAndTime: end];
|
||||
|
||||
[conflicts addObject: [NSDictionary dictionaryWithObjectsAndKeys: [formatter formattedDateAndTime: [o objectForKey: @"startDate"]], @"startDate",
|
||||
formattedEnd, @"endDate", nil]];
|
||||
}
|
||||
|
||||
if([conflicts count] > 0)
|
||||
{
|
||||
[info setObject: conflicts forKey: @"conflicts"];
|
||||
|
||||
// We immediately raise an exception, without processing the possible other attendees.
|
||||
return [self exceptionWithHTTPStatus: 409
|
||||
reason: [info jsonRepresentation]];
|
||||
}
|
||||
}
|
||||
} // if ([fbInfo count]) ...
|
||||
else if (currentAttendee && [user isResource])
|
||||
{
|
||||
// No conflict, we auto-accept. We do this for resources automatically if no
|
||||
// double-booking is observed. If it's not the desired behavior, just don't
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -806,6 +806,7 @@
|
||||
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
dd = [ownerUser domainDefaults];
|
||||
|
||||
if ([dd appointmentSendEMailNotifications] && [object isStillRelevant])
|
||||
{
|
||||
count = [attendees count];
|
||||
@@ -1033,6 +1034,8 @@
|
||||
to: (iCalPerson *) recipient
|
||||
{
|
||||
SOGoDomainDefaults *dd;
|
||||
SOGoUserSettings *settings;
|
||||
BOOL canSendNotification, doNotSendNotificationOnCalDav;
|
||||
|
||||
// We never send IMIP reply when the "initiator" is Outlook 2013/2016 over
|
||||
// the EAS protocol. That is because Outlook will always issue a SendMail command
|
||||
@@ -1042,8 +1045,15 @@
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
|
||||
return;
|
||||
|
||||
canSendNotification = NO;
|
||||
settings = [from userSettings];
|
||||
doNotSendNotificationOnCalDav = ([settings objectForKey: @"Calendar"]
|
||||
&& [[settings objectForKey: @"Calendar"] objectForKey: @"InvitationNotificationsOnCalDavDisabled"]
|
||||
&& 1 == [[[settings objectForKey: @"Calendar"] objectForKey: @"InvitationNotificationsOnCalDavDisabled"] intValue]);
|
||||
canSendNotification = !doNotSendNotificationOnCalDav || !(doNotSendNotificationOnCalDav && [[context request] isSoWebDAVRequest]);
|
||||
|
||||
dd = [from domainDefaults];
|
||||
if ([dd appointmentSendEMailNotifications] && [event isStillRelevant])
|
||||
if ([dd appointmentSendEMailNotifications] && [event isStillRelevant] && canSendNotification)
|
||||
{
|
||||
// We first send to the real recipient (organizer)
|
||||
[self _sendIMIPReplyForEvent: event
|
||||
|
||||
@@ -12,9 +12,9 @@ vtodo_class2 = "(Zaupno opravilo)";
|
||||
"The event \"%{Summary}\" was created" = "Dogodek \"%{Summary}\" je bil ustvarjen";
|
||||
"The event \"%{Summary}\" was deleted" = "Dogodek \"%{Summary}\" je bil izbrisan";
|
||||
"The event \"%{Summary}\" was updated" = "Dogodek \"%{Summary}\" je bil posodobljen";
|
||||
"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" = "Opravilo \"%{Summary}\" je bilo ustvarjeno";
|
||||
"The task \"%{Summary}\" was deleted" = "Opravilo \"%{Summary}\" je bilo izbrisano";
|
||||
"The task \"%{Summary}\" was updated" = "Opravilo \"%{Summary}\" je bilo posodobljeno";
|
||||
"The following attendees(s) were notified" = "Naslednji udeleženci so bili obveščeni";
|
||||
"The following attendees(s) were added" = "Naslednji udeleženci so bili dodani";
|
||||
"The following attendees(s) were removed" = "Naslednji udeleženci so bili odstranjeni";
|
||||
|
||||
@@ -127,6 +127,11 @@
|
||||
return [source listRequiresDot];
|
||||
}
|
||||
|
||||
- (BOOL) globalAddressBookFirstEntriesCount
|
||||
{
|
||||
return [source globalAddressBookFirstEntriesCount];
|
||||
}
|
||||
|
||||
- (NSString *) groupDavResourceType
|
||||
{
|
||||
return @"vcard-collection";
|
||||
@@ -433,45 +438,33 @@
|
||||
{
|
||||
NSArray *records, *result;
|
||||
EOSortOrdering *ordering;
|
||||
BOOL processed;
|
||||
|
||||
result = nil;
|
||||
processed = NO;
|
||||
|
||||
[source setListRequiresDot: NO];
|
||||
if ( ![source listRequiresDot])
|
||||
{
|
||||
if ([filter length] > 0) {
|
||||
records = [source fetchContactsMatching: filter
|
||||
withCriteria: criteria
|
||||
inDomain: domain];
|
||||
processed = YES;
|
||||
} else if ([[SOGoSystemDefaults sharedSystemDefaults] globalAddressBookFirstEntriesEnabled]) {
|
||||
records = [source fetchContactsMatching: filter
|
||||
withCriteria: criteria
|
||||
inDomain: domain
|
||||
limit: [[SOGoSystemDefaults sharedSystemDefaults] globalAddressBookFirstEntriesCount]];
|
||||
processed = YES;
|
||||
}
|
||||
if (![source listRequiresDot]) {
|
||||
records = [source fetchContactsMatching: filter
|
||||
withCriteria: criteria
|
||||
inDomain: domain
|
||||
limit: [source globalAddressBookFirstEntriesCount]];
|
||||
} else {
|
||||
records = [source fetchContactsMatching: filter
|
||||
withCriteria: criteria
|
||||
inDomain: domain];
|
||||
|
||||
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]];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,9 @@ Mailer_RESOURCE_FILES += \
|
||||
SOGoMailUkrainianForward.wo \
|
||||
SOGoMailUkrainianReply.wo \
|
||||
SOGoMailWelshForward.wo \
|
||||
SOGoMailWelshReply.wo
|
||||
SOGoMailWelshReply.wo \
|
||||
SOGoMailBasqueReply.wo \
|
||||
SOGoMailBasqueForward.wo
|
||||
|
||||
Mailer_LANGUAGES = $(SOGO_LANGUAGES)
|
||||
|
||||
|
||||
@@ -264,59 +264,61 @@
|
||||
{
|
||||
// We check if we see <meta ...> in which case, we substitute de charset= stuff.
|
||||
if (i < len-5)
|
||||
{
|
||||
if ((*bytes == '<') &&
|
||||
(*(bytes+1) == 'm' || *(bytes+1) == 'M') &&
|
||||
(*(bytes+2) == 'e' || *(bytes+2) == 'E') &&
|
||||
(*(bytes+3) == 't' || *(bytes+3) == 'T') &&
|
||||
(*(bytes+4) == 'a' || *(bytes+4) == 'A') &&
|
||||
(*(bytes+5) == ' '))
|
||||
in_meta = YES;
|
||||
}
|
||||
{
|
||||
if ((*bytes == '<') &&
|
||||
(*(bytes+1) == 'm' || *(bytes+1) == 'M') &&
|
||||
(*(bytes+2) == 'e' || *(bytes+2) == 'E') &&
|
||||
(*(bytes+3) == 't' || *(bytes+3) == 'T') &&
|
||||
(*(bytes+4) == 'a' || *(bytes+4) == 'A') &&
|
||||
(*(bytes+5) == ' '))
|
||||
in_meta = YES;
|
||||
}
|
||||
|
||||
// We search for something like :
|
||||
//
|
||||
// <meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
|
||||
//
|
||||
if (in_meta && i < len-9)
|
||||
{
|
||||
if ((*bytes == 'c' || *bytes == 'C') &&
|
||||
(*(bytes+1) == 'h' || *(bytes+1) == 'H') &&
|
||||
(*(bytes+2) == 'a' || *(bytes+2) == 'A') &&
|
||||
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
||||
(*(bytes+4) == 's' || *(bytes+4) == 'S') &&
|
||||
(*(bytes+5) == 'e' || *(bytes+5) == 'E') &&
|
||||
(*(bytes+6) == 't' || *(bytes+6) == 'T') &&
|
||||
(*(bytes+7) == '='))
|
||||
{
|
||||
// We search until we find a '"' or a space
|
||||
j = 8;
|
||||
found_delimiter = YES;
|
||||
{
|
||||
if ((*bytes == 'c' || *bytes == 'C') &&
|
||||
(*(bytes+1) == 'h' || *(bytes+1) == 'H') &&
|
||||
(*(bytes+2) == 'a' || *(bytes+2) == 'A') &&
|
||||
(*(bytes+3) == 'r' || *(bytes+3) == 'R') &&
|
||||
(*(bytes+4) == 's' || *(bytes+4) == 'S') &&
|
||||
(*(bytes+5) == 'e' || *(bytes+5) == 'E') &&
|
||||
(*(bytes+6) == 't' || *(bytes+6) == 'T') &&
|
||||
(*(bytes+7) == '='))
|
||||
{
|
||||
// We search until we find a '"' or a space
|
||||
j = 8;
|
||||
found_delimiter = YES;
|
||||
|
||||
while (*(bytes+j) != ' ' && *(bytes+j) != '"' && *(bytes+j) != '\'')
|
||||
{
|
||||
j++;
|
||||
while (*(bytes+j) != ' ' && *(bytes+j) != '"' && *(bytes+j) != '\'')
|
||||
{
|
||||
j++;
|
||||
|
||||
// We haven't found anything, let's return the data untouched
|
||||
if ((i+j) >= len)
|
||||
// We haven't found anything, let's return the data untouched
|
||||
if ((i+j) >= len)
|
||||
{
|
||||
in_meta = found_delimiter = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_delimiter)
|
||||
{
|
||||
[d replaceBytesInRange: NSMakeRange(i, j)
|
||||
withBytes: NULL
|
||||
length: 0];
|
||||
in_meta = found_delimiter = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_delimiter)
|
||||
{
|
||||
[d replaceBytesInRange: NSMakeRange(i, j)
|
||||
withBytes: NULL
|
||||
length: 0];
|
||||
in_meta = found_delimiter = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bytes++;
|
||||
i++;
|
||||
|
||||
len = [d length];
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1089,9 +1089,8 @@ static NSString *userAgent = nil;
|
||||
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
|
||||
|
||||
[self setText: [sourceMail contentForReply]];
|
||||
|
||||
[self setHeaders: info];
|
||||
[self setIsHTML: [[ud mailComposeMessageType] isEqualToString: @"html"]];
|
||||
[self setSourceURL: [sourceMail imap4URLString]];
|
||||
@@ -1099,7 +1098,11 @@ static NSString *userAgent = nil;
|
||||
[self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]];
|
||||
[self setSourceFolderWithMailObject: sourceMail];
|
||||
|
||||
[self setText: [sourceMail contentForReply]];
|
||||
if ([[ud mailComposeMessageType] isEqualToString: @"html"]) {
|
||||
[self setText: [NSString stringWithFormat: @"<br/><br/>%@", [sourceMail contentForReply]]];
|
||||
} else {
|
||||
[self setText: [NSString stringWithFormat: @"\n\n%@", [sourceMail contentForReply]]];
|
||||
}
|
||||
shouldRetrieveAttachements = [[ud mailComposeMessageType] isEqualToString: @"html"];
|
||||
|
||||
if (shouldRetrieveAttachements) {
|
||||
@@ -1835,6 +1838,7 @@ static NSString *userAgent = nil;
|
||||
NSString *s, *dateString;
|
||||
NGMutableHashMap *map;
|
||||
id emails, from, replyTo;
|
||||
BOOL addXForward = NO;
|
||||
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
|
||||
|
||||
@@ -1881,16 +1885,14 @@ static NSString *userAgent = nil;
|
||||
[map addObject: userAgent forKey: @"User-Agent"];
|
||||
|
||||
/* add custom headers */
|
||||
if ([(s = [[context request] headerForKey:@"x-webobjects-remote-host"]) length] > 0 &&
|
||||
addXForward = ![[[context activeUser] domainDefaults] mailDisableXForward];
|
||||
if (addXForward && [(s = [[context request] headerForKey:@"x-webobjects-remote-host"]) length] > 0 &&
|
||||
[s compare: @"localhost"] != NSOrderedSame)
|
||||
[map addObject: s
|
||||
forKey: @"X-Forward"];
|
||||
[map addObject: s forKey: @"X-Forward"];
|
||||
if ([(s = [headers objectForKey: @"X-Priority"]) length] > 0)
|
||||
[map setObject: s
|
||||
forKey: @"X-Priority"];
|
||||
[map setObject: s forKey: @"X-Priority"];
|
||||
if ([(s = [headers objectForKey: @"Disposition-Notification-To"]) length] > 0)
|
||||
[map setObject: s
|
||||
forKey: @"Disposition-Notification-To"];
|
||||
[map setObject: s forKey: @"Disposition-Notification-To"];
|
||||
|
||||
[self _addHeaders: _headers toHeaderMap: map];
|
||||
|
||||
@@ -2403,9 +2405,9 @@ static NSString *userAgent = nil;
|
||||
// - SOGoMailKeepDraftsAfterSend is not set;
|
||||
// - draft is successfully deleted;
|
||||
// - drafts mailbox exists.
|
||||
[self delete];
|
||||
if (!error &&
|
||||
![dd mailKeepDraftsAfterSend] &&
|
||||
![self delete] &&
|
||||
[imap4 doesMailboxExistAtURL: [container imap4URL]])
|
||||
[(SOGoDraftsFolder *) container expunge];
|
||||
|
||||
|
||||
+146
-144
@@ -454,19 +454,22 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
//
|
||||
- (NSString *) _folderType: (NSString *) folderName
|
||||
flags: (NSMutableArray *) flags
|
||||
folderTypes: (NSDictionary *) folderTypes
|
||||
{
|
||||
static NSDictionary *metadata = nil;
|
||||
NSString *folderType, *key, *rights;
|
||||
static NSDictionary *folderTypeDict = nil;
|
||||
NSString *folderType, *key, *rights, *flag;
|
||||
NSEnumerator *flagEnum;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
if (!metadata)
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
metadata = [[[self imap4Connection] allFoldersMetadataForURL: [self imap4URL]
|
||||
onlySubscribedFolders: [ud mailShowSubscribedFoldersOnly]]
|
||||
objectForKey: @"list"];
|
||||
[metadata retain];
|
||||
}
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
metadata = [[[self imap4Connection] allFoldersMetadataForURL: [self imap4URL]
|
||||
onlySubscribedFolders: [ud mailShowSubscribedFoldersOnly]]
|
||||
objectForKey: @"list"];
|
||||
[metadata retain];
|
||||
}
|
||||
|
||||
key = [NSString stringWithFormat: @"/%@", folderName];
|
||||
[flags addObjectsFromArray: [metadata objectForKey: key]];
|
||||
@@ -474,44 +477,32 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
// RFC6154 (https://tools.ietf.org/html/rfc6154) describes special uses for IMAP mailboxes.
|
||||
// We do honor them, as long as your SOGo{Drafts,Trash,Sent,Junk}FolderName are properly configured
|
||||
// See http://wiki.dovecot.org/MailboxSettings for a Dovecot example.
|
||||
if ([folderName isEqualToString: inboxFolderName])
|
||||
folderType = @"inbox";
|
||||
else if ([flags containsObject: [self draftsFolderNameInContext: context]] ||
|
||||
[folderName isEqualToString: [self draftsFolderNameInContext: context]])
|
||||
folderType = @"draft";
|
||||
else if ([flags containsObject: [self sentFolderNameInContext: context]] ||
|
||||
[folderName isEqualToString: [self sentFolderNameInContext: context]])
|
||||
folderType = @"sent";
|
||||
else if ([flags containsObject: [self trashFolderNameInContext: context]] ||
|
||||
[folderName isEqualToString: [self trashFolderNameInContext: context]])
|
||||
folderType = @"trash";
|
||||
else if ([flags containsObject: [self junkFolderNameInContext: context]] ||
|
||||
[folderName isEqualToString: [self junkFolderNameInContext: context]])
|
||||
folderType = @"junk";
|
||||
else if ([flags containsObject: [self templatesFolderNameInContext: context]] ||
|
||||
[folderName isEqualToString: [self templatesFolderNameInContext: context]])
|
||||
folderType = @"templates";
|
||||
else if ([folderName isEqualToString: otherUsersFolderName])
|
||||
folderType = @"otherUsers";
|
||||
else if ([folderName isEqualToString: sharedFoldersName])
|
||||
folderType = @"shared";
|
||||
else
|
||||
if((folderType = [folderTypes objectForKey: folderName]))
|
||||
return folderType;
|
||||
|
||||
flagEnum = [flags objectEnumerator];
|
||||
while((flag = [flagEnum nextObject]))
|
||||
{
|
||||
if([flag isEqualToString: inboxFolderName] || [flag isEqualToString: otherUsersFolderName] || [flag isEqualToString: sharedFoldersName])
|
||||
continue;
|
||||
if((folderType = [folderTypes objectForKey: flag]))
|
||||
return folderType;
|
||||
}
|
||||
|
||||
folderType = @"folder";
|
||||
if (([sharedFoldersName length] && [folderName hasPrefix: sharedFoldersName]) ||
|
||||
([otherUsersFolderName length] && [folderName hasPrefix: otherUsersFolderName]))
|
||||
{
|
||||
rights = [[self imap4Connection] myRightsForMailboxAtURL: [NSURL URLWithString: folderName]];
|
||||
if (![rights isKindOfClass: [NSException class]] &&
|
||||
([rights rangeOfString: @"r"].location == NSNotFound))
|
||||
{
|
||||
folderType = @"folder";
|
||||
if (([sharedFoldersName length] && [folderName hasPrefix: sharedFoldersName]) ||
|
||||
([otherUsersFolderName length] && [folderName hasPrefix: otherUsersFolderName]))
|
||||
{
|
||||
rights = [[self imap4Connection] myRightsForMailboxAtURL: [NSURL URLWithString: folderName]];
|
||||
if (![rights isKindOfClass: [NSException class]] &&
|
||||
([rights rangeOfString: @"r"].location == NSNotFound))
|
||||
{
|
||||
[flags addObjectUniquely: @"noselect"];
|
||||
if ([rights rangeOfString: @"i"].location != NSNotFound)
|
||||
// No read but insert = dropbox
|
||||
folderType = @"dropbox";
|
||||
}
|
||||
}
|
||||
[flags addObjectUniquely: @"noselect"];
|
||||
if ([rights rangeOfString: @"i"].location != NSNotFound)
|
||||
// No read but insert = dropbox
|
||||
folderType = @"dropbox";
|
||||
}
|
||||
}
|
||||
|
||||
return folderType;
|
||||
}
|
||||
@@ -521,6 +512,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
//
|
||||
- (NSMutableDictionary *) _insertFolder: (NSString *) folderPath
|
||||
foldersList: (NSMutableArray *) theFolders
|
||||
folderTypes: (NSDictionary *) folderTypes
|
||||
{
|
||||
NSArray *pathComponents;
|
||||
NSMutableArray *folders, *flags;
|
||||
@@ -541,103 +533,99 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
// The variable folderPath is something like '/INBOX/Junk' so pathComponents becomes ('/', 'INBOX', 'Junk').
|
||||
// That's why we always ignore the first element
|
||||
for (i = 1; i < count; i++)
|
||||
{
|
||||
last = ((count - i) == 1);
|
||||
folder = nil;
|
||||
if ([currentPath length])
|
||||
currentPath = [NSString stringWithFormat: @"%@/%@", currentPath, [pathComponents objectAtIndex: i]];
|
||||
else
|
||||
currentPath = [pathComponents objectAtIndex: i];
|
||||
|
||||
// Search for the current path in the children of the parent folder.
|
||||
// For the first iteration, take the parent folder passed as argument.
|
||||
if (parentFolder)
|
||||
folders = [parentFolder objectForKey: @"children"];
|
||||
else
|
||||
folders = theFolders;
|
||||
|
||||
for (j = 0; j < [folders count]; j++)
|
||||
{
|
||||
last = ((count - i) == 1);
|
||||
folder = nil;
|
||||
if ([currentPath length])
|
||||
currentPath = [NSString stringWithFormat: @"%@/%@", currentPath, [pathComponents objectAtIndex: i]];
|
||||
else
|
||||
currentPath = [pathComponents objectAtIndex: i];
|
||||
|
||||
// Search for the current path in the children of the parent folder.
|
||||
// For the first iteration, take the parent folder passed as argument.
|
||||
if (parentFolder)
|
||||
folders = [parentFolder objectForKey: @"children"];
|
||||
else
|
||||
folders = theFolders;
|
||||
|
||||
for (j = 0; j < [folders count]; j++)
|
||||
{
|
||||
currentFolder = [folders objectAtIndex: j];
|
||||
if ([currentPath isEqualToString: [currentFolder objectForKey: @"path"]])
|
||||
{
|
||||
folder = currentFolder;
|
||||
// Make sure all branches are ready to receive children
|
||||
if (!last && ![folder objectForKey: @"children"])
|
||||
[folder setObject: [NSMutableArray array] forKey: @"children"];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current folder is the "Other users" folder (shared mailboxes)
|
||||
currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName];
|
||||
if (otherUsersFolderName
|
||||
&& [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame)
|
||||
isOtherUsersFolder = YES;
|
||||
else
|
||||
isOtherUsersFolder = NO;
|
||||
|
||||
if (folder == nil)
|
||||
{
|
||||
// Folder was not found; create it and add it to the folders list
|
||||
if (parentIsOtherUsersFolder)
|
||||
{
|
||||
// Parent folder is the "Other users" folder; translate the user's mailbox name
|
||||
// to the full name of the person
|
||||
userManager = [SOGoUserManager sharedUserManager];
|
||||
if ((fullName = [userManager getCNForUID: currentFolderName]) && [fullName length])
|
||||
currentFolderName = fullName;
|
||||
else if ((fullName = [userManager getEmailForUID: currentFolderName]) && [fullName length])
|
||||
currentFolderName = fullName;
|
||||
}
|
||||
else if (isOtherUsersFolder)
|
||||
currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
|
||||
else if (sharedFoldersName
|
||||
&& [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame)
|
||||
currentFolderName = [self labelForKey: @"SharedFoldersName"];
|
||||
|
||||
flags = [NSMutableArray array];
|
||||
|
||||
if (last)
|
||||
{
|
||||
folderType = [self _folderType: currentPath
|
||||
flags: flags];
|
||||
}
|
||||
else
|
||||
{
|
||||
folderType = @"additional";
|
||||
[flags addObject: @"noselect"];
|
||||
}
|
||||
|
||||
if ([subscribedFolders objectForKey: folderPath])
|
||||
isSubscribed = YES;
|
||||
else
|
||||
isSubscribed = NO;
|
||||
|
||||
folder = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
currentPath, @"path",
|
||||
folderType, @"type",
|
||||
currentFolderName, @"name",
|
||||
[NSMutableArray array], @"children",
|
||||
flags, @"flags",
|
||||
[NSNumber numberWithBool: isSubscribed], @"subscribed",
|
||||
nil];
|
||||
|
||||
if (sieveFolderUTF8Encoding)
|
||||
sievePath = [currentPath stringByDecodingImap4FolderName];
|
||||
else
|
||||
sievePath = currentPath;
|
||||
[folder setObject: sievePath forKey: @"sievePath"];
|
||||
|
||||
// Either add this new folder to its parent or the list of root folders
|
||||
[folders addObject: folder];
|
||||
}
|
||||
|
||||
parentFolder = folder;
|
||||
parentIsOtherUsersFolder = isOtherUsersFolder;
|
||||
currentFolder = [folders objectAtIndex: j];
|
||||
if ([currentPath isEqualToString: [currentFolder objectForKey: @"path"]])
|
||||
{
|
||||
folder = currentFolder;
|
||||
// Make sure all branches are ready to receive children
|
||||
if (!last && ![folder objectForKey: @"children"])
|
||||
[folder setObject: [NSMutableArray array] forKey: @"children"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current folder is the "Other users" folder (shared mailboxes)
|
||||
currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName];
|
||||
if (otherUsersFolderName && [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame)
|
||||
isOtherUsersFolder = YES;
|
||||
else
|
||||
isOtherUsersFolder = NO;
|
||||
|
||||
if (folder == nil)
|
||||
{
|
||||
// Folder was not found; create it and add it to the folders list
|
||||
if (parentIsOtherUsersFolder)
|
||||
{
|
||||
// Parent folder is the "Other users" folder; translate the user's mailbox name
|
||||
// to the full name of the person
|
||||
userManager = [SOGoUserManager sharedUserManager];
|
||||
if ((fullName = [userManager getCNForUID: currentFolderName]) && [fullName length])
|
||||
currentFolderName = fullName;
|
||||
else if ((fullName = [userManager getEmailForUID: currentFolderName]) && [fullName length])
|
||||
currentFolderName = fullName;
|
||||
}
|
||||
else if (isOtherUsersFolder)
|
||||
currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
|
||||
else if (sharedFoldersName && [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame)
|
||||
currentFolderName = [self labelForKey: @"SharedFoldersName"];
|
||||
|
||||
flags = [NSMutableArray array];
|
||||
|
||||
if (last)
|
||||
{
|
||||
folderType = [self _folderType: currentPath flags: flags folderTypes: folderTypes];
|
||||
}
|
||||
else
|
||||
{
|
||||
folderType = @"additional";
|
||||
[flags addObject: @"noselect"];
|
||||
}
|
||||
|
||||
if ([subscribedFolders objectForKey: folderPath])
|
||||
isSubscribed = YES;
|
||||
else
|
||||
isSubscribed = NO;
|
||||
|
||||
folder = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
currentPath, @"path",
|
||||
folderType, @"type",
|
||||
currentFolderName, @"name",
|
||||
[NSMutableArray array], @"children",
|
||||
flags, @"flags",
|
||||
[NSNumber numberWithBool: isSubscribed], @"subscribed",
|
||||
nil];
|
||||
|
||||
if (sieveFolderUTF8Encoding)
|
||||
sievePath = [currentPath stringByDecodingImap4FolderName];
|
||||
else
|
||||
sievePath = currentPath;
|
||||
[folder setObject: sievePath forKey: @"sievePath"];
|
||||
|
||||
// Either add this new folder to its parent or the list of root folders
|
||||
[folders addObject: folder];
|
||||
}
|
||||
|
||||
parentFolder = folder;
|
||||
parentIsOtherUsersFolder = isOtherUsersFolder;
|
||||
}
|
||||
|
||||
return parentFolder;
|
||||
}
|
||||
|
||||
@@ -651,11 +639,25 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
NSEnumerator *rawFolders;
|
||||
NSAutoreleasePool *pool;
|
||||
NSArray *allFolderPaths;
|
||||
NSMutableDictionary *folderTypes;
|
||||
|
||||
allFolderPaths = [self allFolderPaths: theListingMode];
|
||||
rawFolders = [allFolderPaths objectEnumerator];
|
||||
folders = [NSMutableArray array];
|
||||
|
||||
folderTypes = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
@"inbox", inboxFolderName,
|
||||
@"draft", [self draftsFolderNameInContext: context],
|
||||
@"sent", [self sentFolderNameInContext: context],
|
||||
@"trash", [self trashFolderNameInContext: context],
|
||||
@"junk", [self junkFolderNameInContext: context],
|
||||
@"templates", [self templatesFolderNameInContext: context],
|
||||
nil];
|
||||
if(otherUsersFolderName && [otherUsersFolderName length] > 0)
|
||||
[folderTypes setObject: @"otherUsers" forKey: otherUsersFolderName];
|
||||
if(sharedFoldersName && [sharedFoldersName length] > 0)
|
||||
[folderTypes setObject: @"shared" forKey: sharedFoldersName];
|
||||
|
||||
while ((currentFolder = [rawFolders nextObject]))
|
||||
{
|
||||
// Using a local pool to avoid using too many file descriptors. This could
|
||||
@@ -666,7 +668,8 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
|
||||
// Insert folder into folders tree
|
||||
[self _insertFolder: currentFolder
|
||||
foldersList: folders];
|
||||
foldersList: folders
|
||||
folderTypes: folderTypes];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
@@ -1119,11 +1122,10 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
folderName = [[account objectForKey: @"specialMailboxes"]
|
||||
objectForKey: purpose];
|
||||
if (!folderName && accountIdx > 0)
|
||||
{
|
||||
account = [accounts objectAtIndex: 0];
|
||||
folderName = [[account objectForKey: @"specialMailboxes"]
|
||||
objectForKey: purpose];
|
||||
}
|
||||
{
|
||||
account = [accounts objectAtIndex: 0];
|
||||
folderName = [[account objectForKey: @"specialMailboxes"] objectForKey: purpose];
|
||||
}
|
||||
|
||||
return folderName;
|
||||
}
|
||||
|
||||
@@ -500,49 +500,55 @@ static BOOL debugOn = NO;
|
||||
|
||||
error = [self matchesRequestConditionInContext: localContext];
|
||||
if (error)
|
||||
{
|
||||
response = error; /* return 304 or 416 */
|
||||
}
|
||||
{
|
||||
response = error; /* return 304 or 416 */
|
||||
}
|
||||
else
|
||||
{
|
||||
// [self debugWithFormat: @"should fetch body part: %@",
|
||||
{
|
||||
// [self debugWithFormat: @"should fetch body part: %@",
|
||||
// [self bodyPartIdentifier]];
|
||||
data = [self fetchBLOB];
|
||||
if (data)
|
||||
{
|
||||
// [self debugWithFormat:@" fetched %d bytes: %@", [data length],
|
||||
// [self partInfo]];
|
||||
data = [self fetchBLOB];
|
||||
if (data)
|
||||
{
|
||||
// [self debugWithFormat:@" fetched %d bytes: %@", [data length],
|
||||
// [self partInfo]];
|
||||
|
||||
response = [localContext response];
|
||||
mimeType = [self davContentType];
|
||||
if ([mimeType isEqualToString: @"application/x-xpinstall"])
|
||||
mimeType = @"application/octet-stream";
|
||||
else if (!asAttachment)
|
||||
mimeType = [self contentTypeForBodyPartInfo: [self partInfo]];
|
||||
response = [localContext response];
|
||||
mimeType = [self davContentType];
|
||||
|
||||
[response setHeader: mimeType forKey: @"content-type"];
|
||||
[response setHeader: [NSString stringWithFormat:@"%d", (int)[data length]]
|
||||
forKey: @"content-length"];
|
||||
if ([mimeType isEqualToString: @"application/x-xpinstall"])
|
||||
mimeType = @"application/octet-stream";
|
||||
else if (!asAttachment)
|
||||
mimeType = [self contentTypeForBodyPartInfo: [self partInfo]];
|
||||
|
||||
if (asAttachment)
|
||||
{
|
||||
fileName = [self filename];
|
||||
if ([fileName length])
|
||||
[response setHeader: [NSString stringWithFormat: @"attachment; filename*=\"utf-8''%@\"",
|
||||
[fileName stringByEscapingURL]]
|
||||
forKey: @"content-disposition"];
|
||||
}
|
||||
|
||||
etag = [self davEntityTag];
|
||||
if (etag)
|
||||
[response setHeader: etag forKey: @"etag"];
|
||||
|
||||
[response setContent: data];
|
||||
}
|
||||
if(([mimeType rangeOfString:@"xml"].location != NSNotFound || [mimeType rangeOfString:@"html"].location != NSNotFound
|
||||
|| [mimeType rangeOfString:@"css"].location != NSNotFound || [mimeType rangeOfString:@"javascript"].location != NSNotFound)
|
||||
&& [mimeType rangeOfString:@"openxmlformats"].location == NSNotFound )
|
||||
[response setHeader: @"text/plain" forKey: @"content-type"];
|
||||
else
|
||||
response = [NSException exceptionWithHTTPStatus: 404 /* not found */
|
||||
reason: @"did not find body part"];
|
||||
}
|
||||
[response setHeader: mimeType forKey: @"content-type"];
|
||||
|
||||
[response setHeader: [NSString stringWithFormat:@"%d", (int)[data length]] forKey: @"content-length"];
|
||||
|
||||
if (asAttachment)
|
||||
{
|
||||
fileName = [self filename];
|
||||
if ([fileName length])
|
||||
[response setHeader: [NSString stringWithFormat: @"attachment; filename*=\"utf-8''%@\"",
|
||||
[fileName stringByEscapingURL]]
|
||||
forKey: @"content-disposition"];
|
||||
}
|
||||
|
||||
etag = [self davEntityTag];
|
||||
if (etag)
|
||||
[response setHeader: etag forKey: @"etag"];
|
||||
|
||||
[response setContent: data];
|
||||
}
|
||||
else
|
||||
response = [NSException exceptionWithHTTPStatus: 404 /* not found */
|
||||
reason: @"did not find body part"];
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@
|
||||
@interface SOGoMailGalicianForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
@interface SOGoMailBasqueForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
@interface SOGoMailGermanForward : SOGoMailForward
|
||||
@end
|
||||
|
||||
|
||||
@@ -339,6 +339,9 @@
|
||||
@implementation SOGoMailGalicianForward
|
||||
@end
|
||||
|
||||
@implementation SOGoMailBasqueForward
|
||||
@end
|
||||
|
||||
@implementation SOGoMailGermanForward
|
||||
@end
|
||||
|
||||
|
||||
File renamed without changes.
@@ -115,7 +115,7 @@
|
||||
NSData *data;
|
||||
|
||||
BOOL htmlComposition, htmlContent;
|
||||
NSUInteger index;
|
||||
NSUInteger index, indexTextPlain;
|
||||
|
||||
content = @"";
|
||||
|
||||
@@ -129,8 +129,20 @@
|
||||
if (htmlComposition)
|
||||
{
|
||||
// Prefer HTML content
|
||||
indexTextPlain = [types indexOfObject: @"text/plain"];
|
||||
index = [types indexOfObject: @"text/html"];
|
||||
if (index == NSNotFound)
|
||||
// Ticket https://bugs.sogo.nu/view.php?id=5983
|
||||
// In this case, the first HTML content is used, but it can be the previous forwarded mail
|
||||
// We check if there is a text/plain before text/html in the types array
|
||||
// is this case, the text/plain is used prior to text/html.
|
||||
if (index != NSNotFound // There is a text/html part
|
||||
&& indexTextPlain != NSNotFound // There is a text/plain part
|
||||
&& indexTextPlain < index // text/plain is before text/html
|
||||
&& (indexTextPlain + 1) < [keys count] // text/plain is not the last part of the mail
|
||||
&& [[[keys objectAtIndex: (indexTextPlain + 1)] objectForKey:@"mimeType"] rangeOfString:@"text/plain"].location != NSNotFound // The text/plain is followed up by another text/plain
|
||||
)
|
||||
index = indexTextPlain;
|
||||
else if (index == NSNotFound)
|
||||
index = [types indexOfObject: @"text/plain"];
|
||||
else
|
||||
htmlContent = YES;
|
||||
|
||||
@@ -84,6 +84,9 @@
|
||||
@interface SOGoMailGalicianReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
@interface SOGoMailBasqueReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
@interface SOGoMailGermanReply : SOGoMailReply
|
||||
@end
|
||||
|
||||
|
||||
@@ -140,6 +140,9 @@
|
||||
@implementation SOGoMailGalicianReply
|
||||
@end
|
||||
|
||||
@implementation SOGoMailBasqueReply
|
||||
@end
|
||||
|
||||
@implementation SOGoMailGermanReply
|
||||
@end
|
||||
|
||||
|
||||
@@ -277,6 +277,7 @@ unsigned char GetRruleMonthNum(unsigned char a, unsigned char b) {
|
||||
NSString *partName, *type, *subtype;
|
||||
NSString *value, *attendee;
|
||||
RTFHandler *handler;
|
||||
NGMimeBodyPart* eventPart;
|
||||
|
||||
DWORD signature;
|
||||
DDWORD *classification;
|
||||
@@ -664,9 +665,10 @@ unsigned char GetRruleMonthNum(unsigned char a, unsigned char b) {
|
||||
if (debugOn)
|
||||
NSLog(@"TNEF reconstructed vCalendar:\n%@", vcalendar);
|
||||
|
||||
[self bodyPartForData: [vcalendar dataUsingEncoding: NSUTF8StringEncoding]
|
||||
eventPart = [self bodyPartForData: [vcalendar dataUsingEncoding: NSUTF8StringEncoding]
|
||||
withType: @"text"
|
||||
andSubtype: @"calendar"];
|
||||
[self setPart: eventPart];
|
||||
|
||||
}
|
||||
// Other classes to handle:
|
||||
@@ -756,6 +758,17 @@ unsigned char GetRruleMonthNum(unsigned char a, unsigned char b) {
|
||||
{
|
||||
partName = [NSString stringWithUTF8String: (const char *)attachmentName->data];
|
||||
|
||||
if (!partName) {
|
||||
// Switch to different encoding
|
||||
partName = [[NSString alloc] initWithData: [NSData dataWithBytes: attachmentName->data length: strlen(attachmentName->data)] encoding:NSISOLatin1StringEncoding];
|
||||
[partName autorelease];
|
||||
}
|
||||
if (!partName) {
|
||||
// Fallback
|
||||
[self warnWithFormat: @"Could not get TNEF attachment name, fallback to unamed.bin"];
|
||||
partName = @"unnamed.bin";
|
||||
}
|
||||
|
||||
type = @"application";
|
||||
subtype = @"octet-stream";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"OtherUsersFolderName" = "Otros usuarios";
|
||||
"SharedFoldersName" = "Carpetas compartidas";
|
||||
|
||||
"Report: Marked messages as junk" = "Report: Marked messages as junk";
|
||||
"Report: Marked messages as not junk" = "Report: Marked messages as not junk";
|
||||
"Report: Marked messages as junk" = "Informe: Mensajes marcados como spam";
|
||||
"Report: Marked messages as not junk" = "Informe: Mensajes marcados como no spam";
|
||||
@@ -64,6 +64,9 @@
|
||||
NSArray *_bindFields;
|
||||
|
||||
BOOL _listRequiresDot;
|
||||
int _globalAddressBookFirstEntriesCount;
|
||||
|
||||
BOOL _disableSubgroups;
|
||||
|
||||
NSString *_domain;
|
||||
NSString *_contactInfoAttribute;
|
||||
|
||||
@@ -106,6 +106,9 @@ static Class NSStringK;
|
||||
_filter = nil;
|
||||
_userPasswordAlgorithm = nil;
|
||||
_listRequiresDot = YES;
|
||||
_globalAddressBookFirstEntriesCount = -1;
|
||||
|
||||
_disableSubgroups = NO;
|
||||
|
||||
_passwordPolicy = NO;
|
||||
_updateSambaNTLMPasswords = NO;
|
||||
@@ -170,7 +173,7 @@ static Class NSStringK;
|
||||
inDomain: (NSString *) sourceDomain
|
||||
{
|
||||
SOGoDomainDefaults *dd;
|
||||
NSNumber *udQueryLimit, *udQueryTimeout, *udGroupExpansionEnabled, *dotValue;
|
||||
NSNumber *udQueryLimit, *udQueryTimeout, *udGroupExpansionEnabled, *dotValue, *disableSubgroupsValue;
|
||||
|
||||
if ((self = [self init]))
|
||||
{
|
||||
@@ -200,10 +203,19 @@ static Class NSStringK;
|
||||
andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]];
|
||||
|
||||
dotValue = [udSource objectForKey: @"listRequiresDot"];
|
||||
if (dotValue)
|
||||
if (dotValue) {
|
||||
[self setListRequiresDot: [dotValue boolValue]];
|
||||
if ([udSource objectForKey: @"globalAddressBookFirstEntriesCount"])
|
||||
[self setGlobalAddressBookFirstEntriesCount: [[udSource objectForKey: @"globalAddressBookFirstEntriesCount"] intValue]];
|
||||
}
|
||||
|
||||
disableSubgroupsValue = [udSource objectForKey: @"disableSubgroups"];
|
||||
if (disableSubgroupsValue)
|
||||
_disableSubgroups = [disableSubgroupsValue boolValue];
|
||||
|
||||
[self setContactMapping: [udSource objectForKey: @"mapping"]
|
||||
andObjectClasses: [udSource objectForKey: @"objectClasses"]];
|
||||
|
||||
|
||||
[self setModifiers: [udSource objectForKey: @"modifiers"]];
|
||||
ASSIGN(_abOU, [udSource objectForKey: @"abOU"]);
|
||||
@@ -400,6 +412,16 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
return _listRequiresDot;
|
||||
}
|
||||
|
||||
- (void) setGlobalAddressBookFirstEntriesCount: (int)value
|
||||
{
|
||||
_globalAddressBookFirstEntriesCount = value;
|
||||
}
|
||||
|
||||
- (int) globalAddressBookFirstEntriesCount
|
||||
{
|
||||
return _globalAddressBookFirstEntriesCount;
|
||||
}
|
||||
|
||||
- (NSArray *) searchFields
|
||||
{
|
||||
return _searchFields;
|
||||
@@ -2339,16 +2361,21 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
|
||||
if ([contactInfos objectForKey: @"isGroup"])
|
||||
if (!_disableSubgroups) {
|
||||
contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
|
||||
if ([contactInfos objectForKey: @"isGroup"])
|
||||
{
|
||||
subusers = [self membersForGroupWithUID: login];
|
||||
[members addObjectsFromArray: subusers];
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
[members addObject: user];
|
||||
}
|
||||
} else {
|
||||
[members addObject: user];
|
||||
}
|
||||
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
@@ -2361,16 +2388,20 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
|
||||
if ([contactInfos objectForKey: @"isGroup"])
|
||||
if (!_disableSubgroups) {
|
||||
contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
|
||||
if ([contactInfos objectForKey: @"isGroup"])
|
||||
{
|
||||
subusers = [self membersForGroupWithUID: login];
|
||||
[members addObjectsFromArray: subusers];
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
[members addObject: user];
|
||||
}
|
||||
} else {
|
||||
[members addObject: user];
|
||||
}
|
||||
}
|
||||
[pool release];
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ static const NSString *kAES256GCMError = @"kAES256GCMError";
|
||||
value = nil;
|
||||
|
||||
if (AES_128_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_128_KEY_SIZE * 8)] userInfo: nil];
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits, but key has %d bits", (AES_128_KEY_SIZE * 8), ([passwordScheme length] * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ static const NSString *kAES256GCMError = @"kAES256GCMError";
|
||||
#ifdef HAVE_OPENSSL
|
||||
|
||||
if (AES_128_KEY_SIZE != [passwordScheme length]) {
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits", (AES_128_KEY_SIZE * 8)] userInfo: nil];
|
||||
*ex = [NSException exceptionWithName: kAES128ECError reason: [NSString stringWithFormat:@"Key must be %d bits, but key has %d bits", (AES_128_KEY_SIZE * 8), ([passwordScheme length] * 8)] userInfo: nil];
|
||||
return nil;
|
||||
}
|
||||
keyData = [passwordScheme dataUsingEncoding: NSUTF8StringEncoding];
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
- (BOOL) mailCustomFromEnabled;
|
||||
- (BOOL) mailAuxiliaryUserAccountsEnabled;
|
||||
|
||||
- (BOOL) mailDisableXForward;
|
||||
- (NSString *) mailDomain;
|
||||
- (NSString *) imapServer;
|
||||
- (NSString *) sieveServer;
|
||||
@@ -101,6 +102,8 @@
|
||||
|
||||
- (NSString *) passwordRecoveryFrom;
|
||||
|
||||
- (BOOL) forceRawHtmlSignature;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGODOMAINDEFAULTS_H */
|
||||
@@ -106,6 +106,11 @@
|
||||
return [self boolForKey: @"SOGoMailAuxiliaryUserAccountsEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) mailDisableXForward
|
||||
{
|
||||
return [self boolForKey: @"SOGoMailDisableXForward"];
|
||||
}
|
||||
|
||||
- (NSString *) mailDomain
|
||||
{
|
||||
return [self stringForKey: @"SOGoMailDomain"];
|
||||
@@ -460,4 +465,9 @@
|
||||
return emailFrom;
|
||||
}
|
||||
|
||||
- (BOOL) forceRawHtmlSignature
|
||||
{
|
||||
return [self objectForKey: @"SOGoForceRawHtmlSignature"] ? [self boolForKey: @"SOGoForceRawHtmlSignature"] : YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1399,6 +1399,10 @@
|
||||
}
|
||||
else if ([nodeName isEqualToString: @"set-roles"])
|
||||
{
|
||||
// Disable Acl modifications if this is not the owner
|
||||
if (![self ignoreRights])
|
||||
return nil;
|
||||
|
||||
// We support two ways of setting roles. The first one is, for example:
|
||||
//
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -1425,7 +1429,7 @@
|
||||
|
||||
allRoles = [self _davGetRolesFromRequest: node];
|
||||
for (i = 0; i < [allUsers count]; i++)
|
||||
{
|
||||
{
|
||||
[self setRoles: allRoles
|
||||
forUser: [allUsers objectAtIndex: i]];
|
||||
}
|
||||
@@ -1446,6 +1450,9 @@
|
||||
//
|
||||
else if ([nodeName isEqualToString: @"add-user"])
|
||||
{
|
||||
// Disable Acl modifications if this is not the owner
|
||||
if (![self ignoreRights])
|
||||
return nil;
|
||||
attrs = [node attributes];
|
||||
userAttr = [attrs namedItem: @"user"];
|
||||
user = [userAttr nodeValue];
|
||||
@@ -1454,6 +1461,9 @@
|
||||
}
|
||||
else if ([nodeName isEqualToString: @"add-users"])
|
||||
{
|
||||
// Disable Acl modifications if this is not the owner
|
||||
if (![self ignoreRights])
|
||||
return nil;
|
||||
attrs = [node attributes];
|
||||
userAttr = [attrs namedItem: @"users"];
|
||||
allUsers = [[userAttr nodeValue] componentsSeparatedByString: @","];
|
||||
@@ -1474,6 +1484,9 @@
|
||||
//
|
||||
else if ([nodeName isEqualToString: @"remove-user"])
|
||||
{
|
||||
// Disable Acl modifications if this is not the owner
|
||||
if (![self ignoreRights])
|
||||
return nil;
|
||||
attrs = [node attributes];
|
||||
userAttr = [attrs namedItem: @"user"];
|
||||
user = [userAttr nodeValue];
|
||||
|
||||
@@ -1243,12 +1243,10 @@ static NSString *sieveScriptName = @"sogo";
|
||||
if (v && [v length] > 0)
|
||||
{
|
||||
notify = @"if header :matches \"subject\" \"*\" {\r\n set \"subject\" \"${1}\";\r\n}\r\n";
|
||||
notify = [notify stringByAppendingString: @"if header :matches \"From\" \"*<*>\" {\r\n set \"from\" \"${1} ${2}\";\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];
|
||||
notify = [notify stringByAppendingFormat: @"notify :message \"%@: '${subject}' from: ${from}\" \"mailto:%@?body=${body_param}\";\r\n", notificationTranslated, v];
|
||||
|
||||
// if (alwaysSend)
|
||||
// [script insertString: notify atIndex: 0];
|
||||
// else
|
||||
[script appendString: notify];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
/* requires a "." to obtain the full list of contacts */
|
||||
- (void) setListRequiresDot: (BOOL) aBool;
|
||||
- (BOOL) listRequiresDot;
|
||||
- (void) setGlobalAddressBookFirstEntriesCount: (int)value;
|
||||
- (int) globalAddressBookFirstEntriesCount;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
|
||||
@@ -36,6 +36,8 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
|
||||
+ (SOGoSystemDefaults *) sharedSystemDefaults;
|
||||
|
||||
- (NSArray *) domainIds;
|
||||
- (BOOL) forbidUnknownDomainsAuth;
|
||||
- (NSArray *) domainsAllowed;
|
||||
- (BOOL) enableDomainBasedUID;
|
||||
- (NSArray *) loginDomains;
|
||||
- (NSArray *) visibleDomainsForDomain: (NSString *) domain;
|
||||
@@ -135,9 +137,6 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
|
||||
- (NSArray *) disableSharingAnyAuthUser;
|
||||
- (NSArray *) disableExport;
|
||||
|
||||
- (BOOL) enableGlobalAddressBookFirstEntries;
|
||||
- (int) globalAddressBookFirstEntriesCount;
|
||||
|
||||
- (BOOL)isURLEncryptionEnabled;
|
||||
- (NSString *)urlEncryptionPassphrase;
|
||||
|
||||
|
||||
@@ -258,7 +258,8 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
|
||||
- (NSArray *) domainIds
|
||||
{
|
||||
return [[self dictionaryForKey: @"domains"] allKeys];
|
||||
NSDictionary *domains = [self dictionaryForKey: @"domains"];
|
||||
return [domains allKeys];
|
||||
}
|
||||
|
||||
- (BOOL) enableDomainBasedUID
|
||||
@@ -266,6 +267,16 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return [self boolForKey: @"SOGoEnableDomainBasedUID"];
|
||||
}
|
||||
|
||||
- (BOOL) forbidUnknownDomainsAuth
|
||||
{
|
||||
return [self boolForKey: @"SOGoForbidUnknownDomainsAuth"];
|
||||
}
|
||||
|
||||
- (NSArray *) domainsAllowed
|
||||
{
|
||||
return [NSMutableArray arrayWithArray: [self stringArrayForKey: @"SOGoDomainAllowed"]];
|
||||
}
|
||||
|
||||
- (NSArray *) loginDomains
|
||||
{
|
||||
NSMutableArray *filteredLoginDomains;
|
||||
@@ -917,21 +928,4 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
|
||||
return disableExport;
|
||||
}
|
||||
|
||||
- (BOOL) globalAddressBookFirstEntriesEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoGlobalAddressBookFirstEntries"];
|
||||
}
|
||||
|
||||
- (int) globalAddressBookFirstEntriesCount
|
||||
{
|
||||
int v;
|
||||
|
||||
v = [self integerForKey: @"SOGoGlobalAddressBookFirstEntriesCount"];
|
||||
|
||||
if (!v)
|
||||
v = 100;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -113,7 +113,9 @@
|
||||
- (unsigned int) weekNumberForDate: (NSCalendarDate *) date;
|
||||
|
||||
- (NSArray *) mailAccounts;
|
||||
- (NSArray *) mailAccountsNoRawHtmlSignature;
|
||||
- (NSArray *) mailAccountsWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities;
|
||||
- (NSArray *)mailAccountsWithDelegatedIdentities:(BOOL)appendDeletegatedIdentities forceRawHtmlSignature:(BOOL)forceRawHtmlSignature;
|
||||
- (NSDictionary *) accountWithName: (NSString *) accountName;
|
||||
- (NSArray *) allIdentities;
|
||||
- (NSDictionary *) primaryIdentity;
|
||||
|
||||
@@ -1026,10 +1026,20 @@ static const NSString *kEncryptedUserNamePrefix = @"uenc";
|
||||
|
||||
- (NSArray *) mailAccounts
|
||||
{
|
||||
return [self mailAccountsWithDelegatedIdentities: YES];
|
||||
return [self mailAccountsWithDelegatedIdentities: YES forceRawHtmlSignature: [[self domainDefaults] forceRawHtmlSignature]];
|
||||
}
|
||||
|
||||
- (NSArray *) mailAccountsNoRawHtmlSignature
|
||||
{
|
||||
return [self mailAccountsWithDelegatedIdentities: YES forceRawHtmlSignature: NO];
|
||||
}
|
||||
|
||||
- (NSArray *) mailAccountsWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities
|
||||
{
|
||||
return [self mailAccountsWithDelegatedIdentities: appendDeletegatedIdentities forceRawHtmlSignature: [[self domainDefaults] forceRawHtmlSignature]];
|
||||
}
|
||||
|
||||
- (NSArray *) mailAccountsWithDelegatedIdentities: (BOOL) appendDeletegatedIdentities forceRawHtmlSignature: (BOOL) forceRawHtmlSignature
|
||||
{
|
||||
NSArray *auxAccounts;
|
||||
|
||||
@@ -1091,7 +1101,42 @@ static const NSString *kEncryptedUserNamePrefix = @"uenc";
|
||||
}
|
||||
}
|
||||
|
||||
return mailAccounts;
|
||||
// Patch for CKEditor 5
|
||||
// HTML Signatures with tables are broken with CKEditor 5
|
||||
// In this case, signatures may be encapsulated with a <div class="raw-html-embed">
|
||||
// Details in https://bugs.sogo.nu/view.php?id=5920
|
||||
NSMutableArray *tmpMailAccounts, *tmpIdentities;
|
||||
NSDictionary *account, *identities, *identity;
|
||||
NSMutableDictionary *tmpAccount, *tmpIdentity;
|
||||
NSString *signature;
|
||||
|
||||
if (mailAccounts && [mailAccounts count] > 0) {
|
||||
tmpMailAccounts = [NSMutableArray array];
|
||||
for (account in mailAccounts) {
|
||||
tmpAccount = [NSMutableDictionary dictionaryWithDictionary: account];
|
||||
if ([account objectForKey: @"identities"]) {
|
||||
identities = [account objectForKey: @"identities"];
|
||||
tmpIdentities = [NSMutableArray array];
|
||||
for (identity in identities) {
|
||||
tmpIdentity = [NSMutableDictionary dictionaryWithDictionary: identity];
|
||||
if ([tmpIdentity objectForKey: @"signature"] && [[tmpIdentity objectForKey: @"signature"] length] > 0) {
|
||||
// Add raw html embed class
|
||||
if ([[tmpIdentity objectForKey: @"signature"] rangeOfString:@"raw-html-embed"].location == NSNotFound
|
||||
&& [[[self userDefaults] mailComposeMessageType] isEqualToString: @"html"]
|
||||
&& forceRawHtmlSignature) {
|
||||
signature = [NSString stringWithFormat:@"<div class=\"raw-html-embed sogo-raw-html-embed\">%@</div>", [tmpIdentity objectForKey: @"signature"]];
|
||||
[tmpIdentity setObject:signature forKey:@"signature"];
|
||||
}
|
||||
}
|
||||
[tmpIdentities addObject: tmpIdentity];
|
||||
}
|
||||
[tmpAccount setObject:tmpIdentities forKey: @"identities"];
|
||||
}
|
||||
[tmpMailAccounts addObject: tmpAccount];
|
||||
}
|
||||
}
|
||||
|
||||
return tmpMailAccounts;
|
||||
}
|
||||
|
||||
- (NSDictionary *) accountWithName: (NSString *) accountName;
|
||||
|
||||
@@ -607,10 +607,48 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
{
|
||||
*_domain = [username substringFromIndex: r.location+1];
|
||||
|
||||
if (![[[SOGoSystemDefaults sharedSystemDefaults] domainIds] containsObject: *_domain])
|
||||
if (![[sd domainIds] containsObject: *_domain])
|
||||
*_domain = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// If the domains is unknwon we reject the auth
|
||||
if([sd forbidUnknownDomainsAuth])
|
||||
{
|
||||
NSArray *domainsAllowed, *domainsKnown;
|
||||
NSString *userDomain;
|
||||
NSRange r;
|
||||
BOOL allowed = NO;
|
||||
if(!*_domain)
|
||||
{
|
||||
r = [username rangeOfString: @"@"];
|
||||
if(r.location != NSNotFound)
|
||||
userDomain = [username substringFromIndex: r.location+1];
|
||||
else
|
||||
userDomain = nil;
|
||||
}
|
||||
else
|
||||
userDomain = *_domain;
|
||||
|
||||
if(!userDomain)
|
||||
{
|
||||
[self errorWithFormat: @"User attempt to login without domain"];
|
||||
return allowed;
|
||||
}
|
||||
|
||||
|
||||
if((domainsAllowed = [sd domainsAllowed]) && [domainsAllowed containsObject: userDomain])
|
||||
allowed = YES;
|
||||
if((domainsKnown = [sd domainIds]) && [domainsKnown containsObject: userDomain])
|
||||
allowed = YES;
|
||||
|
||||
if([domainsKnown length] == 0 && [domainsAllowed length] == 0)
|
||||
[self errorWithFormat: @"SOGoForbidUnknownDomainsAuth is set but sogo don't know any domains"];
|
||||
else if(!allowed)
|
||||
[self errorWithFormat: @"User domain is unknown or not allowed: %@", userDomain];
|
||||
|
||||
return allowed;
|
||||
}
|
||||
|
||||
// We check the fail count per user in memcache (per server). If the
|
||||
// fail count reaches X in Y minutes, we deny immediately the
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
NSString *_multipleBookingsField;
|
||||
|
||||
BOOL _listRequiresDot;
|
||||
int _globalAddressBookFirstEntriesCount;
|
||||
|
||||
NSDictionary *_modulesConstraints;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
_imapHostField = nil;
|
||||
_sieveHostField = nil;
|
||||
_listRequiresDot = YES;
|
||||
_globalAddressBookFirstEntriesCount = -1;
|
||||
_modulesConstraints = nil;
|
||||
}
|
||||
|
||||
@@ -170,8 +171,12 @@
|
||||
_viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]];
|
||||
|
||||
dotValue = [udSource objectForKey: @"listRequiresDot"];
|
||||
if (dotValue)
|
||||
if (dotValue) {
|
||||
[self setListRequiresDot: [dotValue boolValue]];
|
||||
if ([udSource objectForKey: @"globalAddressBookFirstEntriesCount"])
|
||||
[self setGlobalAddressBookFirstEntriesCount: [[udSource objectForKey: @"globalAddressBookFirstEntriesCount"] intValue]];
|
||||
}
|
||||
|
||||
|
||||
#warning this domain code has no effect yet
|
||||
if ([sourceDomain length])
|
||||
@@ -1156,6 +1161,16 @@
|
||||
return _listRequiresDot;
|
||||
}
|
||||
|
||||
- (void) setGlobalAddressBookFirstEntriesCount: (int)value
|
||||
{
|
||||
_globalAddressBookFirstEntriesCount = value;
|
||||
}
|
||||
|
||||
- (int) globalAddressBookFirstEntriesCount
|
||||
{
|
||||
return _globalAddressBookFirstEntriesCount;
|
||||
}
|
||||
|
||||
/* card editing */
|
||||
- (void) setModifiers: (NSArray *) newModifiers
|
||||
{
|
||||
|
||||
@@ -13,6 +13,10 @@ let utility, user, attendee1, attendee1Delegate
|
||||
let userCalendar, attendee1Calendar, attendee1DelegateCalendar
|
||||
let icsName, icsList, vcalendar
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('PreventInvitations', function() {
|
||||
|
||||
const _getEvent = async function(client, calendarName, filename, expectedCode = 200) {
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('read and set calendar properties', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const utility = new TestUtility(webdav)
|
||||
|
||||
@@ -3,6 +3,10 @@ import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
import ICAL from 'ical.js'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('CalDAV Scheduling', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
@@ -933,5 +937,5 @@ describe('CalDAV Scheduling', function() {
|
||||
|
||||
vcalendarInvitationAttendee = await _getEvent(webdavAttendee1, attendee1Calendar, icsName, 404)
|
||||
vcalendarInvitationDelegate = await _getEvent(webdavAttendee1Delegate, attendee1DelegateCalendar, icsName, 404)
|
||||
}, 10000) // increase timeout for this long test
|
||||
}) // increase timeout for this long test
|
||||
})
|
||||
@@ -70,6 +70,10 @@ X-AIM:pseudo aim
|
||||
END:VCARD`
|
||||
}
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('CardDAV extensions', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
@@ -232,7 +236,7 @@ describe('CardDAV extensions', function() {
|
||||
expect(emails)
|
||||
.withContext(`Returned vCard has email of ${config.attendee1_username} (${config.attendee1})`)
|
||||
.toContain(config.attendee1)
|
||||
}, 10000) // increase timeout for this long test
|
||||
}) // increase timeout for this long test
|
||||
|
||||
// CARDDAV:addressbook-multiget Report
|
||||
// https://datatracker.ietf.org/doc/html/rfc6352#section-8.7
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import config from '../lib/config'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('config tests', function() {
|
||||
|
||||
it('required configuration parameters', async function() {
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('create, read, modify, delete tasks for regular user', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('create, read, modify, delete events for regular user', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
|
||||
@@ -10,6 +10,10 @@ import TestUtility from '../lib/utilities'
|
||||
* additional calendars.
|
||||
*/
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('Apple iCal', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
@@ -229,5 +233,5 @@ describe('Apple iCal', function() {
|
||||
.toBe(0)
|
||||
|
||||
await webdav_su.deleteObject(otherResource)
|
||||
}, 10000) // increase timeout for this long test
|
||||
}) // increase timeout for this long test
|
||||
})
|
||||
@@ -1,6 +1,10 @@
|
||||
import config from '../lib/config'
|
||||
import { default as WebDAV, DAVInverse } from '../lib/WebDAV'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('calendar classification', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
|
||||
@@ -35,7 +39,7 @@ describe('calendar classification', function() {
|
||||
expect(status)
|
||||
.withContext('Setting an invalid classification')
|
||||
.toBe(403)
|
||||
})
|
||||
})
|
||||
|
||||
it('setting a valid classification', async function() {
|
||||
for (let component of ['events', 'tasks']) {
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('public access', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_anon = new WebDAV()
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('DAVCalendarSuperUserAcl', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import config from '../lib/config'
|
||||
import { default as WebDAV, DAVInverse } from '../lib/WebDAV'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('contacts categories', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('public access', function() {
|
||||
const webdav_anon = new WebDAV()
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import WebDAV from '../lib/WebDAV'
|
||||
|
||||
let webdav, resource
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('HTTP Calendar', function() {
|
||||
|
||||
beforeAll(async function() {
|
||||
|
||||
@@ -3,6 +3,10 @@ import Preferences from '../lib/Preferences'
|
||||
|
||||
const prefs = new Preferences(config.username, config.password)
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('preferences', function() {
|
||||
|
||||
// preferencesTest
|
||||
|
||||
@@ -152,6 +152,10 @@ let msg3Size = 720
|
||||
let webdav, utility
|
||||
let user, resource, mailboxesList
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
// DAVMailCollectionTest
|
||||
|
||||
describe('MailDAV', function() {
|
||||
|
||||
@@ -6,6 +6,10 @@ import ManageSieve from '../lib/ManageSieve'
|
||||
|
||||
let prefs, webdav, utility, manageSieve, user
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('Sieve', function() {
|
||||
|
||||
async function _getSogoSieveScript() {
|
||||
|
||||
@@ -5,6 +5,10 @@ const os = require('os')
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('sogo-tool tests', function() {
|
||||
let tmpdir, isRoot
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import config from '../lib/config'
|
||||
import WebDAV from '../lib/WebDAV'
|
||||
import TestUtility from '../lib/utilities'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('WebDAV', function() {
|
||||
var webdav
|
||||
var utility
|
||||
|
||||
@@ -3,6 +3,10 @@ import WebDAV from '../lib/WebDAV'
|
||||
import { DAVNamespace, DAVNamespaceShorthandMap } from 'tsdav'
|
||||
import convert from 'xml-js'
|
||||
|
||||
beforeAll(function () {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.timeout || 10000;
|
||||
});
|
||||
|
||||
describe('webdav sync', function() {
|
||||
const webdav = new WebDAV(config.username, config.password)
|
||||
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
|
||||
|
||||
@@ -171,13 +171,6 @@
|
||||
owner = [SOGoUser userWithLogin: ownerId];
|
||||
mailer = [SOGoMailer mailerWithDomainDefaults: [owner domainDefaults]];
|
||||
|
||||
if (!staticAuthenticator && [mailer requiresAuthentication])
|
||||
{
|
||||
fprintf (stderr, "Specify the file containing credentials to use for SMTP AUTH.\n"
|
||||
"Use -h for help.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
localContext = [WOContext context];
|
||||
[localContext setActiveUser: owner];
|
||||
app = [[WOApplication alloc] initWithName: @"SOGo"];
|
||||
@@ -278,6 +271,7 @@
|
||||
withMetadata: metadata];
|
||||
|
||||
max = [alarms count];
|
||||
|
||||
for (count = 0; count < max; count++)
|
||||
[self _processAlarm: [alarms objectAtIndex: count]
|
||||
withOwner: [[metadata objectAtIndex: count] objectForKey: @"owner"]
|
||||
@@ -287,7 +281,7 @@
|
||||
// events or tasks for example). This will also delete any email
|
||||
// alarms that are no longer relevant
|
||||
max = [metadata count];
|
||||
|
||||
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
d = [metadata objectAtIndex: count];
|
||||
|
||||
@@ -419,6 +419,7 @@
|
||||
max = [records count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
version = 0;
|
||||
if (count > 0 && count%100 == 0)
|
||||
{
|
||||
DESTROY(pool);
|
||||
@@ -428,7 +429,7 @@
|
||||
cName = [currentRecord objectForKey: @"c_name"];
|
||||
if (![existingRecords objectForKey: cName])
|
||||
{
|
||||
NSLog (@"restoring record '%@'", cName);
|
||||
NSLog (@"restoring record '%@' with version %d", cName, version);
|
||||
cContent = [currentRecord objectForKey: @"c_content"];
|
||||
[gcsFolder writeContent: cContent
|
||||
fromComponent: [[self parsingClassForContent: cContent] parseSingleFromSource: cContent]
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (void) updateSecretFromEncryptedData: (NSString*) newSecret oldSecret: (NSString*) oldSecret
|
||||
- (BOOL) updateSecretFromEncryptedData: (NSString*) newSecret oldSecret: (NSString*) oldSecret
|
||||
{
|
||||
BOOL rc;
|
||||
rc = [self fetchAllUsersForAuxiliaryAccountPassword];
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Nenhum recurso";
|
||||
"Any Authenticated User" = "Qualquer Usuário Autenticado";
|
||||
"Public Access" = "Acesso Público";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Salvar";
|
||||
"Clear" = "Limpar";
|
||||
"Message of the day" = "Mensagem do dia";
|
||||
"Message of the day has been saved" = "A mensagem do dia foi salva";
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Není zdroj";
|
||||
"Any Authenticated User" = "Každý ověřený uživatel";
|
||||
"Public Access" = "Veřejný přístup";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Uložit";
|
||||
"Clear" = "Vymazat";
|
||||
"Message of the day" = "Zpráva dne";
|
||||
"Message of the day has been saved" = "Zpráva dne byla uložena";
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Ingen ressource";
|
||||
"Any Authenticated User" = "Enhver godkendt bruger";
|
||||
"Public Access" = "Offentlig adgang";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Gem";
|
||||
"Clear" = "Ryd";
|
||||
"Message of the day" = "Dagens besked";
|
||||
"Message of the day has been saved" = "Dagens besked er blevet gemt";
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Sen recurso";
|
||||
"Any Authenticated User" = "Calquera usuario autenticado";
|
||||
"Public Access" = "Acceso público";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Gardar";
|
||||
"Clear" = "Borrar";
|
||||
"Message of the day" = "Mensaxe do día";
|
||||
"Message of the day has been saved" = "A mensaxe do día foi gardado";
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Ingen ressurs";
|
||||
"Any Authenticated User" = "Enhver autentisert bruker";
|
||||
"Public Access" = "Offentlig tilgang";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Lagre";
|
||||
"Clear" = "Tøm";
|
||||
"Message of the day" = "Dagens melding";
|
||||
"Message of the day has been saved" = "Dagens melding er lagret";
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "Ni virov";
|
||||
"Any Authenticated User" = "Vsak prijavljen uporabnik";
|
||||
"Public Access" = "Javni dostop";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Shrani";
|
||||
"Clear" = "Počisti";
|
||||
"Message of the day" = "Sporočilo dneva";
|
||||
"Message of the day has been saved" = "Sporočilo dneva je bilo shranjeno";
|
||||
@@ -10,7 +10,7 @@
|
||||
/* Modules descriptions */
|
||||
"ACLs_description" = "<p>El módulo administrativo de ACL (Listas de Control de Acceso por su siglas inglés) permite cambiar los ACLs de las Agendas y Libreta de Direcciones de cada usuario.</p><p>Para modificar los ACLs de las carpetas de un usuario, teclea el nombre de usuario en el campo de búsqueda arriba de la ventana y haz un doble-click sobre la carpeta deseada.</p>";
|
||||
"Name or Email" = "Nombre o Correo";
|
||||
"ACLs saved" = "ACLs saved";
|
||||
"ACLs saved" = "LCAs guardadas";
|
||||
/* Rights module: initial search message */
|
||||
"Start a search to edit the rights" = "Iniciar una búsqueda para modificar los derechos";
|
||||
/* Rights module: Empty search result */
|
||||
@@ -25,7 +25,7 @@
|
||||
"No resource" = "No recursos";
|
||||
"Any Authenticated User" = "Cualquier Usuario Autenticado ";
|
||||
"Public Access" = "Acceso publico";
|
||||
"Save" = "Save";
|
||||
"Clear" = "Clear";
|
||||
"Message of the day" = "Message of the day";
|
||||
"Message of the day has been saved" = "Message of the day has been saved";
|
||||
"Save" = "Guardar";
|
||||
"Clear" = "Borrar";
|
||||
"Message of the day" = "Mensaje del día";
|
||||
"Message of the day has been saved" = "Se ha guardado el mensaje del día";
|
||||
@@ -41,22 +41,22 @@
|
||||
= "Potenciálně kdokoliv v Internetu bude mít přístup k vašemu adresáři \"%{0}\", i když není uživatelem tohoto systému. Jsou tyto informace vhodné pro veřejný Internet?";
|
||||
"Give Access" = "Umožnit přístup";
|
||||
"Keep Private" = "Ponechat soukromým";
|
||||
"Expand" = "Expand";
|
||||
"Reduce" = "Reduce";
|
||||
"Expand" = "Rozbalit";
|
||||
"Reduce" = "Sbalit";
|
||||
|
||||
/* tooltip */
|
||||
"Refresh" = "Refresh";
|
||||
"Refresh" = "Obnovit";
|
||||
|
||||
/* generic.js */
|
||||
"Unable to subscribe to that folder!"
|
||||
= "Není možné se přihlásit k odebírání této složky!";
|
||||
= "Nelze se přihlásit k odběru této složky!";
|
||||
"You cannot subscribe to a folder that you own!"
|
||||
= "Nemůžete se přihlásit k odebírání své vlastní složky!";
|
||||
"Unable to unsubscribe from that folder!"
|
||||
= "Není možné se odhlásit z odebírání této složky!";
|
||||
= "Nelze se odhlásit z odběru této složky!";
|
||||
"You cannot unsubscribe from a folder that you own!"
|
||||
= "Nemůžete se odhlásit z odebírání své vlastní složky!";
|
||||
"Unable to rename that folder!" = "Není možné tuto složku přejmenovat!";
|
||||
"Unable to rename that folder!" = "Nelze přejmenovat tuto složku!";
|
||||
"You have already subscribed to that folder!"
|
||||
= "Tuto složku již odebíráte!";
|
||||
"The user rights cannot be edited for this object!"
|
||||
@@ -109,7 +109,7 @@
|
||||
"Personal" = "Osobní";
|
||||
"To Do" = "Třeba udělat";
|
||||
"Later" = "Později";
|
||||
"Forwarded" = "Forwarded";
|
||||
"Forwarded" = "Přeposláno";
|
||||
"Return Receipt Sent" = "Odesláno potvrzení o přečtení";
|
||||
"a2_Sunday" = "Ne";
|
||||
"a2_Monday" = "Po";
|
||||
@@ -128,15 +128,15 @@
|
||||
"S/MIME Certificate" = "S/MIME Certifikát";
|
||||
"Subject Name" = "Jméno Subjektu";
|
||||
"Issuer" = "Vydavatel";
|
||||
"streetAddress" = "Street";
|
||||
"stateOrProvinceName" = "State or Province";
|
||||
"localityName" = "Locality";
|
||||
"streetAddress" = "Ulice";
|
||||
"stateOrProvinceName" = "Oblast";
|
||||
"localityName" = "Umístění";
|
||||
"countryName" = "Země";
|
||||
"organizationName" = "Organizace";
|
||||
"organizationalUnitName" = "Organizační jednotka";
|
||||
"commonName" = "Obecné Jméno";
|
||||
"surname" = "Surname";
|
||||
"givenName" = "Given Name";
|
||||
"surname" = "Příjmení";
|
||||
"givenName" = "Jméno";
|
||||
"emailAddress" = "E-mail";
|
||||
|
||||
|
||||
@@ -227,9 +227,9 @@
|
||||
"Move forward" = "Posun vpřed";
|
||||
|
||||
/* Password Recovery */
|
||||
"passwordRecovery_Disabled" = "Disabled";
|
||||
"passwordRecovery_SecretQuestion" = "Secret question";
|
||||
"passwordRecovery_SecretQuestion1" = "What is the name of your first pet ?";
|
||||
"passwordRecovery_SecretQuestion2" = "What was your first car ?";
|
||||
"passwordRecovery_SecretQuestion3" = "What is your favorite movie ?";
|
||||
"passwordRecovery_SecondaryEmail" = "Secondary E-mail";
|
||||
"passwordRecovery_Disabled" = "Zakázáno";
|
||||
"passwordRecovery_SecretQuestion" = "Tajná otázka";
|
||||
"passwordRecovery_SecretQuestion1" = "Jaké je jméno Vašeho prvního mazlíčka?";
|
||||
"passwordRecovery_SecretQuestion2" = "Jaké bylo Vaše první auto?";
|
||||
"passwordRecovery_SecretQuestion3" = "Jaký je Váš oblíbený film?";
|
||||
"passwordRecovery_SecondaryEmail" = "Sekundární e-mail";
|
||||
@@ -45,7 +45,7 @@
|
||||
"Reduce" = "Skrči";
|
||||
|
||||
/* tooltip */
|
||||
"Refresh" = "Refresh";
|
||||
"Refresh" = "Osveži";
|
||||
|
||||
/* generic.js */
|
||||
"Unable to subscribe to that folder!"
|
||||
@@ -109,7 +109,7 @@
|
||||
"Personal" = "Osebno";
|
||||
"To Do" = "Opravilo";
|
||||
"Later" = "Pozneje";
|
||||
"Forwarded" = "Forwarded";
|
||||
"Forwarded" = "Posreduj";
|
||||
"Return Receipt Sent" = "Povratnica poslana";
|
||||
"a2_Sunday" = "Ne";
|
||||
"a2_Monday" = "Po";
|
||||
@@ -227,9 +227,9 @@
|
||||
"Move forward" = "Naprej";
|
||||
|
||||
/* Password Recovery */
|
||||
"passwordRecovery_Disabled" = "Disabled";
|
||||
"passwordRecovery_SecretQuestion" = "Secret question";
|
||||
"passwordRecovery_SecretQuestion1" = "What is the name of your first pet ?";
|
||||
"passwordRecovery_SecretQuestion2" = "What was your first car ?";
|
||||
"passwordRecovery_SecretQuestion3" = "What is your favorite movie ?";
|
||||
"passwordRecovery_SecondaryEmail" = "Secondary E-mail";
|
||||
"passwordRecovery_Disabled" = "Deaktiviraj";
|
||||
"passwordRecovery_SecretQuestion" = "Varnostno vprašanje";
|
||||
"passwordRecovery_SecretQuestion1" = "Kako je bilo ime tvoji prvi domači živali?";
|
||||
"passwordRecovery_SecretQuestion2" = "Kateri je bi tvoj prvi avto?";
|
||||
"passwordRecovery_SecretQuestion3" = "Kateri je tvoj najljubši film?";
|
||||
"passwordRecovery_SecondaryEmail" = "Rezervni elekronski naslov";
|
||||
@@ -41,11 +41,11 @@
|
||||
= "Potencialmente cualquier usuario de Internet tendrá acceso a su libreta de dirección \"%{0}\", también si no tiene una cuenta en este sistema. ¿Es esta información adecuada para todo el Internet publico?";
|
||||
"Give Access" = "Permite Acceso";
|
||||
"Keep Private" = "Mantiene Privado";
|
||||
"Expand" = "Expand";
|
||||
"Reduce" = "Reduce";
|
||||
"Expand" = "Ampliar";
|
||||
"Reduce" = "Reducir";
|
||||
|
||||
/* tooltip */
|
||||
"Refresh" = "Refresh";
|
||||
"Refresh" = "Actualizar";
|
||||
|
||||
/* generic.js */
|
||||
"Unable to subscribe to that folder!"
|
||||
@@ -83,10 +83,10 @@
|
||||
|
||||
/* generic messages */
|
||||
"Error" = "Error";
|
||||
"Success" = "Success";
|
||||
"Success" = "Éxito";
|
||||
|
||||
/* confirmation dialog box */
|
||||
"Confirmation" = "Confirmation";
|
||||
"Confirmation" = "Confirmación";
|
||||
|
||||
/* alarms */
|
||||
"Reminder" = "Recordatorio";
|
||||
@@ -109,8 +109,8 @@
|
||||
"Personal" = "Personal";
|
||||
"To Do" = "Por hacer";
|
||||
"Later" = "Más tarde";
|
||||
"Forwarded" = "Forwarded";
|
||||
"Return Receipt Sent" = "Return Receipt Sent";
|
||||
"Forwarded" = "Reenviado";
|
||||
"Return Receipt Sent" = "Envío de acuse de recibo";
|
||||
"a2_Sunday" = "Do";
|
||||
"a2_Monday" = "Lu";
|
||||
"a2_Tuesday" = "Ma";
|
||||
@@ -125,19 +125,19 @@
|
||||
"You cannot (un)subscribe to a folder that you own!" = "No es posible suscribirse a una carpeta que le pertenece.";
|
||||
|
||||
/* SMIME Certificate field */
|
||||
"S/MIME Certificate" = "S/MIME Certificate";
|
||||
"Subject Name" = "Subject Name";
|
||||
"Issuer" = "Issuer";
|
||||
"streetAddress" = "Street";
|
||||
"stateOrProvinceName" = "State or Province";
|
||||
"localityName" = "Locality";
|
||||
"countryName" = "Country";
|
||||
"organizationName" = "Organization";
|
||||
"organizationalUnitName" = "Organizational Unit";
|
||||
"commonName" = "Common Name";
|
||||
"surname" = "Surname";
|
||||
"givenName" = "Given Name";
|
||||
"emailAddress" = "Email Address";
|
||||
"S/MIME Certificate" = "Certificado S/MIME";
|
||||
"Subject Name" = "Titular";
|
||||
"Issuer" = "Remitente";
|
||||
"streetAddress" = "Dirección";
|
||||
"stateOrProvinceName" = "Estado/Provincia";
|
||||
"localityName" = "Localidad";
|
||||
"countryName" = "País";
|
||||
"organizationName" = "Organización";
|
||||
"organizationalUnitName" = "Departamento/ Sección";
|
||||
"commonName" = "Dominio";
|
||||
"surname" = "Apellido";
|
||||
"givenName" = "Nombre";
|
||||
"emailAddress" = "Correo electrónico";
|
||||
|
||||
|
||||
/* Authentication username */
|
||||
@@ -156,18 +156,18 @@
|
||||
"File size upload limit reached" = "Alcanzado el tamaño máximo del archivo";
|
||||
|
||||
/* Generic server error */
|
||||
"An error occured, please try again." = "An error occured, please try again.";
|
||||
"An error occured, please try again." = "Se ha producido un error, inténtelo de nuevo.";
|
||||
|
||||
/* Open datepicker */
|
||||
"Open Calendar" = "Open Calendar";
|
||||
"Open Calendar" = "Abrir calendario";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Cambiar la visibilidad";
|
||||
|
||||
"Keyboard Shortcuts" = "Keyboard Shortcuts";
|
||||
"Keyboard Shortcuts" = "Atajos del teclado";
|
||||
|
||||
/* Aria label for avatar button to select and unselect an item */
|
||||
"Toggle item" = "Toggle item";
|
||||
"Toggle item" = "Activar elemento";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Cambiar el rango de elementos";
|
||||
@@ -179,7 +179,7 @@
|
||||
"key_space" = "space";
|
||||
|
||||
/* Delete key */
|
||||
"key_delete" = "delete";
|
||||
"key_delete" = "borrar";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "Mayúsculas + espacio";
|
||||
@@ -227,9 +227,9 @@
|
||||
"Move forward" = "Avanzar";
|
||||
|
||||
/* Password Recovery */
|
||||
"passwordRecovery_Disabled" = "Disabled";
|
||||
"passwordRecovery_SecretQuestion" = "Secret question";
|
||||
"passwordRecovery_SecretQuestion1" = "What is the name of your first pet ?";
|
||||
"passwordRecovery_SecretQuestion2" = "What was your first car ?";
|
||||
"passwordRecovery_SecretQuestion3" = "What is your favorite movie ?";
|
||||
"passwordRecovery_SecondaryEmail" = "Secondary E-mail";
|
||||
"passwordRecovery_Disabled" = "Desactivado";
|
||||
"passwordRecovery_SecretQuestion" = "Pregunta secreta";
|
||||
"passwordRecovery_SecretQuestion1" = "¿Cuál es el nombre de su primera mascota?";
|
||||
"passwordRecovery_SecretQuestion2" = "¿Cuál fue su primer coche?";
|
||||
"passwordRecovery_SecretQuestion3" = "¿Cuál es su película favorita?";
|
||||
"passwordRecovery_SecondaryEmail" = "Email secundario";
|
||||
@@ -62,4 +62,9 @@
|
||||
return (vacationOptions && [[vacationOptions objectForKey: @"enabled"] boolValue]);
|
||||
}
|
||||
|
||||
- (BOOL) userHasVacationDisabled
|
||||
{
|
||||
return ![self userHasVacationEnabled];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -196,7 +196,7 @@
|
||||
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuration profile";
|
||||
|
||||
/* categories */
|
||||
"contacts_category_labels" = "الزميل، المنافس، العملاء، الصديق، العائلة، شريك تجاري، مقدم خدمة، الصحافة ، كبار الشخصيات";
|
||||
"contacts_category_labels" = "الزميل, المنافس, العملاء, الصديق, العائلة, شريك تجاري, مقدم خدمة , الصحافة ,كبار الشخصيات";
|
||||
"New category" = "فئة جديدة";
|
||||
|
||||
/* adresses */
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
"Work" = "Zaměstnání";
|
||||
"Mobile" = "Mobilní telefon";
|
||||
"Pager" = "Pager";
|
||||
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuration profile";
|
||||
"Download iOS - MacOS configuration profile" = "Stáhnout konfiguraci pro iOS - MacOS";
|
||||
|
||||
/* categories */
|
||||
"contacts_category_labels" = "Kolega, Konkurent, Zákazník, Přítel, Rodina, Obchodní partner, Dodavatel, Tisk, VIP";
|
||||
@@ -241,7 +241,7 @@
|
||||
"You cannot subscribe to a folder that you own!"
|
||||
= "Nemůžete se přihlásit k odebírání vlastní složky! ";
|
||||
"Unable to subscribe to that folder!"
|
||||
= "Nemůžete se přihlásit k odebírání této složky!";
|
||||
= "Nelze se přihlásit k odběru této složky.";
|
||||
|
||||
/* security */
|
||||
"Security" = "Zabezpečení";
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
"OfficePhone" = "Teléfono oficina";
|
||||
"Organization" = "Organización";
|
||||
"Work Phone" = "Teléfono trabajo";
|
||||
"Cell" = "Cell";
|
||||
"Cell" = "Celda";
|
||||
"Pager" = "Busca";
|
||||
"Pref" = "Pref";
|
||||
"Pref" = "Prefs";
|
||||
"Phone" = "Teléfono";
|
||||
"Phones" = "Teléfonos";
|
||||
"Postal" = "Código Postal";
|
||||
@@ -49,88 +49,88 @@
|
||||
"Blind Carbon Copy (Bcc)" = "Con Copia Oculta (Bcc)";
|
||||
|
||||
/* Search scope: name fields */
|
||||
"name" = "Name";
|
||||
"name" = "Nombre";
|
||||
|
||||
/* Search scope: name fields */
|
||||
"c_cn" = "Name";
|
||||
"c_cn" = "Nombre";
|
||||
|
||||
/* Search scope: secondary email field */
|
||||
"mozillanickname" = "Secondary email";
|
||||
"mozillanickname" = "Correo secundario";
|
||||
|
||||
/* Search scope: screen name field */
|
||||
"nsaimid" = "Screen name";
|
||||
"nsaimid" = "Nombre en pantalla";
|
||||
|
||||
/* Search scope: mail fields */
|
||||
"mail" = "Mail";
|
||||
"mail" = "Correo";
|
||||
|
||||
/* Search scope: mail fields */
|
||||
"c_mail" = "Mail";
|
||||
"c_mail" = "Correo";
|
||||
|
||||
/* Search scope: telephone field */
|
||||
"telephonenumber" = "Telephone";
|
||||
"telephonenumber" = "Teléfono";
|
||||
|
||||
/* Search scope: telephone field */
|
||||
"homephone" = "Telephone";
|
||||
"homephone" = "Teléfono";
|
||||
|
||||
/* Search scope: mobile field */
|
||||
"mobile" = "Mobile";
|
||||
"mobile" = "Móvil";
|
||||
|
||||
/* Search scope: fax field */
|
||||
"facsimiletelephonenumber" = "Fax";
|
||||
|
||||
/* Search scope: pager field */
|
||||
"pager" = "Pager";
|
||||
"pager" = "Localizador";
|
||||
|
||||
/* Search scope: categories field */
|
||||
"c_categories" = "Categories";
|
||||
"c_categories" = "Categorias";
|
||||
|
||||
/* Search scope: categories field */
|
||||
"vcardcategories" = "Categories";
|
||||
"vcardcategories" = "Categorias";
|
||||
|
||||
/* Search scope: title field */
|
||||
"title" = "Title";
|
||||
"title" = "Cargo";
|
||||
|
||||
/* Search scope: organization field */
|
||||
"c_o" = "Organization";
|
||||
"c_o" = "Organización";
|
||||
|
||||
/* Search scope: organization field */
|
||||
"o" = "Organization";
|
||||
"o" = "Organización";
|
||||
|
||||
/* Search scope: department field */
|
||||
"ou" = "Department";
|
||||
"ou" = "Departamento";
|
||||
|
||||
/* Search scope: city field */
|
||||
"l" = "City";
|
||||
"l" = "Ciudad";
|
||||
|
||||
/* Search scope: country field */
|
||||
"c" = "Country";
|
||||
"c" = "País";
|
||||
|
||||
/* Search scope: home address field */
|
||||
"mozillahomestreet" = "Home Address";
|
||||
"mozillahomestreet" = "Domicilio particular";
|
||||
|
||||
/* Search scope: state/province field */
|
||||
"st" = "State/Province";
|
||||
"st" = "Estado/Provincia";
|
||||
|
||||
/* Search scope: home state/province field */
|
||||
"mozillahomestate" = "Home State/Province";
|
||||
"mozillahomestate" = "Provincia Natal";
|
||||
|
||||
/* Search scope: zip/postal code field */
|
||||
"postalCode" = "Zip/Postal Code";
|
||||
"postalCode" = "Código Postal";
|
||||
|
||||
/* Search scope: home zip/postal code field */
|
||||
"mozillahomepostalcode" = "Zip/Postal Code";
|
||||
"mozillahomepostalcode" = "Código Postal";
|
||||
|
||||
/* Search scope: home country field */
|
||||
"mozillahomecountryname" = "Home Country";
|
||||
"mozillahomecountryname" = "País Natal";
|
||||
|
||||
/* Search scope: home web page field */
|
||||
"mozillahomeurl" = "Home page";
|
||||
"mozillahomeurl" = "Pagina de inicio";
|
||||
|
||||
/* Search scope: work web page field */
|
||||
"mozillaworkurl" = "Web page";
|
||||
"mozillaworkurl" = "Página web";
|
||||
|
||||
/* Search scope: note field */
|
||||
"description" = "Note";
|
||||
"description" = "Nota";
|
||||
|
||||
/* Subheader of empty addressbook */
|
||||
"No contact" = "No contacto";
|
||||
@@ -193,7 +193,7 @@
|
||||
"Work" = "Trabajo";
|
||||
"Mobile" = "Móvil";
|
||||
"Pager" = "Busca";
|
||||
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuration profile";
|
||||
"Download iOS - MacOS configuration profile" = "Descargar el perfil de configuración iOS - macOS";
|
||||
|
||||
/* categories */
|
||||
"contacts_category_labels" = "Colega, Competidor, Cliente, Amigo, Familia, Socio, Proveedor, Prensa, VIP";
|
||||
@@ -222,7 +222,7 @@
|
||||
"New Addressbook..." = "Crear libreta de direcciones...";
|
||||
"Subscribe to an Addressbook..." = "Suscribir a una libreta de direcciones...";
|
||||
"Remove the selected Addressbook" = "Borrar libreta de direcciones seleccionada";
|
||||
"Subscribe to a shared contact" = "Subscribe to a shared addressbook";
|
||||
"Subscribe to a shared contact" = "Suscribirse a una libreta de direcciones compartida";
|
||||
"Search User" = "Buscar usuarios";
|
||||
"Name of the Address Book" = "Nombre de la libreta de direcciones";
|
||||
"Are you sure you want to delete the selected address book?"
|
||||
@@ -244,10 +244,10 @@
|
||||
= "No puede suscribirse a esta carpeta.";
|
||||
|
||||
/* security */
|
||||
"Security" = "Security";
|
||||
"Uninstall" = "Uninstall";
|
||||
"Error reading the card certificate." = "Error reading the card certificate.";
|
||||
"No certificate associated to card." = "No certificate associated to card.";
|
||||
"Security" = "Seguridad";
|
||||
"Uninstall" = "Desinstalar";
|
||||
"Error reading the card certificate." = "Error al leer el certificado de la tarjeta.";
|
||||
"No certificate associated to card." = "No hay un certificado asociado a la tarjeta.";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Derechos de accesos a";
|
||||
@@ -351,7 +351,7 @@
|
||||
"Synchronization" = "Sincronización";
|
||||
"Synchronize" = "Sincroniza";
|
||||
"Sucessfully subscribed to address book" = "Suscripción a la libreta de direcciones con éxito";
|
||||
"A similar card already exists. Would you like to save it anyway?" = "A similar card already exists. Would you like to save it anyway?";
|
||||
"A similar card already exists. Would you like to save it anyway?" = "Ya existe una tarjeta similar. ¿Quiere guardarla de todos modos?";
|
||||
|
||||
/* Aria label for scope of search on contacts */
|
||||
"Search scope" = "Ámbito de búsqueda";
|
||||
|
||||
@@ -47,3 +47,4 @@ reply_info = "هذا هو الرد على دعوة الحدث التي قمت ب
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comment";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Zuk egindako gonbidapen bati erantzuna da hau.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comment";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Ovo je odgovor na event koji ste vi kreirali.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Komentar";
|
||||
"Description" = "Opis";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Esta é uma resposta de um convite feito por você.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comentário:";
|
||||
"Description" = "Descrição";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Това е в отговор на покана за събити
|
||||
"OK" = "ДА";
|
||||
"Comment" = "Коментар";
|
||||
"Description" = "Описание";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Aquesta és la resposta a una invitació d'esdeveniment que heu fe
|
||||
"OK" = "D'acord";
|
||||
"Comment" = "Comentari:";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "这事你邀请信的答复。";
|
||||
"OK" = "OK";
|
||||
"Comment" = "评论";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "這是您發起的事件的出席回覆。";
|
||||
"OK" = "OK";
|
||||
"Comment" = "備註";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "To je odgovor na poziv događaja napravljeno sa Vaše strane.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Komentar";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -46,4 +46,5 @@ reply_info = "Toto je odpověď na pozvánku k událost, kterou jste vytvořili
|
||||
"Cancel" = "Storno";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Komentář:";
|
||||
"Description" = "Description";
|
||||
"Description" = "Popis";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Dette er et svar på en begivenhed inviteret af dig.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Kommentar";
|
||||
"Description" = "Beskrivelse";
|
||||
"Location" = "Sted";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Dit is een antwoord op een door u verstuurde uitnodiging.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Commentaar:";
|
||||
"Description" = "Omschrijving";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "This is a reply to an event invitation done by you.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comment";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Tämä on vastaus tapahtumakutsuusi.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Kommentti:";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Ceci est une réponse à un événement que vous avez organisé.";
|
||||
"OK" = "Ok";
|
||||
"Comment" = "Description :";
|
||||
"Description" = "Description";
|
||||
"Location" = "Lieu";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Esta é a resposta a un convite de evento feito por Vde.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comentario:";
|
||||
"Description" = "Descrición";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Dies ist eine Antwort auf eine Termineinladung von Ihnen.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Kommentar:";
|
||||
"Description" = "Beschreibung";
|
||||
"Location" = "Ort";
|
||||
@@ -47,3 +47,4 @@ reply_info = "זו תגובה להזמנה לאירוע שנוצרה על ידכ
|
||||
"OK" = "אוקיי";
|
||||
"Comment" = "תגובה";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Ez egy válasz az ön által kiküldött meghívásra.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Megjegyzés:";
|
||||
"Description" = "Leírás";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Þetta er svar við boði á viðburð sem þú hefur gert.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Comment";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Ini adalah balasan untuk undangan acara yang anda lakukan.";
|
||||
"OK" = "OK";
|
||||
"Comment" = "Komentar";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "Questa è una risposta ad un invito ad un evento richiesto da te."
|
||||
"OK" = "OK";
|
||||
"Comment" = "Commento";
|
||||
"Description" = "Descrizione";
|
||||
"Location" = "Location";
|
||||
@@ -47,3 +47,4 @@ reply_info = "これはイベントの招待への返信です。";
|
||||
"OK" = "OK";
|
||||
"Comment" = "コメント";
|
||||
"Description" = "Description";
|
||||
"Location" = "Location";
|
||||
Loaded 100 of 525 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user