Compare commits

...
158 Commits
Author SHA1 Message Date
Francis Lachapelle 3d0636c756 Update ChangeLog 2017-01-23 10:02:35 -05:00
Francis Lachapelle 655e5ffe01 (css) Update default theme 2017-01-23 09:59:25 -05:00
Francis Lachapelle f65514f13e (js/css) Update generated files 2017-01-23 09:57:38 -05:00
Francis Lachapelle b60749e86c Preparation for release 3.2.6 2017-01-23 09:56:40 -05:00
Francis Lachapelle 1825b167dc (css,js) Update md to v1.1.1-master-2c9a5cc 2017-01-23 09:55:44 -05:00
Francis Lachapelle 214ce5ef86 (i18n) Update translations 2017-01-23 09:50:35 -05:00
Ludovic Marcotte 927f0c3220 Updated NEWS 2017-01-23 08:55:33 -05:00
Ludovic Marcotte 227cf582d9 (fix) touch objects when server-side changes occured 2017-01-23 08:53:41 -05:00
InverseBot 8cdc435945 (js/css) Update generated files 2017-01-21 01:28:13 -05:00
Francis Lachapelle 3efe0e8098 Add support for date extension of Sieve
Fixes #1530, #1949
2017-01-20 11:08:24 -05:00
Ludovic Marcotte d38d42b83a (fix) use login in domain 2017-01-20 09:25:02 -05:00
InverseBot 1f3a89b984 (js/css) Update generated files 2017-01-20 01:27:56 -05:00
Francis Lachapelle 0dea9c226d Allow to select multiple files when uploading
Fixes #3999
2017-01-19 15:41:21 -05:00
Francis Lachapelle db1cc24c20 (css) Improve display of HTML CKEditor 2017-01-19 14:47:11 -05:00
Francis Lachapelle d7d88e2620 (js) Compose new message in a popup window 2017-01-19 12:23:55 -05:00
Francis Lachapelle 4fca484ed0 Improve error handling in SOGoWebAppointmentFolder 2017-01-19 10:31:03 -05:00
Francis Lachapelle fcb79dc14b Improve error handling in MSExchangeFreeBusy class 2017-01-19 10:17:17 -05:00
Francis Lachapelle 41dbb88b64 (js) Expandable mail editor
Added "autogrow" plugin for CKEditor.
2017-01-19 07:05:52 -05:00
InverseBot bb97b57ad7 (js/css) Update generated files 2017-01-19 01:28:13 -05:00
Ludovic Marcotte 5300d4185d (fix) improve doc around SOGoTimeZone (fixes #3995) 2017-01-18 16:17:12 -05:00
Francis Lachapelle 26b69d25ee (js) Fix opening message in popup window 2017-01-18 15:03:29 -05:00
Ludovic Marcotte a3c056ea84 (fix) deep-search for text/calendar parts 2017-01-17 15:12:29 -05:00
Ludovic Marcotte e4b2539b04 (fix) strip attendees with no email and don't send IMIP when it's Outlook the EAS client 2017-01-17 12:40:41 -05:00
Francis Lachapelle fcb6542d06 (i18n) Update translations 2017-01-17 11:47:43 -05:00
Francis Lachapelle 174fcd5c31 (i18n) Update translations 2017-01-17 08:53:52 -05:00
InverseBot 2f6c5148aa (js/css) Update generated files 2017-01-17 01:30:41 -05:00
Francis Lachapelle 936b21b8c0 (css) Fix glitches in Month view with Firefox 2017-01-16 16:30:24 -05:00
Francis Lachapelle 5f2a6c53c4 (js) Fix pasting images in CKEditor
Fixes #3986
2017-01-16 15:31:12 -05:00
Francis Lachapelle ba12c14a1d (js) Update CKEditor to version 4.6.2
- Add Upload Image plugin

Fixes #3986
2017-01-13 16:11:10 -05:00
Ludovic Marcotte 4158f7555e Updated NEWS 2017-01-13 13:19:43 -05:00
Ludovic Marcotte 6e0f5b2254 (fix) return if not content is there, to avoid random crash 2017-01-13 13:16:01 -05:00
Francis Lachapelle 4a21a02f98 Fix rendering of forwarded HTML message with img
Fixes #3981 (cont'd)
2017-01-13 12:23:38 -05:00
Ludovic Marcotte 9d7d2575d3 (ix) remove alarms in IMIP responses (fixes #3925) 2017-01-12 15:23:09 -05:00
InverseBot 905859053c (js/css) Update generated files 2017-01-12 01:31:09 -05:00
Francis Lachapelle e5d0b0b0ca Fix rendering of forwarded HTML message with img
Fixes #3981 (partially)
2017-01-11 16:08:56 -05:00
Francis Lachapelle 589827a414 (html) Show locale codes beside language names 2017-01-11 14:20:43 -05:00
Francis Lachapelle 42ee981613 (js) Update Angular to branch 1.6.x 2017-01-11 14:20:43 -05:00
Francis Lachapelle 5985262e0f Improve WebDAV Python lib for integration tests 2017-01-11 14:20:42 -05:00
Francis Lachapelle c36fcd2902 Improve debugging output of recurrence calculator 2017-01-11 14:20:42 -05:00
Francis Lachapelle bce09f413f Update NEWS file 2017-01-11 14:20:42 -05:00
Francis Lachapelle 9477c1af16 (html) Precise EAS beside "Synchronize" option 2017-01-11 14:20:42 -05:00
InverseBot 8011d9093e (js/css) Update generated files 2017-01-11 01:26:52 -05:00
Francis Lachapelle 37334db0e3 Update ChangeLog 2017-01-10 09:42:57 -05:00
Francis Lachapelle 6637bde56d Preparation for release 3.2.5 2017-01-10 09:42:09 -05:00
Francis Lachapelle e27efc4d3a (css) Update default theme 2017-01-10 09:37:05 -05:00
Francis Lachapelle ff7b9a3725 (js/css) Update generated files 2017-01-10 09:33:43 -05:00
Francis Lachapelle 36c7ffcc3c (css,js) Update md to v1.1.1-master-64b2621 2017-01-10 09:16:04 -05:00
InverseBot 3912d2ab3e (js/css) Update generated files 2017-01-10 02:12:03 -05:00
Ludovic Marcotte 5e775ea4ce (fix) some more memory info 2017-01-09 16:09:09 -05:00
Ludovic Marcotte 841fdb96cc (fix) be a bit more verbose about memory usage 2017-01-09 16:03:32 -05:00
Francis Lachapelle 51ff746595 (js) Update CKEditor to version 4.6.1 2017-01-06 20:49:42 -05:00
Francis Lachapelle ef1f5a948f (i18n) Update translations 2017-01-06 20:34:37 -05:00
Francis Lachapelle 3560f0486e (js) Fix hotkeys with an active dialog in Mailer
Fixes #3983
2017-01-06 18:26:12 -05:00
Francis Lachapelle 327ebf00a4 Fix public URLs to Calendars
Fixes #3974
2017-01-06 16:49:00 -05:00
InverseBot abb1e1b447 (js/css) Update generated files 2017-01-07 01:28:44 -05:00
Francis Lachapelle 818233535f Improve error messages around draft attachments
(cont'd)
2017-01-06 16:17:05 -05:00
Francis Lachapelle 65e3fe978c Minor code cleanup 2017-01-06 16:15:26 -05:00
Francis Lachapelle 0216f19e55 Improve Installation Guide 2017-01-06 16:14:44 -05:00
Francis Lachapelle 8404de758b Improve error messages around draft attachments 2017-01-06 16:12:42 -05:00
Francis Lachapelle 5ec7ad54bb Fix error handling when draft is too big 2017-01-06 15:45:30 -05:00
Ludovic Marcotte be8e1373ea Updated NEWS 2017-01-06 16:01:52 -05:00
Ludovic Marcotte 9fbc84158a (fix) handle sha512-crypt with rounds 2017-01-06 15:52:19 -05:00
Ludovic Marcotte 071c1032c1 Updated NEWS 2017-01-06 14:34:43 -05:00
Ludovic Marcotte af8baf41cb (fix) correctly handle all identities during IMIP status update 2017-01-06 14:32:45 -05:00
Ludovic Marcotte 0b7ddf7ca4 (fix) small optimization over previous commit 2017-01-06 14:29:41 -05:00
Ludovic Marcotte 942ddcabe4 (fix) handle all identities during IMIP REPLY processing 2017-01-06 14:23:52 -05:00
Ludovic Marcotte bdd8254e02 (fix) make sure we compare the organizer's email only 2017-01-06 11:03:59 -05:00
Ludovic Marcotte ce0575517d (fix) make sure to bump LAST-MODIFIED to trigger all auto-scheduling code 2017-01-06 09:54:02 -05:00
Francis Lachapelle ea7934a73c Fix saving of inactive calendars
Fixes #3862, #3980
2017-01-06 06:42:12 -05:00
InverseBot 02fc147624 (js/css) Update generated files 2017-01-06 02:11:11 -05:00
Francis Lachapelle 02a1ad48fc (js) Fix computation of week number
Fixes #3973, 3976
2017-01-05 12:00:55 -05:00
Ludovic Marcotte e7e1283fb3 (fix) disabled bias computation for now 2017-01-05 09:27:16 -05:00
InverseBot d5e04be1df (js/css) Update generated files 2017-01-01 01:20:37 -05:00
Ludovic Marcotte e2ec8af0db Updated NEWS 2016-12-30 12:29:09 -05:00
Ludovic Marcotte 477c06a7ff (fix) fix events in floating time during CalDAV's PUT operation (fixes #2865) 2016-12-30 12:26:31 -05:00
Ludovic Marcotte 479b534857 (fix) properly save email alarms in the db (fixes #3949) 2016-12-30 10:31:29 -05:00
Ludovic Marcotte f6bcf26793 Updated NEWS 2016-12-30 10:00:58 -05:00
Ludovic Marcotte cdf6aaa53e (fix) provide correct error response to EAS client when sending big mails (fixes #3956) 2016-12-30 09:58:51 -05:00
Ludovic Marcotte 1a9b20519e Updated NEWS 2016-12-29 16:23:07 -05:00
Ludovic Marcotte 4f211678e9 (fix) handle semicolon in value during LDIF import (fixes #1760) 2016-12-29 16:21:30 -05:00
Ludovic Marcotte e578787acd Updated NEWS 2016-12-29 14:37:04 -05:00
Ludovic Marcotte 239dffa391 (fix) handle URI in vCard photos (fixes #2683) 2016-12-29 14:35:06 -05:00
Ludovic Marcotte 6cd3d90410 Updated NEWS 2016-12-29 14:14:39 -05:00
Ludovic Marcotte 93dfbc5a94 (feat) added photo support for LDIF import (fixes #1084) 2016-12-29 14:12:45 -05:00
Ludovic Marcotte 896f57f634 Updated NEWS 2016-12-29 10:53:44 -05:00
Ludovic Marcotte f48ab310d6 (fix) add support for "other" addresses (fixes #3966) 2016-12-29 10:52:32 -05:00
Ludovic Marcotte 5bc21e4f53 (fix) avoid trying to parse empty request (fixes #3969) 2016-12-29 09:55:20 -05:00
Ludovic Marcotte 8296123650 Updated NEWS 2016-12-28 10:25:31 -05:00
Ludovic Marcotte 2cebee42b7 (feat) added new SOGoMaximumMessageSizeLimit config parameter (fixes #3510)
Conflicts:

	SoObjects/Mailer/SOGoDraftObject.m
2016-12-28 10:23:19 -05:00
Ludovic Marcotte 7c7440c988 Updated NEWS 2016-12-23 11:38:39 -05:00
Ludovic Marcotte eed5679068 (fix) properly honor "include in freebusy" (fixes #3354) 2016-12-23 11:36:44 -05:00
Ludovic Marcotte f1dbb88303 Updated NEWS 2016-12-23 10:22:07 -05:00
Ludovic Marcotte 9cb6772ae8 (fix) do not decode from hex the event's UID (fixes #3965) 2016-12-23 10:19:46 -05:00
Ludovic Marcotte dceab8fd2a Updated NEWS 2016-12-23 10:06:15 -05:00
Ludovic Marcotte 7197b7eb41 (fix) handle delegation scenario with no SENT-BY set (fixes #3368) 2016-12-23 10:04:43 -05:00
InverseBot 18b502c39c (js/css) Update generated files 2016-12-23 02:10:39 -05:00
Ludovic Marcotte 94fc5d17db (fix) also use the filter when doing DN lookups (fixes #2253) 2016-12-22 14:26:27 -05:00
Ludovic Marcotte 8c075d2e1b (fix) comment out vars too 2016-12-22 09:41:39 -05:00
Ludovic Marcotte 278aeef864 (fix) disabled IMIP force sending for Lightning (fixes #3274) 2016-12-22 08:55:12 -05:00
Ludovic Marcotte ef69344a59 Updated NEWS 2016-12-21 11:45:41 -05:00
Ludovic Marcotte 457fccd5ac (fix) append .ics extension if not present when importing events (fixes #2308) 2016-12-21 11:43:08 -05:00
Ludovic Marcotte 1da36a3ef9 (fix) always force save events creation over EAS (fixes #3958) 2016-12-21 10:47:33 -05:00
Francis Lachapelle 7327ec21b2 Avoid duplicate emails in LDAP-based address book
Fixes #3941
2016-12-21 10:37:04 -05:00
Ludovic Marcotte 94c522ea5c Updated NEWS 2016-12-21 08:50:38 -05:00
Ludovic Marcotte 5c0229466e (feat) handle alarms for tasks (fixes #3964) 2016-12-21 08:49:05 -05:00
Ludovic Marcotte eb6d234c4b Updated NEWS 2016-12-20 13:17:19 -05:00
Ludovic Marcotte 06903f8376 (fix) use the source's domain if none is defined (fixes #3523) 2016-12-20 13:15:15 -05:00
Francis Lachapelle 596c4bb4c2 (js) Fix attached links in task viewer
Fixes #3963
2016-12-20 10:44:43 -05:00
Ludovic Marcotte 3d16283d3c (fix) avoid double encoding (fixes #3960) 2016-12-20 08:56:13 -05:00
InverseBot 31d2db51ad (js/css) Update generated files 2016-12-20 01:24:11 -05:00
Ludovic Marcotte abd0c0a744 (fix) sage-guard against empty rrule 2016-12-19 15:01:31 -05:00
Francis Lachapelle 1375237697 (js) Don't allow to create a list in a remote AB 2016-12-19 12:07:17 -05:00
Francis Lachapelle 4c78d81bf7 Formatting & Comments 2016-12-19 11:15:35 -05:00
Francis Lachapelle 2e9168080c (js) Fix freebusy initialization in event editor 2016-12-19 11:05:48 -05:00
Francis Lachapelle 1eb378c956 (js) Fix display of auto-reply (vacation) message
Fixes #3940
2016-12-19 10:25:04 -05:00
Francis Lachapelle e739ed46a3 (js) Fix saving monthly BYDAY recurrence
Fixes #3948
2016-12-19 09:51:09 -05:00
Francis Lachapelle 93f559a0df Remove unnecessary div in UIxTopnavToolbar.wox 2016-12-19 09:46:58 -05:00
InverseBot 13a4398c4d (js/css) Update generated files 2016-12-17 01:24:00 -05:00
Ludovic Marcotte 462d274497 Updated NEWS 2016-12-16 15:53:12 -05:00
Ludovic Marcotte ab1c699bc7 (feat) better support for RFC 6638 (schedule-agent) (fixes #2599) 2016-12-16 15:51:19 -05:00
Ludovic Marcotte 9882052e81 Updated NEWS 2016-12-16 10:12:40 -05:00
Ludovic Marcotte 3adbef5aa4 (fix) 30 mins offset in FB 2016-12-16 10:08:04 -05:00
Ludovic Marcotte 5ecf917e6b (fix) return a 404 for public user 2016-12-16 09:07:55 -05:00
InverseBot a02642cad9 (js/css) Update generated files 2016-12-16 01:09:03 -05:00
Ludovic Marcotte 92ba421dd1 Updated NEWS 2016-12-15 14:24:08 -05:00
Ludovic Marcotte ea940e645e (fix) return login page for unknown users (fixes #2135) 2016-12-15 14:22:26 -05:00
Francis Lachapelle 651c0fdf14 Load photos of LDAP-based address books in listing
Fixes #3942
2016-12-15 10:19:47 -05:00
InverseBot 15a3bf48d9 (js/css) Update generated files 2016-12-15 01:22:53 -05:00
Ludovic Marcotte 8493701408 Updated NEWS 2016-12-14 21:22:45 -05:00
Ludovic Marcotte f0085ef43e (fix) correctly block if fail count is within interval (fixes #2850) 2016-12-14 21:20:29 -05:00
Francis Lachapelle e0108088ee (js) Rename buttons of alarm toast
Fixes #3945
2016-12-14 16:39:21 -05:00
Francis Lachapelle 5591b9d423 (js) Switch on "remember username" when necessary 2016-12-14 16:33:21 -05:00
Ludovic Marcotte ef96c21334 Updated NEWS 2016-12-14 13:46:27 -05:00
Ludovic Marcotte d8eae96d63 (fix) properly skip folders we don't want (fixes #3943) 2016-12-14 13:42:22 -05:00
InverseBot f6acc3e984 (js/css) Update generated files 2016-12-11 01:19:01 -05:00
Ludovic Marcotte 340ddf0ae6 (fix) make sure we safely escape all chars 2016-12-09 10:45:44 -05:00
Ludovic Marcotte b0fcaeef86 (fix) removed old code 2016-12-09 10:32:42 -05:00
Ludovic Marcotte ec19a1290d (fix) use the organizer's alarm by default when accepting IMIP messages (fixes #3934) 2016-12-09 09:47:48 -05:00
InverseBot 384f1cb53f (js/css) Update generated files 2016-12-09 01:17:34 -05:00
Francis Lachapelle d0e0d5300a (js) Fix ordering of calendars
Fixes #3931
2016-12-08 10:17:25 -05:00
InverseBot 4b2b3693c5 (js/css) Update generated files 2016-12-08 01:18:39 -05:00
Francis Lachapelle 011fae8a65 Download attachments of a message as a zip archive 2016-12-07 14:38:33 -05:00
Ludovic Marcotte 2a2ebd553e (test) handle sanitization before passing data to libxml 2016-12-06 14:05:27 -05:00
Francis Lachapelle 0038e4bc60 (js) Disable HTML entities in CKEditor 2016-12-06 10:08:02 -05:00
InverseBot e053f12e78 (js/css) Update generated files 2016-12-06 01:17:41 -05:00
Francis Lachapelle c663f7ab4c Prevent using localhost on auxiliary IMAP accounts 2016-12-05 16:12:16 -05:00
Ludovic Marcotte 68ddcd7045 (fix) prevent using localhost on additional IMAP accounts 2016-12-05 14:51:33 -05:00
InverseBot ffd69337f6 (js/css) Update generated files 2016-12-03 02:04:11 -05:00
Francis Lachapelle ba5bd65ea3 Fix support for SOGoSieveFolderEncoding defaults
Cont'd. Fixes #3904
2016-12-02 17:38:33 -05:00
Francis Lachapelle 6ce64a9a27 (js) Fix sorting after renaming an AddressBook 2016-12-02 14:48:59 -05:00
Ludovic Marcotte 41701cc94c (fix) properly set interval for monthly recurring events using EAS 2016-12-02 09:21:18 -05:00
Ludovic Marcotte 56d7f634a0 (fix) avoid compiler warning 2016-12-02 08:48:26 -05:00
InverseBot 80f67785c3 (js/css) Update generated files 2016-12-02 01:29:36 -05:00
Francis Lachapelle 29dfadfe75 (css) Update default theme 2016-12-01 17:47:29 -05:00
Francis Lachapelle fb743d6381 Update Developer's Guide 2016-12-01 16:01:45 -05:00
Francis Lachapelle 203dcf1d05 (js) Improve sgAvatarImage (cont'd) 2016-12-01 15:42:28 -05:00
Francis Lachapelle 0d95267fe5 Fix ACLs management of folders in Administration
The active (super) user was not able to add herself to the ACLs of a
different user because we were removing the active user from the users
search instead of removing the folder's owner.
2016-12-01 15:40:38 -05:00
Francis Lachapelle e997923c31 (html) Fix sidenav breakpoint in Admin module 2016-12-01 15:32:24 -05:00
237 changed files with 8949 additions and 4954 deletions

No files matched your search

+95 -4
View File
@@ -83,7 +83,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{
NSArray *emails, *addresses, *categories, *elements;
CardElement *n, *homeAdr, *workAdr;
NSMutableArray *other_addresses;
CardElement *n, *homeAdr, *workAdr, *otherAdr;
NSMutableString *s, *a;
NSString *url;
id o;
@@ -186,7 +187,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", a];
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
@@ -217,7 +218,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", a];
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
@@ -232,6 +233,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<BusinessCountry xmlns=\"Contacts:\">%@</BusinessCountry>", [o activeSyncRepresentationInContext: context]];
}
// Other Address
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
if ([other_addresses count])
{
otherAdr = [other_addresses objectAtIndex: 0];
a = [NSMutableString string];
if ((o = [otherAdr flattenedValueAtIndex: 2 forKey: @""]))
[a appendString: [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
[s appendFormat: @"<OtherStreet xmlns=\"Contacts:\">%@</OtherStreet>", a];
if ((o = [otherAdr flattenedValueAtIndex: 3 forKey: @""]))
[s appendFormat: @"<OtherCity xmlns=\"Contacts:\">%@</OtherCity>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 4 forKey: @""]))
[s appendFormat: @"<OtherState xmlns=\"Contacts:\">%@</OtherState>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 5 forKey: @""]))
[s appendFormat: @"<OtherPostalCode xmlns=\"Contacts:\">%@</OtherPostalCode>", [o activeSyncRepresentationInContext: context]];
if ((o = [otherAdr flattenedValueAtIndex: 6 forKey: @""]))
[s appendFormat: @"<OtherCountry xmlns=\"Contacts:\">%@</OtherCountry>", [o activeSyncRepresentationInContext: context]];
}
// Other, less important fields
if ((o = [self birthday]))
{
@@ -267,7 +301,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inContext: (WOContext *) context
{
CardElement *element;
NSMutableArray *addressLines;
NSMutableArray *addressLines, *other_addresses;
id o;
// Contact's note
@@ -398,6 +432,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
atIndex: 6 forKey: @""];
}
// OtherCountry
//
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
if ([other_addresses count])
element = [other_addresses objectAtIndex: 0];
else
{
element = [CardElement elementWithTag: @"adr"];
[self addChild: element];
}
if ((o = [theValues objectForKey: @"OtherStreet"]) || ![self _isGhosted: @"OtherStreet" inContext: context])
{
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
[element setSingleValue: @""
atIndex: 1 forKey: @""];
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
atIndex: 2 forKey: @""];
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
if ([addressLines count] > 1)
{
[addressLines removeObjectAtIndex: 0];
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
atIndex: 1 forKey: @""];
}
}
if ((o = [theValues objectForKey: @"OtherCity"]) || ![self _isGhosted: @"OtherCity" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherCity"]
atIndex: 3 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherState"]) || ![self _isGhosted: @"OtherState" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherState"]
atIndex: 4 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherPostalCode"]) || ![self _isGhosted: @"OtherPostalCode" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherPostalCode"]
atIndex: 5 forKey: @""];
}
if ((o = [theValues objectForKey: @"OtherCountry"]) || ![self _isGhosted: @"OtherCountry" inContext: context])
{
[element setSingleValue: [theValues objectForKey: @"OtherCountry"]
atIndex: 6 forKey: @""];
}
// Company's name
if ((o = [theValues objectForKey: @"CompanyName"]))
[self setOrg: o units: nil];
+39 -5
View File
@@ -269,6 +269,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncAddCommand: (id <DOMElement>) theDocumentElement
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
objectsToTouch: (NSMutableArray *) objectsToTouch
inBuffer: (NSMutableString *) theBuffer
{
NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues;
@@ -280,6 +281,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
int i;
additions = (id)[theDocumentElement getElementsByTagName: @"Add"];
if ([additions count])
{
for (i = 0; i < [additions count]; i++)
@@ -367,9 +369,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
[o takeActiveSyncValues: allValues inContext: context];
[sogoObject setIsNew: is_new];
[sogoObject saveComponent: o];
if (theFolderType == ActiveSyncEventFolder)
[sogoObject saveComponent: o force: YES];
else
[sogoObject saveComponent: o];
}
if ([sogoObject isKindOfClass: [SOGoAppointmentObject class]] && [sogoObject resourceHasAutoAccepted])
[objectsToTouch addObject: sogoObject];
// Update syncCache
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
@@ -421,7 +431,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", easId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Add>"];
}
}
}
@@ -465,7 +474,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncChangeCommand: (id <DOMElement>) theDocumentElement
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
inBuffer: (NSMutableString *) theBuffer
objectsToTouch: (NSMutableArray *) objectsToTouch
inBuffer: (NSMutableString *) theBuffer
{
NSDictionary *allChanges;
NSString *serverId, *easId, *origServerId, *mergedFolder;
@@ -505,6 +515,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncChangeCommand: theDocumentElement
inCollection: [self collectionFromId: mergedFolder type: theFolderType]
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
continue;
@@ -588,7 +599,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
{
[o takeActiveSyncValues: allChanges inContext: context];
[sogoObject saveComponent: o];
if (theFolderType == ActiveSyncEventFolder)
{
[sogoObject saveComponent: o force: YES];
if ([sogoObject resourceHasAutoAccepted])
[objectsToTouch addObject: sogoObject];
}
else
[sogoObject saveComponent: o];
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
@@ -1637,6 +1656,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inCollection: (id) theCollection
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
inBuffer: (NSMutableString *) theBuffer
objectsToTouch: (NSMutableArray *) objectsToTouch
processed: (BOOL *) processed
{
id <DOMElement> aCommand, element;
@@ -1665,6 +1685,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncAddCommand: element
inCollection: theCollection
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
*processed = YES;
}
@@ -1674,6 +1695,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncChangeCommand: element
inCollection: theCollection
withType: theFolderType
objectsToTouch: objectsToTouch
inBuffer: theBuffer];
*processed = YES;
}
@@ -1711,7 +1733,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *filterType, *lastServerKey, *syncKeyInCache, *folderKey;
NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableArray *supportedElements, *supportedElementNames;
NSMutableArray *supportedElements, *supportedElementNames, *objectsToTouch;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
@@ -1941,6 +1963,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// We process the commands from the request
//
objectsToTouch = [NSMutableArray array];
if (!first_sync)
{
NSMutableString *s;
@@ -1953,6 +1977,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
inCollection: collection
withType: folderType
inBuffer: s
objectsToTouch: objectsToTouch
processed: &processed];
// Windows phones don't like empty Responses tags - such as: <Responses></Responses>.
@@ -2271,6 +2296,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendString: changeBuffer];
[theBuffer appendString: @"</Collection>"];
// We touch objects (likely only SOGoAppointmentObjects for now) that might have been changed
// by the server and that we want the EAS client to re-pull. For example, that's the case for
// auto-accepted events by resources
for (i = 0; i < [objectsToTouch count]; i++)
{
sleep(1);
[[objectsToTouch objectAtIndex: i] touch];
}
}
//
+86 -30
View File
@@ -43,6 +43,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGObjWeb/WOCoreApplication.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalPerson.h>
#import <NGExtensions/NGBase64Coding.h>
@@ -59,8 +61,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeFileData.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGConcreteMimeType.h>
#import <NGMail/NGMimeMessageParser.h>
#import <NGMail/NGMimeMessageGenerator.h>
@@ -947,6 +950,7 @@ void handle_eas_terminate(int signum)
command_count++;
}
[o destroy];
continue;
}
// Remove the folder from device if it is a contact folder and we have no SOGoRole_ObjectViewer.
@@ -1097,6 +1101,7 @@ void handle_eas_terminate(int signum)
![currentFolder synchronize])
{
[folders removeObjectAtIndex: count];
continue;
}
// Remove the folder from the device if it is a contact folder and we have no SOGoRole_ObjectViewer access right.
@@ -1815,7 +1820,10 @@ void handle_eas_terminate(int signum)
{
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
inContainer: collection];
[appointmentObject saveComponent: event];
if (folderType == ActiveSyncEventFolder)
[appointmentObject saveComponent: event force: YES];
else
[appointmentObject saveComponent: event];
}
if (uidCache && [calendarId length] > 64)
@@ -2651,6 +2659,7 @@ void handle_eas_terminate(int signum)
NSData *d;
unsigned int startdate, enddate, increments;
NGCalendarDateRange *r1, *r2;
char c;
startDate = [[[(id)[theDocumentElement getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate];
@@ -2697,17 +2706,13 @@ void handle_eas_terminate(int signum)
folder = [user personalCalendarFolderInContext: context];
freebusy = [folder fetchFreeBusyInfosFrom: startDate to: endDate];
NGCalendarDateRange *r1, *r2;
for (j = 1; j <= increments; j++)
for (j = 0; j < increments; j++)
{
c = '0';
r1 = [NGCalendarDateRange calendarDateRangeWithStartDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60)]
endDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60 + 30)]];
for (k = 0; k < [freebusy count]; k++)
{
@@ -2725,11 +2730,8 @@ void handle_eas_terminate(int signum)
[s appendFormat: @"%c", c];
}
[s appendString: @"</MergedFreeBusy>"];
[s appendString: @"</Availability>"];
[s appendString: @"</Recipient>"];
[s appendString: @"</Response>"];
}
@@ -3167,6 +3169,28 @@ void handle_eas_terminate(int signum)
return nil;
}
//
// See https://msdn.microsoft.com/en-us/library/ee218647(v=exchg.80).aspx
// for valid status codes.
//
- (NSData *) _sendMailErrorResponseWithStatus: (int) status
{
NSMutableString *s;
NSData *d;
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
[s appendFormat: @"<Status>%d</Status>", status];
[s appendString: @"</SendMail>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
return d;
}
//
//
//
@@ -3180,6 +3204,7 @@ void handle_eas_terminate(int signum)
NSData *new_from_header;
NSDictionary *identity;
NSString *fullName, *email;
NGMimeType *contentType;
NSArray *from;
const char *bytes;
@@ -3195,6 +3220,40 @@ void handle_eas_terminate(int signum)
message = [parser parsePartFromData: data];
RELEASE(parser);
// If an EAS client is trying to send an invitation email (request or response), we make sure to
// remove all attendees that have NO email addresses. Outlook 2016 (and likely other EAS clients)
// do that when sending IMIP only to "newly added or deleted attendees" - existing attendees have
// their email addresses stripped, while keeping the display name value.
contentType = [message contentType];
if ([contentType isKindOfClass: [NGConcreteTextMimeType class]] &&
[[[message contentType] subType] caseInsensitiveCompare: @"calendar"] == NSOrderedSame &&
([[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"request"] == NSOrderedSame ||
[[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"reply"] == NSOrderedSame))
{
NGMimeMessageGenerator *generator;
iCalCalendar *calendar;
iCalPerson *attendee;
NSArray *attendees;
iCalEvent *event;
calendar = [iCalCalendar parseSingleFromSource: [message body]];
event = [[calendar events] lastObject];
attendees = [event attendees];
for (i = [attendees count]-1; i >= 0; i--)
{
attendee = [attendees objectAtIndex: i];
if (![attendee rfc822Email] || [[attendee rfc822Email] caseInsensitiveCompare: @"nomail"] == NSOrderedSame)
[event removeFromAttendees: attendee];
}
// We regenerate the data to use
[message setBody: [[calendar versitString] dataUsingEncoding: NSUTF8StringEncoding]];
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
data = [NSMutableData dataWithData: [generator generateMimeFromPart: message]];
}
from = [message headersForKey: @"from"];
if (![from count] || ![self _isEMailValid: [[from objectAtIndex: 0] pureEMailAddress]] ||
@@ -3296,20 +3355,7 @@ void handle_eas_terminate(int signum)
{
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.0)
{
NSMutableString *s;
NSData *d;
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
[s appendString: @"<Status>120</Status>"];
[s appendString: @"</SendMail>"];
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 120]];
}
else
{
@@ -3993,23 +4039,32 @@ void handle_eas_terminate(int signum)
}
xml = [NSString stringWithFormat: @"<?xml version=\"1.0\"?><!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\"><%@ xmlns=\"ComposeMail:\"><SaveInSentItems/><MIME>%@</MIME></%@>", cmdName, [s stringByEncodingBase64], cmdName];
d = [xml dataUsingEncoding: NSASCIIStringEncoding];
}
else
{
d = [[theRequest content] wbxml2xml];
// Handle empty Ping request, no need to try decoding the WBXML blob here
if ([[theRequest content] length])
d = [[theRequest content] wbxml2xml];
else
d = nil;
}
documentElement = nil;
if (!d)
{
// If we got no data in the SendMail request, that means SOPE rejected it because of the WOMaxUploadSize.
// We generate here the proper failed response for SendMail
if ([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame)
{
[theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"];
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 122]];
goto return_response;
}
// We check if it's a Ping command with no body.
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
else if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
{
RELEASE(context);
RELEASE(pool);
@@ -4047,6 +4102,7 @@ void handle_eas_terminate(int signum)
[self performSelector: aSelector withObject: documentElement withObject: theResponse];
return_response:
[theResponse setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"];
[theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
+45 -35
View File
@@ -163,7 +163,7 @@ struct GlobalObjectId {
const char *bytes;
uid = [event uid];
uidAsASCII = [uid decodeFromHexidecimal];
uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding];
newGlobalId = (struct GlobalObjectId*)calloc(sizeof(uint8_t), sizeof(struct GlobalObjectId) + 0x0c + [uidAsASCII length]);
prefix = @"040000008200e00074c5b7101a82e008";
@@ -726,12 +726,53 @@ struct GlobalObjectId {
//
//
//
- (iCalCalendar *) calendarFromIMIPMessage
- (iCalCalendar *) _calendarFromParts: (NSArray *) parts
parent: (id) parent
{
NSDictionary *part;
id bodyPart;
int i;
for (i = 0; i < [parts count]; i++)
{
part = [parts objectAtIndex: i];
bodyPart = [parent lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
inContext: self->context];
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
{
if (bodyPart)
{
iCalCalendar *calendar;
NSData *calendarData;
calendarData = [bodyPart fetchBLOBWithPeek: YES];
calendar = nil;
NS_DURING
calendar = [iCalCalendar parseSingleFromSource: calendarData];
NS_HANDLER
calendar = nil;
NS_ENDHANDLER
return calendar;
}
}
else if ([[part objectForKey: @"type"] isEqualToString: @"multipart"])
return [self _calendarFromParts: [part objectForKey: @"parts"] parent: bodyPart];
}
return nil;
}
//
//
//
- (iCalCalendar *) calendarFromIMIPMessage
{
NSString *type, *subtype;
NSArray *parts;
int i;
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
@@ -764,38 +805,7 @@ struct GlobalObjectId {
parts = [[self bodyStructure] objectForKey: @"parts"];
if ([parts count] > 1)
{
for (i = 0; i < [parts count]; i++)
{
part = [parts objectAtIndex: i];
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
{
id bodyPart;
bodyPart = [self lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
inContext: self->context];
if (bodyPart)
{
iCalCalendar *calendar;
NSData *calendarData;
calendarData = [bodyPart fetchBLOBWithPeek: YES];
calendar = nil;
NS_DURING
calendar = [iCalCalendar parseSingleFromSource: calendarData];
NS_HANDLER
calendar = nil;
NS_ENDHANDLER
return calendar;
}
}
}
}
return [self _calendarFromParts: parts parent: self];
return nil;
}
+48 -28
View File
@@ -36,6 +36,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalTrigger.h>
#import <NGCards/NSCalendarDate+NGCards.h>
#include "NSDate+ActiveSync.h"
@implementation iCalAlarm (ActiveSync)
@@ -53,9 +56,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
nextAlarmDate = [self nextAlarmDate];
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
if ([parent isKindOfClass: [iCalEvent class]])
{
// don't send negative reminder - not supported
if (delta > 0)
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
}
else
{
[s appendFormat: @"<ReminderTime xmlns=\"Task:\">%@</ReminderTime>", [nextAlarmDate activeSyncRepresentationInContext: context]];
}
}
return s;
@@ -67,37 +77,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
iCalTrigger *trigger;
id o;
o = [theValues objectForKey: @"Reminder"];
// Outlook: if reminder is set to 0 minutes before start save it as 1 minute since -> 0 minutes in not accepted by SOGo
if ([o isEqualToString: @"0"])
o = @"1";
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DURATION"];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
// 1week = -P1W
// 2weeks > -PxD
// xdays > -PxD
// xhours -> -PTxH
// xmin -> -PTxM
if ([o intValue] == 10080)
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
else
if ((o = [theValues objectForKey: @"Reminder"]))
{
if (([o intValue] % 1440) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
// Outlook: if reminder is set to 0 minutes before starttime, save it as 1 minute since -> 0 minutes in not accepted by SOGo
if ([o isEqualToString: @"0"])
o = @"1";
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DURATION"];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
// 1week = -P1W
// 2weeks > -PxD
// xdays > -PxD
// xhours -> -PTxH
// xmin -> -PTxM
if ([o intValue] == 10080)
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
else
{
if (([o intValue] % 60) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
if (([o intValue] % 1440) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
else
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
{
if (([o intValue] % 60) == 0)
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
else
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
}
}
}
else if ((o = [theValues objectForKey: @"ReminderTime"]))
{
o = [o calendarDate];
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
[trigger setValueType: @"DATE-TIME"];
[trigger setSingleValue: [NSString stringWithFormat: @"%@Z", [o iCalFormattedDateTimeString]] forKey: @""];
[self setTrigger: trigger];
[self setAction: @"DISPLAY"];
}
}
@end
+3 -1
View File
@@ -602,7 +602,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
// Recurrence
if ((o = [theValues objectForKey: @"Recurrence"]))
if ((o = [theValues objectForKey: @"Recurrence"]) && [o isKindOfClass: [NSDictionary class]])
{
iCalRecurrenceRule *rule;
@@ -884,6 +884,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self setAttendees: attendees];
}
}
[self setLastModified: [NSCalendarDate calendarDate]];
}
- (void) changeParticipationStatus: (NSDictionary *) theValues
@@ -134,6 +134,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, firstOccurrence];
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
}
else if ([[self byMonthDay] count])
{
@@ -149,6 +150,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
dm = [self byDayMask];
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, 5];
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
}
else
{
+25 -3
View File
@@ -43,9 +43,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
#include "iCalRecurrenceRule+ActiveSync.h"
#include "iCalAlarm+ActiveSync.h"
@implementation iCalToDo (ActiveSync)
@@ -95,8 +98,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
v = 1;
[s appendFormat: @"<Importance xmlns=\"Tasks:\">%d</Importance>", v];
// Reminder - FIXME
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
// Reminder
if ([self hasAlarms])
{
iCalAlarm *alarm;
alarm = [self firstDisplayOrAudioAlarm];
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 1];
[s appendString: [alarm activeSyncRepresentationInContext: context]];
}
else
{
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
}
// Sensitivity
if ([[self accessClass] isEqualToString: @"PRIVATE"])
@@ -145,7 +159,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: @"</Body>"];
}
}
return s;
}
@@ -249,6 +263,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"ReminderTime"]))
{
iCalAlarm *alarm;
alarm = [[iCalAlarm alloc] init];
[alarm takeActiveSyncValues: theValues inContext: context];
[self removeAllAlarms];
[self addToAlarms: alarm];
RELEASE(alarm);
}
+1686
View File
File diff suppressed because it is too large. Load diff
+12 -6
View File
@@ -118,6 +118,11 @@ http://trochette.github.io/Angular-Design-Patterns-Best-Practices/
* Move business logic into models and share them.
* Keep it simple, separate server interaction and error handling from the model. That way model only handle data processing and code is easier to maintain.
Controllers
^^^^^^^^^^^
* Functions not exposed in the controller must be prefixed with an underscore.
SASS/CSS
~~~~~~~~
@@ -133,11 +138,6 @@ SASS/CSS
A `@mixin` is like a stamp: it creates a duplicated version of the property block (optionally) with arguments provided. An `@extend` appends the element you are extending to the property block. It is your "yes, and ___" statement.
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
git submodule init
git submodule update
* FlexBox compatibility http://caniuse.com/#feat=flexbox
Fonts
@@ -155,10 +155,16 @@ Building frontend
npm install -g grunt-cli
npm install -g bower
* Generate JavaScript files
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
git submodule init
git submodule update
* Generate the JavaScript and CSS files
cd UI/WebServerResources
npm install
bower install
grunt build
Version Control
+7 -2
View File
@@ -391,9 +391,14 @@ internet connection could be killed prematurely.
|Parameter used to set the maximum allowed size for content being
sent to SOGo using a PUT or a POST call. This can also limit the file
attachment size being uploaded to SOGo when composing a mail. The
value is in kilobyte. By default, the value is 0, or disabled so no
value is in kilobytes. By default, the value is 0, or disabled so no
limit will be set.
|S |SOGoMaximumMessageSizeLimit
|Parameter used to set the maximum allowed email message size when
composing a mail. The value is in kilobytes. By default, the value is 0,
or disabled so no limit will be set.
|S |SxVMemLimit
|Parameter used to set the maximum amount of memory (in
megabytes) that a child can use. Reaching that value will force children
@@ -501,7 +506,7 @@ SOGoSAML2LogoutEnabled must be set to YES. If unset, the user will be
redirected to a blank page.
|D |SOGoTimeZone
|Parameter used to set a default time zone for users. The default
|Mandatory parameter used to set a default time zone for users. The default
timezone is set to UTC. The Olson database is a standard database that
takes all the time zones around the world into account and represents
them along with their history. On GNU/Linux systems, time zone
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 3.2.4 - December 2016</releaseinfo>
<subtitle>for version 3.2.4</subtitle>
<date>2016-12-01</date>
<releaseinfo>Version 3.2.6 - January 2017</releaseinfo>
<subtitle>for version 3.2.6</subtitle>
<date>2017-01-23</date>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
@@ -6,13 +6,13 @@
Authors:
- Inverse inc. <info@inverse.ca>
Copyright (C) 2008-2016 Inverse inc.
Copyright (C) 2008-2017 Inverse inc.
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
////
// TODO have the build system take care of this
:release_version: 3.2.4
:release_version: 3.2.6
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
+10 -3
View File
@@ -27,6 +27,7 @@
#import <GDLContentStore/GCSAlarmsFolder.h>
#import <GDLContentStore/GCSSessionsFolder.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoClassSecurityInfo.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WORequest+So.h>
@@ -114,7 +115,7 @@ static BOOL debugLeaks;
vMemSizeLimit = [defaults vmemLimit];
if (vMemSizeLimit > 0)
[self logWithFormat: @"vmem size check enabled: shutting down app when "
@"vmem > %d MB", vMemSizeLimit];
@"vmem > %d MB. Currently at %d MB", vMemSizeLimit, [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
/* SoClass security declarations */
sInfo = [self soClassSecurityInfo];
@@ -137,6 +138,8 @@ static BOOL debugLeaks;
/* load products */
[[SOGoProductLoader productLoader] loadAllProducts: YES];
if (vMemSizeLimit > 0)
[self logWithFormat: @"All products loaded - current memory usage at %d MB", [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
}
- (id) init
@@ -418,8 +421,12 @@ static BOOL debugLeaks;
"GET" if no method was provided in the query path.
*/
if ([_key length] > 0 && ![_key isEqualToString:@"favicon.ico"])
obj = [self lookupUser: _key inContext: _ctx];
}
{
obj = [self lookupUser: _key inContext: _ctx];
if (!obj && ![_key isEqualToString: @"public"])
obj = [self lookupUser: @"anonymous" inContext: _ctx];
}
}
}
else
obj = nil;
+71
View File
@@ -1,3 +1,74 @@
3.2.6 (2017-01-23)
------------------
Enhancements
- [web] show locale codes beside language names in Preferences module
- [web] fixed visual glitches in Month view with Firefox
- [web] mail editor can now be expanded horizontally and automatically expands vertically
- [web] compose a new message inline or in a popup window
- [web] allow to select multiple files when uploading attachments (#3999)
- [web] use "date" extension of Sieve to enable/disable vacation auto-reply (#1530, #1949)
- [web] updated Angular to version 1.6.1
- [web] updated CKEditor to version 4.6.2
Bug fixes
- [core] remove all alarms before sending IMIP replies (#3925)
- [web] fixed rendering of forwared HTML message with inline images (#3981)
- [web] fixed pasting images in CKEditor using Chrome (#3986)
- [eas] make sure we trigger a download of service-side changed events
- [eas] now strip attendees with no email during MeetingResponse calls
3.2.5 (2017-01-10)
------------------
New features
- [web] download attachments of a message as a zip archive
Enhancements
- [core] improved IMIP handling from Exchange/Outlook clients
- [web] prevent using localhost on additional IMAP accounts
- [web] renamed buttons of alarm toast (#3945)
- [web] load photos of LDAP-based address books in contacts list (#3942)
- [web] added SOGoMaximumMessageSizeLimit to limit webmail message size
- [web] added photo support for LDIF import (#1084)
- [web] updated CKEditor to version 4.6.1
Bug fixes
- [core] honor blocking wrong login attempts within time interval (#2850)
- [core] better support for RFC 6638 (schedule-agent)
- [core] use source's domain when none defined and trying to match users (#3523)
- [core] handle delegation with no SENT-BY set (#3368)
- [core] properly honor the "include in freebusy" setting (#3354)
- [core] properly save next email alarm in the database (#3949)
- [core] fix events in floating time during CalDAV's PUT operation (#2865)
- [core] handle rounds in sha512-crypt password hashes
- [web] fixed confusion between owner and active user in ACLs management of Administration module
- [web] fixed JavaScript exception after renaming an address book
- [web] fixed Sieve folder encoding support (#3904)
- [web] fixed ordering of calendars when renaming or adding a calendar (#3931)
- [web] use the organizer's alarm by default when accepting IMIP messages (#3934)
- [web] switch on "Remember username" when cookie username is set
- [web] return login page for unknown users (#2135)
- [web] fixed saving monthly recurrence rule with "by day" condition (#3948)
- [web] fixed display of message content when enabling auto-reply (#3940)
- [web] don't allow to create lists in a remote address book (not yet supported)
- [web] fixed attached links in task viewer (#3963)
- [web] avoid duplicate mail entries in contact of LDAP-based address book (#3941)
- [web] append ics file extension when importing events (#2308)
- [web] handle URI in vCard photos (#2683)
- [web] handle semicolon in values during LDIF import (#1760)
- [web] fixed computation of week number (#3973, #3976)
- [web] fixed saving of inactive calendars (#3862, #3980)
- [web] fixed public URLs to Calendars (#3974)
- [web] fixed hotkeys in Mail module when a dialog is active (#3983)
- [eas] properly skip folders we don't want to synchronize (#3943)
- [eas] fixed 30 mins freebusy offset with S Planner
- [eas] now correctly handles reminders on tasks (#3964)
- [eas] always force save events creation over EAS (#3958)
- [eas] do not decode from hex the event's UID (#3965)
- [eas] add support for "other addresses" (#3966)
- [eas] provide correct response status when sending too big mails (#3956)
3.2.4 (2016-12-01)
------------------
@@ -61,6 +61,8 @@
long i, count, repeatCount;
unsigned interval;
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
startDate = [_r startDate];
endDate = [_r endDate];
+1 -1
View File
@@ -141,7 +141,7 @@
[updatedProperties addObject:@"accessClass"];
if(!IS_EQUAL([_from sequence], [_to sequence], isEqualToNumber:))
[updatedProperties addObject:@"sequence"];
if(!IS_EQUAL([_from organizer], [_to organizer], isEqual:))
if(!IS_EQUAL([[_from organizer] rfc822Email], [[_to organizer] rfc822Email], isEqual:))
[updatedProperties addObject:@"organizer"];
if(!IS_EQUAL([_from recurrenceRules], [_to recurrenceRules], isEqual:))
[updatedProperties addObject:@"rrule"];
+2 -2
View File
@@ -108,9 +108,9 @@ extern NSString *iCalWeekDayString[];
/* parse complete iCal RRULE */
- (void) setRrule: (NSString *) _rrule; // TODO: weird name? (better: RRule?)
- (void) setRrule: (NSString *) _rrule;
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;;
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;
@end
+24
View File
@@ -921,4 +921,28 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
return isEqual;
}
- (NSString *) description
{
NSMutableString *str;
str = [NSMutableString stringWithString: [self flattenedValuesForKey: @"freq"]];
if ([self repeatInterval] > 1)
[str appendFormat: @" interval of %i", [self repeatInterval]];
if ([self repeatCount])
[str appendFormat: @" for %i times", [self repeatCount]];
if ([self untilDate])
[str appendFormat: @" until %@", [self untilDate]];
if ([[self valuesAtIndex: 0 forKey: @"bymonthday"] count])
[str appendFormat: @" BYMONTHDAY:%@", [self flattenedValuesForKey: @"bymonthday"]];
if ([[self valuesAtIndex: 0 forKey: @"byday"] count])
[str appendFormat: @" BYDAY:%@", [self flattenedValuesForKey: @"byday"]];
if ([[self valuesAtIndex: 0 forKey: @"bymonth"] count])
[str appendFormat: @" BYMONTH:%@", [self flattenedValuesForKey: @"bymonth"]];
if ([[self valuesAtIndex: 0 forKey: @"bysetpos"] count])
[str appendFormat: @" BYSETPOS:%@", [self flattenedValuesForKey: @"bysetpos"]];
return str;
}
@end /* iCalRecurrenceRule */
@@ -70,7 +70,7 @@
iCalByDayMask *dayMask;
BOOL hasRepeatCount;
[self logWithFormat: @"Weekly %@", rrule];
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
startDate = [_r startDate];
+1 -1
View File
@@ -110,7 +110,7 @@
//SOGoTrustProxyAuthentication = NO;
//SOGoXSRFValidationEnabled = YES;
/* General */
/* General - SOGoTimeZone *MUST* be defined */
//SOGoLanguage = English;
//SOGoTimeZone = America/Montreal;
//SOGoCalendarDefaultRoles = (
+4 -2
View File
@@ -133,6 +133,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, [rawRequest UTF8String]);
//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
//curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function_freebusy);
@@ -141,8 +142,9 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
// Perform SOAP request
rc = curl_easy_perform(curl);
if (rc != 0)
[self errorWithFormat: @"CURL error while accessing %@ (%d): ", url, rc, [NSString stringWithCString: error]];
if (rc != CURLE_OK)
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", url, rc,
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
curl_easy_cleanup(curl);
curl_slist_free_all(headerlist);
@@ -114,6 +114,7 @@
- (NSString *) bias
{
#if 0
NSTimeZone *userTimeZone;
NSInteger secs;
@@ -121,6 +122,9 @@
secs = [userTimeZone secondsFromGMT];
return [NSString stringWithFormat: @"%i", (secs/60)];
#else
return @"0";
#endif
}
@end
+25 -13
View File
@@ -423,26 +423,32 @@ static Class iCalEventK = nil;
//
// If the user is the owner of the calendar, by default we include the freebusy information.
//
// If the user is NOT the owner of the calendar, by default we exclude the freebusy information.
// If the user is NOT the owner of the calendar but is subscribed, by default we exclude the freebusy information.
//
// We must include the freebusy information of other users if we are actually looking at their freebusy information
// but we aren't necessarily subscribed to their calendars.
//
- (BOOL) includeInFreeBusy
{
NSString *userLogin, *subscriptionReference;
NSNumber *excludeFromFreeBusy;
NSString *userLogin;
BOOL is_owner;
BOOL is_owner, is_subscription;
userLogin = [[context activeUser] login];
is_owner = [userLogin isEqualToString: self->owner];
// We check if we're subscribed. We must NOT use here [self isSubscribed] as it'll look
// at the owner's settings, and not the activeUser's settings.
subscriptionReference = [NSString stringWithFormat: @"%@:Calendar/%@", self->owner, [self nameInContainer]];
is_subscription = [[[[[self->context activeUser] userSettings] objectForKey: @"Calendar"] objectForKey: @"SubscribedFolders"] containsObject: subscriptionReference];
// Check if the owner (not the active user) has excluded the calendar from her/his free busy data.
excludeFromFreeBusy
= [self folderPropertyValueInCategory: @"FreeBusyExclusions"
forUser: [context activeUser]];
if ([self isSubscription])
if (is_subscription)
{
// If the user has not yet set an include/not include fb information let's EXCLUDE it.
if (!excludeFromFreeBusy)
@@ -696,7 +702,7 @@ static Class iCalEventK = nil;
if ([title length])
[baseWhere
addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString]]];
if (component)
{
@@ -1436,14 +1442,14 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
{
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString],
[title asSafeSQLString],
[title asSafeSQLString]]];
}
}
else
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
[title asSafeSQLString]]];
}
/* prepare mandatory fields */
@@ -2619,7 +2625,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
if (uid && folder)
{
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_uid = %@",
uid];
[uid asSafeSQLString]];
records = [folder fetchFields: nameFields matchingQualifier: qualifier];
count = [records count];
if (count)
@@ -3237,6 +3243,12 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
}
}
// If the UID isn't ending with the ".ics" extension, let's add it to avoid
// confusing broken CalDAV client (like Nokia N9 and Korganizer) that relies
// on this (see #2308)
if (![[uid lowercaseString] hasSuffix: @".ics"])
uid = [NSString stringWithFormat: @"%@.ics", uid];
object = [SOGoAppointmentObject objectWithName: uid
inContainer: self];
[object setIsNew: YES];
@@ -3316,7 +3328,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
else
{
// If the start date is a "floating time", let's use the user's timezone
// during the import for both the start and end dates.
// during the import for both the start and end dates. This is similar
// to what we do in SOGoAppointmentObject: -_adjustFloatingTimeInRequestCalendar:
NSString *s;
s = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
@@ -3400,8 +3413,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
timezone: timezone]))
{
imported++;
[uids setValue: uid
forKey: originalUid];
[uids setValue: uid forKey: originalUid];
}
}
@@ -36,6 +36,9 @@
#import "SOGoCalendarComponent.h"
@interface SOGoAppointmentObject : SOGoCalendarComponent
{
BOOL _resourceHasAutoAccepted;
}
- (NSException *) changeParticipationStatus: (NSString *) status
withDelegate: (iCalPerson *) delegate
@@ -57,6 +60,8 @@
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
fromRequest: (WORequest *) rq;
- (BOOL) resourceHasAutoAccepted;
@end
#endif /* __Appointments_SOGoAppointmentObject_H__ */
+222 -132
View File
@@ -369,6 +369,52 @@
return listHasChanged;
}
//
//
//
- (BOOL) _shouldScheduleEvent: (iCalPerson *) thePerson
{
//NSArray *userAgents;
NSString *v;
BOOL b;
//int i;
b = YES;
if (thePerson && (v = [thePerson value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
{
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
b = NO;
}
//
// If we have to deal with Thunderbird/Lightning, we always send invitation
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
// to NONE/CLIENT when responding to an external invitation received by
// SOGo - so no invitation responses are ever sent by Lightning. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
//
// This code has been disabled - see 0003274.
//
#if 0
userAgents = [[context request] headersForKey: @"User-Agent"];
for (i = 0; i < [userAgents count]; i++)
{
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
{
b = YES;
break;
}
}
#endif
return b;
}
//
//
//
@@ -394,11 +440,12 @@
owner: owner];
}
[self sendEMailUsingTemplateNamed: @"Update"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: updateAttendees
withType: @"calendar:invitation-update"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Update"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: updateAttendees
withType: @"calendar:invitation-update"];
}
// This method scans the list of attendees.
@@ -503,7 +550,9 @@
NSEnumerator *enumerator;
NSString *currentUID;
SOGoUser *user, *currentUser;
_resourceHasAutoAccepted = NO;
// Build a list of the attendees uids
attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]];
enumerator = [theAttendees objectEnumerator];
@@ -643,6 +692,7 @@
{
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
_resourceHasAutoAccepted = YES;
}
}
else
@@ -713,6 +763,7 @@
// set the resource as one!
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
_resourceHasAutoAccepted = YES;
}
} // if ([user isResource]) ...
@@ -823,11 +874,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
[self _handleRemovedUsers: deletedAttendees
withRecurrenceId: [newEvent recurrenceId]];
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [newEvent itipEntryWithMethod: @"cancel"]
previousObject: oldEvent
toAttendees: deletedAttendees
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [newEvent itipEntryWithMethod: @"cancel"]
previousObject: oldEvent
toAttendees: deletedAttendees
withType: @"calendar:cancellation"];
}
if ((ex = [self _handleAttendeesConflicts: [newEvent attendees] forEvent: newEvent force: forceSave]))
@@ -883,12 +935,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// Send an invitation to new attendees
if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent force: forceSave]))
return ex;
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: addedAttendees
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: oldEvent
toAttendees: addedAttendees
withType: @"calendar:invitation"];
}
if ([changes hasMajorChanges])
@@ -962,11 +1015,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([attendees count])
{
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [newEvent organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [newEvent itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
[self sendReceiptEmailForObject: newEvent
@@ -1166,19 +1220,17 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
statusChange: (NSString *) newStatus
inEvent: (iCalEvent *) event
{
iCalPerson *otherAttendee, *otherDelegate;
NSString *currentStatus, *organizerUID;
SOGoUser *ownerUser, *currentUser;
NSString *delegateEmail;
NSException *ex;
ex = nil;
BOOL addDelegate, removeDelegate;
currentStatus = [attendee partStat];
iCalPerson *otherAttendee, *otherDelegate;
NSString *delegateEmail;
BOOL addDelegate, removeDelegate;
otherAttendee = attendee;
ex = nil;
delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length])
@@ -1189,7 +1241,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
otherDelegate = nil;
/* We handle the addition/deletion of delegate users */
// We handle the addition/deletion of delegate users
addDelegate = NO;
removeDelegate = NO;
if (delegate)
@@ -1272,12 +1324,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
otherDelegate = nil;
}
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [event itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [event itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:cancellation"];
} // if (removeDelegate)
if (addDelegate)
@@ -1291,12 +1344,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self _addOrUpdateEvent: event
forUID: delegatedUID
owner: [theOwnerUser login]];
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:invitation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: delegates
withType: @"calendar:invitation"];
} // if (addDelegate)
// If the current user isn't the organizer of the event
@@ -1477,9 +1531,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]];
event = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
// If no occurence found, create one
if (event == nil)
// If no occurence found, create one
event = (iCalEvent*)[self newOccurenceWithID: recurrenceTime];
}
else
@@ -1558,50 +1612,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
return ex;
}
//
//
//
- (BOOL) _shouldScheduleEvent: (iCalPerson *) theOrganizer
{
NSArray *userAgents;
NSString *v;
BOOL b;
int i;
b = YES;
if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
{
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
b = NO;
}
//
// If we have to deal with Thunderbird/Lightning, we always send invitation
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
// to NONE/CLIENT when responding to an external invitation received by
// SOGo - so no invitation responses are ever sent by Lightning. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
//
userAgents = [[context request] headersForKey: @"User-Agent"];
for (i = 0; i < [userAgents count]; i++)
{
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
{
b = YES;
break;
}
}
return b;
}
//
//
//
@@ -1613,14 +1623,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
iCalEvent *event;
BOOL send_receipt;
ownerUser = [SOGoUser userWithLogin: owner];
event = [self component: NO secure: NO];
send_receipt = YES;
if (![self _shouldScheduleEvent: [event organizer]])
return;
if (occurence == nil)
{
// No occurence specified; use the master event.
@@ -1646,11 +1652,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// and send them an email.
[self _handleRemovedUsers: attendees
withRecurrenceId: recurrenceId];
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [occurence itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:cancellation"];
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [occurence itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:cancellation"];
}
}
else if ([occurence userIsAttendee: ownerUser])
@@ -1664,12 +1672,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
send_receipt = NO;
}
if (send_receipt)
[self sendReceiptEmailForObject: event
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
operation: EventDeleted];
if (send_receipt)
[self sendReceiptEmailForObject: event
addedAttendees: nil
deletedAttendees: nil
updatedAttendees: nil
operation: EventDeleted];
}
- (NSException *) prepareDelete
@@ -1853,6 +1861,54 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
}
}
//
// This is similar to what we do in SOGoAppointmentFolder: -importCalendar:
//
- (void) _adjustFloatingTimeInRequestCalendar: (iCalCalendar *) rqCalendar
{
iCalDateTime *startDate, *endDate;
NSString *startDateAsString;
SOGoUserDefaults *ud;
NSArray *allEvents;
iCalTimeZone *tz;
iCalEvent *event;
int i, delta;
allEvents = [rqCalendar events];
for (i = 0; i < [allEvents count]; i++)
{
event = [allEvents objectAtIndex: i];
if ([event isAllDay])
continue;
startDate = (iCalDateTime *)[event uniqueChildWithTag: @"dtstart"];
startDateAsString = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
if (![startDate timeZone] &&
![startDateAsString hasSuffix: @"Z"] &&
![startDateAsString hasSuffix: @"z"])
{
ud = [[context activeUser] userDefaults];
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
if ([rqCalendar addTimeZone: tz])
{
delta = [[tz periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[startDate setTimeZone: tz];
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
if (endDate)
{
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
[endDate setTimeZone: tz];
}
}
}
}
}
- (void) _decomposeGroupsInRequestCalendar: (iCalCalendar *) rqCalendar
{
NSArray *allEvents;
@@ -1942,9 +1998,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
fromRequest: (WORequest *) rq
{
SOGoUser *ownerUser;
NSException *ex;
NSArray *roles;
SOGoUser *ownerUser;
BOOL userIsOrganizer;
if (calendar == fullCalendar || calendar == safeCalendar
|| calendar == originalCalendar)
@@ -1974,6 +2032,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self _adjustEventsInRequestCalendar: calendar];
[self adjustClassificationInRequestCalendar: calendar];
[self _adjustPartStatInRequestCalendar: calendar];
[self _adjustFloatingTimeInRequestCalendar: calendar];
}
//
@@ -1984,13 +2043,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
iCalEvent *event;
NSArray *attendees;
NSString *eventUID;
BOOL scheduling;
attendees = nil;
event = [[calendar events] objectAtIndex: 0];
eventUID = [event uid];
scheduling = [self _shouldScheduleEvent: [event organizer]];
attendees = nil;
// make sure eventUID doesn't conflict with an existing event - see bug #1853
// TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1)
@@ -2003,34 +2059,51 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
// New event and we're the organizer -- send invitation to all attendees
//
if (scheduling && [event userIsOrganizer: ownerUser])
{
attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count])
{
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
return ex;
else
{
// We might have auto-accepted resources here. If that's the
// case, let's regenerate the versitstring and replace the
// one from the request.
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
}
userIsOrganizer = [event userIsOrganizer: ownerUser];
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
if (!userIsOrganizer &&
[[context activeUser] hasEmail: [[event organizer] rfc822Email]])
{
[[event organizer] setCn: [ownerUser cn]];
[[event organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[event organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
userIsOrganizer = YES;
}
if (userIsOrganizer)
{
attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count])
{
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
return ex;
else
{
// We might have auto-accepted resources here. If that's the
// case, let's regenerate the versitstring and replace the
// one from the request.
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
}
if ([self _shouldScheduleEvent: [event organizer]])
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: attendees
withType: @"calendar:invitation"];
}
}
//
// We aren't the organizer but we're an attendee. That can happen when
// we receive an external invitation (IMIP/ITIP) and we accept it
// from a CUA - it gets added to a specific CalDAV calendar using a PUT
//
else if (scheduling && [event userIsAttendee: ownerUser])
else if ([event userIsAttendee: ownerUser] && [self _shouldScheduleEvent: [event userAsAttendee: ownerUser]])
{
[self sendIMIPReplyForEvent: event
from: ownerUser
@@ -2138,8 +2211,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
if ([[newEvent attendees] count] || [[oldEvent attendees] count])
{
BOOL userIsOrganizer;
// newEvent might be nil here, if we're deleting a RECURRENCE-ID with attendees
// If that's the case, we use the oldEvent to obtain the organizer
if (newEvent)
@@ -2147,6 +2218,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
userIsOrganizer = [oldEvent userIsOrganizer: ownerUser];
// We handle the situation where the SOGo Integrator extension isn't installed or
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
// and set Bob as the organizer. We fix this for them. See #3368 for details.
if (!userIsOrganizer &&
[[context activeUser] hasEmail: [[newEvent organizer] rfc822Email]])
{
[[newEvent organizer] setCn: [ownerUser cn]];
[[newEvent organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
[[newEvent organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
userIsOrganizer = YES;
}
// With Thunderbird 10, if you create a recurring event with an exception
// occurence, and invite someone, the PUT will have the organizer in the
// recurrence-id and not in the master event. We must fix this, otherwise
@@ -2344,4 +2429,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
return response;
}
- (BOOL) resourceHasAutoAccepted
{
return _resourceHasAutoAccepted;
}
@end /* SOGoAppointmentObject */
+15 -1
View File
@@ -619,7 +619,10 @@
if (content)
ASSIGN (originalCalendar, [iCalCalendar parseSingleFromSource: content]);
else
[self warnWithFormat: @"content not available, we will crash"];
{
[self warnWithFormat: @"content not available, we don't update the event"];
return;
}
}
oldMaster = (iCalRepeatableEntityObject *)
@@ -794,6 +797,14 @@
if ([object firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"])
return;
// We never send IMIP inivitaton/deletion/update when the "initiator" is Outlook 2013/2016 over
// the EAS protocol. That is because Outlook will always issue a SendMail command
// with the meeting details (ie., IMIP message with METHOD:REQUEST) so there's
// no need to send it twice. Moreover, Outlook users can also choose to NOT send
// the IMIP messsage at all, so SOGo won't send one without user's consent
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
return;
ownerUser = [SOGoUser userWithLogin: owner];
dd = [ownerUser domainDefaults];
if ([dd appointmentSendEMailNotifications] && [object isStillRelevant])
@@ -933,6 +944,9 @@
/* get WOApplication instance */
app = [WOApplication application];
/* remove all alarms to avoid bug #3925 */
[event removeAllAlarms];
/* construct message content */
p = [app pageWithName: @"SOGoAptMailICalReply" inContext: context];
[p setApt: (iCalEvent *) event];
@@ -124,14 +124,14 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
url = [NSURL URLWithString: location];
if (url)
{
curl_global_init (CURL_GLOBAL_SSL);
curl = curl_easy_init ();
curl_global_init(CURL_GLOBAL_SSL);
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt (curl, CURLOPT_URL, [location UTF8String]);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt (curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_URL, [location UTF8String]);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
authInfos = [self _loadAuthData];
@@ -145,17 +145,17 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
/* buffering ivar, no need to retain/release */
buffer = [NSMutableData data];
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
error[0] = 0;
curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, &error);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
// Perform SOAP request
rc = curl_easy_perform (curl);
if (rc == 0)
rc = curl_easy_perform(curl);
if (rc == CURLE_OK)
{
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
[result setObject: [NSNumber numberWithUnsignedInt: status]
forKey: @"status"];
[self logWithFormat: @"Load web calendar %@ (%i)", location, status];
@@ -188,7 +188,11 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
[result setObject: @"http-error" forKey: @"error"];
}
else
[result setObject: @"bad-url" forKey: @"error"];
{
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", location, rc,
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
[result setObject: @"bad-url" forKey: @"error"];
}
curl_easy_cleanup (curl);
}
}
@@ -861,7 +861,7 @@ NSNumber *iCalDistantFutureNumber = nil;
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
// Delete old email alarms
if (alarm_number >= 0)
if (!nextAlarmDate && alarm_number >= 0)
[af deleteRecordForEntryWithCName: nameInContainer
inCalendarAtPath: [theContainer ocsPath]];
}
+1
View File
@@ -40,6 +40,7 @@
- (NSString *) workCompany;
- (NSString *) fullName;
- (NSArray *) emails;
- (NSArray *) secondaryEmails;
- (NSString *) workPhone;
+6 -3
View File
@@ -236,6 +236,7 @@ convention:
- (void) _setEmails: (NSDictionary *) ldifRecord
{
CardElement *homeMail;
NSString* mail;
// Emails from the configured mail fields of the source have already been extracted in
// [LDAPSource _fillEmailsOfEntry:intoLDIFRecord:]
@@ -243,9 +244,11 @@ convention:
ofType: @"work"
withValue: [ldifRecord objectForKey: @"c_emails"]];
// When importing an LDIF file, add the default mail attribute
[self addElementWithTag: @"email"
ofType: @"work"
withValue: [ldifRecord objectForKey: @"mail"]];
mail = [ldifRecord objectForKey: @"mail"];
if ([mail length] && ![[self emails] containsObject: mail])
[self addElementWithTag: @"email"
ofType: @"work"
withValue: [ldifRecord objectForKey: @"mail"]];
homeMail = [self elementWithTag: @"email" ofType: @"home"];
[homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""];
[[self uniqueChildWithTag: @"x-mozilla-html"]
+14 -5
View File
@@ -40,25 +40,34 @@
- (id) GETAction: (WOContext *) localContext
{
NGVCardPhoto *photo;
NSData *data;
NSString *uri;
NSData *data;
id response;
photo = [self photo];
data = nil;
uri = nil;
if ([photo isInline])
data = [photo decodedContent];
else
data = [[photo flattenedValuesForKey: @""]
dataUsingEncoding: NSISOLatin1StringEncoding];
else if ([[photo value: 0 ofAttribute: @"value"] isEqualToString: @"uri"])
uri = [photo flattenedValuesForKey: @""];
if (data)
{
response = [localContext response];
[response setHeader: [self davContentType] forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat:@" %d",
(int)[data length]]
forKey: @"content-length"];
[response setContent: data];
}
else if (uri)
{
response = [localContext response];
[response setStatus: 302];
[response setHeader: uri forKey: @"location"];
}
else
response = nil;
+3 -5
View File
@@ -121,7 +121,7 @@ static NSArray *folderListingFields = nil;
NSString *component;
Class objectClass;
qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_name = %@", [name asSafeSQLString]];
records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
matchingQualifier: qualifier];
@@ -190,8 +190,7 @@ static NSArray *folderListingFields = nil;
if ([filter length] > 0)
{
filter = [[filter stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'\\'"];
filter = [filter asSafeSQLString];
if ([criteria isEqualToString: @"name_or_address"])
qs = [NSString stringWithFormat:
@"(c_sn isCaseInsensitiveLike: '%%%@%%') OR "
@@ -338,8 +337,7 @@ static NSArray *folderListingFields = nil;
if (aName && [aName length] > 0)
{
aName = [[aName stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'\\'"];
aName = [aName asSafeSQLString];
qs = [NSString stringWithFormat: @"(c_name='%@')", aName];
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
@@ -338,6 +338,10 @@
[newRecord setObject: data forKey: @"contactInfo"];
}
// photo => hasPhoto
data = [oldRecord objectForKey: @"photo"];
[newRecord setObject: [NSNumber numberWithInt: [data length] ? 1 : 0] forKey: @"hasPhoto"];
recordSource = [oldRecord objectForKey: @"source"];
if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] &&
[[(NSObject <SOGoDNSource>*) recordSource MSExchangeHostname] length])
+1
View File
@@ -33,6 +33,7 @@
fromIndex: (int) start;
- (int) indexOf: (unichar) _c;
- (NSString *) decodedHeader;
- (NSString *) asSafeFilename;
@end
+35 -13
View File
@@ -48,11 +48,11 @@
@interface _SOGoHTMLContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
{
NSMutableArray *images;
NSArray *ignoreContentTags;
NSArray *specialTreatmentTags;
NSArray *voidTags;
BOOL ignoreContent;
BOOL orderedList;
BOOL unorderedList;
@@ -300,7 +300,7 @@
i = [value indexOf: ';'];
j = [value indexOf: ';' fromIndex: i+1];
k = [value indexOf: ','];
// We try to get the MIME type
mimeType = nil;
@@ -314,7 +314,7 @@
// We might get a stupid value. We discard anything that doesn't have a / in it
if ([mimeType indexOf: '/'] < 0)
mimeType = @"image/jpeg";
// We check and skip the charset
if (j < i)
j = i;
@@ -335,14 +335,14 @@
[map setObject: [NSString stringWithFormat: @"inline; filename=\"%@\"", uniqueId] forKey: @"content-disposition"];
[map setObject: [NSString stringWithFormat: @"%@; name=\"%@\"", mimeType, uniqueId] forKey: @"content-type"];
[map setObject: [NSString stringWithFormat: @"<%@>", uniqueId] forKey: @"content-id"];
body = [[NGMimeFileData alloc] initWithBytes: [data bytes] length: [data length]];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: body];
[body release];
[images addObject: bodyPart];
[result appendFormat: @"<img src=\"cid:%@\" type=\"%@\"", uniqueId, mimeType];
@@ -357,7 +357,7 @@
[result appendFormat: @" %@=\"%@\"", attrName, value];
}
}
[result appendString: @"/>"];
}
}
@@ -656,19 +656,19 @@ convertChars (const char *oldString, unsigned int oldLength,
fromIndex: (int) start
{
int i, len;
len = [self length];
if (start < 0 || start >= len)
start = 0;
for (i = start; i < len; i++)
{
if ([self characterAtIndex: i] == _c) return i;
}
return -1;
}
- (int) indexOf: (unichar) _c
@@ -684,8 +684,30 @@ convertChars (const char *oldString, unsigned int oldLength,
decodedHeader];
if (!decodedHeader)
decodedHeader = self;
return decodedHeader;
}
- (NSString *) asSafeFilename
{
NSRange r;
NSMutableString *safeName;
r = [self rangeOfString: @"\\"
options: NSBackwardsSearch];
if (r.length > 0)
safeName = [NSMutableString stringWithString: [self substringFromIndex: r.location + 1]];
else
safeName = [NSMutableString stringWithString: self];
[safeName replaceString: @"/" withString: @"_"];
if ([self isEqualToString: @"."])
return @"_";
if ([self isEqualToString: @".."])
return @"__";
return safeName;
}
@end
-1
View File
@@ -97,7 +97,6 @@
/* attachments */
- (NSArray *) fetchAttachmentAttrs;
- (BOOL) isValidAttachmentName: (NSString *) _name;
- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name;
- (NSString *) pathToAttachmentWithName: (NSString *) _name;
- (NSException *) saveAttachment: (NSData *) _attach
+139 -173
View File
@@ -55,6 +55,8 @@
#import <SOGo/SOGoMailer.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <NGCards/NGVCard.h>
@@ -74,7 +76,7 @@
static NSString *contentTypeValue = @"text/plain; charset=utf-8";
static NSString *htmlContentTypeValue = @"text/html; charset=utf-8";
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
@"from", @"replyTo", @"message-id",
nil};
@@ -142,18 +144,18 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
const char *b, *bytes;
int i, len, slen;
if (!theCString)
{
return NSMakeRange(NSNotFound,0);
}
bytes = [self bytes];
len = [self length];
slen = strlen(theCString);
b = bytes;
if (len > theRange.location + theRange.length)
{
len = theRange.location + theRange.length;
@@ -163,7 +165,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!strncasecmp(theCString,b,slen))
@@ -176,7 +178,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
{
i = theRange.location;
b += i;
for (; i <= len-slen; i++, b++)
{
if (!memcmp(theCString,b,slen))
@@ -185,7 +187,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
}
}
}
return NSMakeRange(NSNotFound,0);
}
@@ -271,7 +273,7 @@ static NSString *userAgent = nil;
NSFileManager *fm;
fm = [NSFileManager defaultManager];
return ([fm createDirectoriesAtPath: [container userSpoolFolderPath]
attributes: nil]
&& [fm createDirectoriesAtPath: [self draftFolderPath]
@@ -308,7 +310,7 @@ static NSString *userAgent = nil;
messageID = [NSString generateMessageID];
[headers setObject: messageID forKey: @"message-id"];
}
priority = [newHeaders objectForKey: @"X-Priority"];
if (priority)
{
@@ -641,6 +643,13 @@ static NSString *userAgent = nil;
error = nil;
message = [self mimeMessageAsData];
if (!message)
{
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Message is too big"];
return error;
}
client = [[self imap4Connection] client];
if (![imap4 doesMailboxExistAtURL: [container imap4URL]])
@@ -649,7 +658,7 @@ static NSString *userAgent = nil;
atURL: [[self mailAccountFolder] imap4URL]];
[imap4 flushFolderHierarchyCache];
}
folder = [imap4 imap4FolderNameForURL: [container imap4URL]];
result = [client append: message toFolder: folder
withFlags: [NSArray arrayWithObjects: @"draft", nil]];
@@ -745,7 +754,7 @@ static NSString *userAgent = nil;
- if there is a 'reply-to' header, only include that (as TO)
- if we reply to all, all non-from addresses are added as CC
- the from is always the lone TO (except for reply-to)
Note: we cannot check reply-to, because Cyrus even sets a reply-to in the
envelope if none is contained in the message itself! (bug or
feature?)
@@ -776,11 +785,11 @@ static NSString *userAgent = nil;
int i;
identities = [[[self container] mailAccountFolder] identities];
for (i = 0; i < [identities count]; i++)
{
email = [[identities objectAtIndex: i] objectForKey: @"email"];
if (email)
[allRecipients addObject: email];
}
@@ -838,66 +847,29 @@ static NSString *userAgent = nil;
[self _purgeRecipients: allRecipients
fromAddresses: addrs];
[self _addEMailsOfAddresses: addrs toArray: to];
[_info setObject: to forKey: @"cc"];
[to release];
}
}
//
//
//
- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
fromResponseFetch: (NSDictionary *) fetch;
{
NSEnumerator *attachmentKeys;
NSMutableArray *bodies;
NSString *currentKey;
NSDictionary *body;
bodies = [NSMutableArray array];
attachmentKeys = [paths objectEnumerator];
while ((currentKey = [attachmentKeys nextObject]))
{
body = [fetch objectForKey: [currentKey lowercaseString]];
if (body)
[bodies addObject: [body objectForKey: @"data"]];
else
[bodies addObject: [NSData data]];
}
return bodies;
}
//
//
//
- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail
{
unsigned int count, max;
NSArray *parts, *paths, *bodies;
NSData *body;
unsigned int max, count;
NSArray *attachments;
NSDictionary *currentInfo;
NGHashMap *response;
parts = [sourceMail fetchFileAttachmentKeys];
max = [parts count];
if (max > 0)
attachments = [sourceMail fetchFileAttachments];
max = [attachments count];
for (count = 0; count < max; count++)
{
paths = [parts keysWithFormat: @"BODY[%{path}]"];
response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"];
bodies = [self _attachmentBodiesFromPaths: paths
fromResponseFetch: [response objectForKey: @"fetch"]];
for (count = 0; count < max; count++)
{
currentInfo = [parts objectAtIndex: count];
body = [[bodies objectAtIndex: count]
bodyDataFromEncoding: [currentInfo
objectForKey: @"encoding"]];
[self saveAttachment: body withMetadata: currentInfo];
}
currentInfo = [attachments objectAtIndex: count];
[self saveAttachment: [currentInfo objectForKey: @"body"]
withMetadata: currentInfo];
}
}
@@ -1002,14 +974,14 @@ static NSString *userAgent = nil;
SOGoUserDefaults *ud;
[sourceMail fetchCoreInfos];
if ([sourceMail subjectForForward])
{
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
forKey: @"subject"];
[self setHeaders: info];
}
[self setSourceURL: [sourceMail imap4URLString]];
[self setSourceFlag: @"$Forwarded"];
[self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]];
@@ -1052,7 +1024,7 @@ static NSString *userAgent = nil;
- (NSString *) sender
{
id tmp;
if ((tmp = [headers objectForKey: @"from"]) == nil)
return nil;
if ([tmp isKindOfClass:[NSArray class]])
@@ -1098,52 +1070,36 @@ static NSString *userAgent = nil;
return ma;
}
- (BOOL) isValidAttachmentName: (NSString *) filename
{
return (!([filename rangeOfString: @"/"].length
|| [filename isEqualToString: @"."]
|| [filename isEqualToString: @".."]));
}
- (NSString *) pathToAttachmentWithName: (NSString *) _name
{
if ([_name length] == 0)
return nil;
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
}
- (NSException *) invalidAttachmentNameError: (NSString *) _name
{
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason: @"Invalid attachment name!"];
}
/**
* Write attachment file to the spool directory of the draft and write a dot
* file with its mime type.
*/
- (NSException *) saveAttachment: (NSData *) _attach
withMetadata: (NSDictionary *) metadata
{
NSString *p, *pmime, *name, *mimeType;
NSRange r;
if (![_attach isNotNull]) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason: @"Missing attachment content!"];
}
if (![self _ensureDraftFolderPath]) {
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason: @"Could not create folder for draft!"];
}
name = [metadata objectForKey: @"filename"];
r = [name rangeOfString: @"\\"
options: NSBackwardsSearch];
if (r.length > 0)
name = [name substringFromIndex: r.location + 1];
if (![self isValidAttachmentName: name])
return [self invalidAttachmentNameError: name];
name = [[metadata objectForKey: @"filename"] asSafeFilename];
p = [self pathToAttachmentWithName: name];
if (![_attach writeToFile: p atomically: YES])
{
@@ -1162,7 +1118,7 @@ static NSString *userAgent = nil;
reason: @"Could not write attachment to draft!"];
}
}
return nil; /* everything OK */
}
@@ -1173,21 +1129,14 @@ static NSString *userAgent = nil;
NSException *error;
error = nil;
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName: [_name asSafeFilename]];
if ([fm fileExistsAtPath: p])
if (![fm removeFileAtPath: p handler: nil])
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Could not delete attachment from draft!"];
if ([self isValidAttachmentName:_name])
{
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName:_name];
if ([fm fileExistsAtPath: p])
if (![fm removeFileAtPath: p handler: nil])
error
= [NSException exceptionWithHTTPStatus: 500 /* Server Error */
reason: @"Could not delete attachment from draft!"];
}
else
error = [self invalidAttachmentNameError:_name];
return error;
return error;
}
//
@@ -1201,9 +1150,9 @@ static NSString *userAgent = nil;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
[map setObject: contentTypeValue forKey: @"content-type"];
/* prepare body content */
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
@@ -1224,7 +1173,7 @@ static NSString *userAgent = nil;
*/
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
@@ -1232,7 +1181,7 @@ static NSString *userAgent = nil;
if (text)
[map setObject: (isHTML ? htmlContentTypeValue : contentTypeValue)
forKey: @"content-type"];
/* prepare body content */
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: text];
@@ -1242,11 +1191,11 @@ static NSString *userAgent = nil;
- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map
{
NGMimeMessage *message;
NGMimeMessage *message;
id body;
message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
if (!isHTML)
{
[message setHeader: contentTypeValue forKey: @"content-type"];
@@ -1263,9 +1212,9 @@ static NSString *userAgent = nil;
// Add the HTML part
[body addBodyPart: [self bodyPartForText]];
}
[message setBody: body];
return message;
}
@@ -1287,9 +1236,8 @@ static NSString *userAgent = nil;
{
NSString *s, *p;
NSData *mimeData;
p = [self pathToAttachmentWithName:
[NSString stringWithFormat: @".%@.mime", _name]];
p = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.mime", _name]];
mimeData = [NSData dataWithContentsOfFile: p];
if (mimeData)
{
@@ -1308,20 +1256,18 @@ static NSString *userAgent = nil;
}
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
andContentType: (NSString *) _type
{
NSString *type;
NSString *cdtype;
NSString *cd;
SOGoDomainDefaults *dd;
type = [self contentTypeForAttachmentWithName:_name];
if ([type hasPrefix: @"text/"])
if ([_type hasPrefix: @"text/"])
{
dd = [[context activeUser] domainDefaults];
cdtype = [dd mailAttachTextDocumentsInline] ? @"inline" : @"attachment";
}
else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"])
else if ([_type hasPrefix: @"image/"] || [_type hasPrefix: @"message"])
cdtype = @"inline";
else
cdtype = @"attachment";
@@ -1348,7 +1294,7 @@ static NSString *userAgent = nil;
if (_name == nil) return nil;
/* check attachment */
fm = [NSFileManager defaultManager];
p = [self pathToAttachmentWithName: _name];
if (![fm isReadableFileAtPath: p]) {
@@ -1357,7 +1303,7 @@ static NSString *userAgent = nil;
}
attachAsString = NO;
attachAsRFC822 = NO;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
@@ -1369,22 +1315,22 @@ static NSString *userAgent = nil;
else if ([s hasPrefix: @"message/rfc822"])
attachAsRFC822 = YES;
}
if ((s = [self contentDispositionForAttachmentWithName: _name]))
if ((s = [self contentDispositionForAttachmentWithName: _name andContentType: s]))
{
NGMimeContentDispositionHeaderField *o;
o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s];
[map setObject: o forKey: @"content-disposition"];
[o release];
}
/* prepare body content */
if (attachAsString) { // TODO: is this really necessary?
NSString *s;
content = [[NSData alloc] initWithContentsOfMappedFile:p];
s = [[NSString alloc] initWithData: content
encoding: [NSString defaultCStringEncoding]];
if (s != nil) {
@@ -1399,7 +1345,7 @@ static NSString *userAgent = nil;
}
}
else {
/*
/*
Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
NGMimeFileData objects are not processed by the MIME generator!
*/
@@ -1415,34 +1361,43 @@ static NSString *userAgent = nil;
content = [content dataByEncodingBase64];
[map setObject: @"base64" forKey: @"content-transfer-encoding"];
}
[map setObject:[NSNumber numberWithInt:[content length]]
[map setObject:[NSNumber numberWithInt:[content length]]
forKey: @"content-length"];
/* Note: the -init method will create a temporary file! */
body = [[NGMimeFileData alloc] initWithBytes:[content bytes]
length:[content length]];
}
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody:body];
[body release]; body = nil;
return bodyPart;
}
//
//
// returns nil on error
//
- (NSArray *) bodyPartsForAllAttachments
{
/* returns nil on error */
NSArray *attrs;
unsigned i, count;
NGMimeBodyPart *bodyPart;
NSMutableArray *bodyParts;
NSArray *attrs;
unsigned i, count, size, limit;
attrs = [self fetchAttachmentAttrs];
count = [attrs count];
size = 0;
// We first check if we don't go over our message size limit
limit = [[SOGoSystemDefaults sharedSystemDefaults] maximumMessageSizeLimit] * 1024;
for (i = 0; i < count; i++)
size += [[[attrs objectAtIndex: i] objectForKey: @"size"] intValue];
if (limit && size > limit)
return nil;
bodyParts = [NSMutableArray arrayWithCapacity: count];
for (i = 0; i < count; i++)
@@ -1462,14 +1417,14 @@ static NSString *userAgent = nil;
NGMimeMultipartBody *textParts;
NGMutableHashMap *header;
NGMimeBodyPart *part;
header = [NGMutableHashMap hashMap];
[header addObject: MultiAlternativeType forKey: @"content-type"];
part = [NGMimeBodyPart bodyPartWithHeader: header];
textParts = [[NGMimeMultipartBody alloc] initWithPart: part];
// Get the text part from it and add it
[textParts addBodyPart: [self plainTextBodyPartForText]];
@@ -1488,25 +1443,21 @@ static NSString *userAgent = nil;
- (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map
andBodyParts: (NSArray *) _bodyParts
{
NGMimeMessage *message;
NGMimeMessage *message;
NGMimeMultipartBody *mBody;
NSEnumerator *e;
id part;
[map addObject: MultiMixedType forKey: @"content-type"];
message = [[NGMimeMessage alloc] initWithHeader: map];
[message autorelease];
mBody = [[NGMimeMultipartBody alloc] initWithPart: message];
if (!isHTML)
{
part = [self bodyPartForText];
}
part = [self bodyPartForText];
else
{
part = [self mimeMultipartAlternative];
}
part = [self mimeMultipartAlternative];
[mBody addBodyPart: part];
@@ -1535,11 +1486,11 @@ static NSString *userAgent = nil;
if ([_h count] == 0)
return;
names = [_h keyEnumerator];
while ((name = [names nextObject]) != nil) {
id value;
value = [_h objectForKey:name];
[_map addObject:value forKey:name];
}
@@ -1549,10 +1500,10 @@ static NSString *userAgent = nil;
{
if (![_value isNotNull])
return YES;
if ([_value isKindOfClass: [NSArray class]])
return [_value count] == 0 ? YES : NO;
if ([_value isKindOfClass: [NSString class]])
return [_value length] == 0 ? YES : NO;
@@ -1630,9 +1581,9 @@ static NSString *userAgent = nil;
NSString *s, *dateString;
NGMutableHashMap *map;
id emails, from, replyTo;
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
/* add recipients */
if ((emails = [headers objectForKey: @"to"]) != nil && [emails isKindOfClass: [NSArray class]])
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"to"];
@@ -1643,7 +1594,7 @@ static NSString *userAgent = nil;
/* add senders */
from = [headers objectForKey: @"from"];
if (![self isEmptyValue:from]) {
if ([from isKindOfClass:[NSArray class]])
[map setObjects: [self _quoteSpecialsInArray: from] forKey: @"from"];
@@ -1661,7 +1612,7 @@ static NSString *userAgent = nil;
if ([(s = [headers objectForKey: @"subject"]) length] > 0)
[map setObject: [s asQPSubjectString: @"utf-8"]
forKey: @"subject"];
if ([(s = [headers objectForKey: @"message-id"]) length] > 0)
[map setObject: s
forKey: @"message-id"];
@@ -1685,16 +1636,16 @@ static NSString *userAgent = nil;
forKey: @"Disposition-Notification-To"];
[self _addHeaders: _headers toHeaderMap: map];
// We remove what we have to...
if (_exclude)
{
int i;
for (i = 0; i < [_exclude count]; i++)
[map removeAllObjectsForKey: [_exclude objectAtIndex: i]];
}
return map;
}
@@ -1707,8 +1658,10 @@ static NSString *userAgent = nil;
{
NSMutableArray *bodyParts;
NGMimeMessage *message;
NSArray *allBodyParts;
NGMutableHashMap *map;
NSString *newText;
BOOL has_inline_images;
message = nil;
@@ -1730,11 +1683,15 @@ static NSString *userAgent = nil;
if (map)
{
//[self debugWithFormat: @"MIME Envelope: %@", map];
[bodyParts addObjectsFromArray: [self bodyPartsForAllAttachments]];
allBodyParts = [self bodyPartsForAllAttachments];
if (!allBodyParts)
return nil;
[bodyParts addObjectsFromArray: allBodyParts];
//[self debugWithFormat: @"attachments: %@", bodyParts];
if ([bodyParts count] == 0)
/* no attachments */
message = [self mimeMessageForContentWithHeaderMap: map];
@@ -1748,13 +1705,13 @@ static NSString *userAgent = nil;
[map addObject: MultiRelatedType forKey: @"content-type"];
}
message = [self mimeMultiPartMessageWithHeaderMap: map
message = [self mimeMultiPartMessageWithHeaderMap: map
andBodyParts: bodyParts];
//[self debugWithFormat: @"message: %@", message];
}
}
return message;
}
@@ -1772,10 +1729,19 @@ static NSString *userAgent = nil;
- (NSData *) mimeMessageAsData
{
NGMimeMessageGenerator *generator;
NGMimeMessage *mimeMessage;
NSData *message;
generator = [NGMimeMessageGenerator new];
message = [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO]];
mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO];
if (!mimeMessage)
{
[generator release];
return nil;
}
message = [generator generateMimeFromPart: mimeMessage];
[generator release];
return message;
@@ -1827,9 +1793,9 @@ static NSString *userAgent = nil;
- (NSException *) sendMail
{
SOGoUserDefaults *ud;
ud = [[context activeUser] userDefaults];
if ([ud mailAddOutgoingAddresses])
{
NSString *recipient, *emailAddress, *addressBook, *uid;
@@ -1840,9 +1806,9 @@ static NSString *userAgent = nil;
NGMailAddressParser *parser;
SOGoFolder <SOGoContactFolder> *folder;
NGVCard *card;
int i;
// Get all the addressbooks
contactFolders = [[[context activeUser] homeFolderInContext: context]
lookupName: @"Contacts"
@@ -1857,7 +1823,7 @@ static NSString *userAgent = nil;
parser = [NGMailAddressParser mailAddressParserWithString: recipient];
parsedRecipient = [parser parse];
emailAddress = [parsedRecipient address];
matchingContacts = [contactFolders allContactsFromFilter: emailAddress
excludeGroups: YES
excludeLists: YES];
@@ -1869,13 +1835,13 @@ static NSString *userAgent = nil;
addressBook = [ud selectedAddressBook];
folder = [contactFolders lookupName: addressBook inContext: context acquire: NO];
uid = [folder globallyUniqueObjectId];
if (folder && uid)
{
card = [NGVCard cardWithUid: uid];
[card addEmail: emailAddress types: nil];
[card setFn: [parsedRecipient displayName]];
newContact = [SOGoContactGCSEntry objectWithName: uid
inContainer: folder];
[newContact setIsNew: YES];
@@ -1921,7 +1887,7 @@ static NSString *userAgent = nil;
r1 = [cleaned_message rangeOfCString: "\r\nbcc: "
options: 0
range: NSMakeRange(0,limit)];
if (r1.location != NSNotFound)
{
// We search for the first \r\n AFTER the Bcc: header and
+1
View File
@@ -67,6 +67,7 @@ typedef enum {
NSString *otherUsersFolderName;
NSString *sharedFoldersName;
NSMutableDictionary *subscribedFolders;
BOOL sieveFolderUTF8Encoding;
}
- (SOGoIMAPAclStyle) imapAclStyle;
+11 -1
View File
@@ -37,6 +37,7 @@
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoAuthenticator.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/SOGoSieveManager.h>
@@ -62,6 +63,7 @@ static NSString *inboxFolderName = @"INBOX";
{
if ((self = [super init]))
{
NSString *sieveFolderEncoding = [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
inboxFolder = nil;
draftsFolder = nil;
sentFolder = nil;
@@ -72,6 +74,7 @@ static NSString *inboxFolderName = @"INBOX";
otherUsersFolderName = nil;
sharedFoldersName = nil;
subscribedFolders = nil;
sieveFolderUTF8Encoding = [sieveFolderEncoding isEqualToString: @"UTF-8"];
}
return self;
@@ -461,7 +464,7 @@ static NSString *inboxFolderName = @"INBOX";
NSArray *pathComponents;
NSMutableArray *folders, *flags;
NSMutableDictionary *currentFolder, *parentFolder, *folder;
NSString *currentFolderName, *currentPath, *fullName, *folderType;
NSString *currentFolderName, *currentPath, *sievePath, *fullName, *folderType;
SOGoUserManager *userManager;
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed;
@@ -550,6 +553,13 @@ static NSString *inboxFolderName = @"INBOX";
flags, @"flags",
[NSNumber numberWithBool: isSubscribed], @"subscribed",
nil];
if (sieveFolderUTF8Encoding)
sievePath = [folderPath stringByDecodingImap4FolderName];
else
sievePath = folderPath;
[folder setObject: [sievePath substringFromIndex: 1] forKey: @"sievePath"];
// Either add this new folder to its parent or the list of root folders
[folders addObject: folder];
}
+16 -3
View File
@@ -132,10 +132,23 @@
- (NGImap4Connection *) _createIMAP4Connection
{
NGImap4ConnectionManager *manager;
NSString *password;
NGImap4Connection *newConnection;
NSString *password;
NSHost *host;
[self imap4URL];
// We first check if we're trying to establish an IMAP connection to localhost
// for an account number greater than 0 (default account). We prevent that
// for security reasons if admins use an IMAP trust.
host = [NSHost hostWithName: [[self imap4URL] host]];
if (![[[self mailAccountFolder] nameInContainer] isEqualToString: @"0"] &&
[[host address] isEqualToString: @"127.0.0.1"])
{
[self errorWithFormat: @"Trying to use localhost for additional IMAP account - aborting."];
return nil;
}
manager = [self mailManager];
password = [self imap4PasswordRenewed: NO];
if (password)
@@ -212,8 +225,8 @@
if (!imap4)
{
imap4 = [self _createIMAP4Connection];
[sogoCache registerIMAP4Connection: imap4
forKey: cacheKey];
[sogoCache registerIMAP4Connection: imap4
forKey: cacheKey];
}
[imap4 retain];
}
+5
View File
@@ -1,4 +1,5 @@
/*
Copyright (C) 2009-2016 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
@@ -48,6 +49,8 @@
@class NGImap4Envelope;
@class NGImap4EnvelopeAddress;
@class WOResponse;
NSArray *SOGoMailCoreInfoKeys;
@interface SOGoMailObject : SOGoMailBaseObject
@@ -107,6 +110,8 @@ NSArray *SOGoMailCoreInfoKeys;
- (BOOL) hasAttachment;
- (NSDictionary *) fetchFileAttachmentIds;
- (NSArray *) fetchFileAttachmentKeys;
- (NSArray *) fetchFileAttachments;
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName;
/* flags */
+248 -89
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2014 Inverse inc.
Copyright (C) 2007-2016 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOGo.
@@ -20,6 +20,7 @@
02111-1307, USA.
*/
#import <Foundation/NSTask.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>
@@ -27,6 +28,7 @@
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSFileManager+Extensions.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h>
@@ -38,7 +40,9 @@
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/NSCalendarDate+SOGo.h>
@@ -122,10 +126,10 @@ static BOOL debugSoParts = NO;
- (NSString *) keyExtensionForPart: (id) _partInfo
{
NSString *mt, *st;
if (_partInfo == nil)
return nil;
mt = [_partInfo valueForKey: @"type"];
st = [[_partInfo valueForKey: @"subtype"] lowercaseString];
if ([mt isEqualToString: @"text"]) {
@@ -140,7 +144,7 @@ static BOOL debugSoParts = NO;
if ([st isEqualToString: @"pgp-signature"])
return @".asc";
}
return nil;
}
@@ -149,18 +153,18 @@ static BOOL debugSoParts = NO;
NSMutableArray *ma;
NSArray *parts;
unsigned i, count;
parts = [[self bodyStructure] valueForKey: @"parts"];
if (![parts isNotNull])
if (![parts isNotNull])
return nil;
if ((count = [parts count]) == 0)
return nil;
for (i = 0, ma = nil; i < count; i++) {
NSString *key, *ext;
id part;
BOOL hasParts;
part = [parts objectAtIndex:i];
hasParts = [part valueForKey: @"parts"] != nil ? YES:NO;
if ((hasParts && !_withParts) || (_withParts && !hasParts))
@@ -168,7 +172,7 @@ static BOOL debugSoParts = NO;
if (ma == nil)
ma = [NSMutableArray arrayWithCapacity:count - i];
ext = [self keyExtensionForPart:part];
key = [[NSString alloc] initWithFormat: @"%d%@", i + 1, ((id)ext?(id)ext: (id)@"")];
[ma addObject:key];
@@ -204,15 +208,15 @@ static BOOL debugSoParts = NO;
{
static NSArray *existsKey = nil;
id msgs;
if (coreInfos != nil) /* if we have coreinfos, we can use them */
return [coreInfos isNotNull];
/* otherwise fetch something really simple */
if (existsKey == nil) /* we use size, other suggestions? */
existsKey = [[NSArray alloc] initWithObjects: @"RFC822.SIZE", nil];
msgs = [self fetchParts:existsKey]; // returns dict
msgs = [msgs valueForKey: @"fetch"];
return [msgs count] > 0 ? YES : NO;
@@ -229,7 +233,7 @@ static BOOL debugSoParts = NO;
if (heavyDebug)
[self logWithFormat: @"M: %@", msgs];
msgs = [msgs valueForKey: @"fetch"];
// We MUST honor untagged IMAP responses here otherwise we could
// return really borken and nasty results.
if ([msgs count] > 0)
@@ -237,7 +241,7 @@ static BOOL debugSoParts = NO;
for (i = 0; i < [msgs count]; i++)
{
coreInfos = [msgs objectAtIndex: i];
if ([[coreInfos objectForKey: @"uid"] intValue] == [[self nameInContainer] intValue])
break;
@@ -337,13 +341,13 @@ static BOOL debugSoParts = NO;
{
NGMimeMessageParser *parser;
NSData *data;
if (headerPart != nil)
return [headerPart isNotNull] ? headerPart : nil;
if ([(data = [self mailHeaderData]) length] == 0)
return nil;
// TODO: do we need to set some delegate method which stops parsing the body?
parser = [[NGMimeMessageParser alloc] init];
headerPart = [[parser parsePartFromData:data] retain];
@@ -372,21 +376,21 @@ static BOOL debugSoParts = NO;
if (![_path isNotNull])
return nil;
if ((info = [self bodyStructure]) == nil) {
[self errorWithFormat: @"got no body part structure!"];
return nil;
}
/* ensure array argument */
if ([_path isKindOfClass:[NSString class]]) {
if ([_path length] == 0 || [_path isEqualToString: @"text"])
return info;
_path = [_path componentsSeparatedByString: @"."];
}
// deal with mails of type text/calendar
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"text"] &&
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
@@ -399,12 +403,12 @@ static BOOL debugSoParts = NO;
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"])
return info;
/*
For each path component, eg 1,1,3
/*
For each path component, eg 1,1,3
Remember that we need special processing for message/rfc822 which maps the
namespace of multiparts directly into the main namespace.
TODO(hh): no I don't remember, please explain in more detail!
*/
pe = [_path objectEnumerator];
@@ -412,7 +416,7 @@ static BOOL debugSoParts = NO;
unsigned idx;
NSArray *parts;
NSString *mt;
[self debugWithFormat: @"check PATH: %@", p];
idx = [p intValue] - 1;
@@ -421,7 +425,7 @@ static BOOL debugSoParts = NO;
if ([mt isEqualToString: @"message"]) {
/* we have special behaviour for message types */
id body;
if ((body = [info valueForKey: @"body"]) != nil) {
mt = [body valueForKey: @"type"];
if ([mt isEqualToString: @"multipart"])
@@ -430,10 +434,10 @@ static BOOL debugSoParts = NO;
parts = [NSArray arrayWithObject:body];
}
}
if (idx >= [parts count]) {
[self errorWithFormat:
@"body part index out of bounds(idx=%d vs count=%d): %@",
@"body part index out of bounds(idx=%d vs count=%d): %@",
(idx + 1), [parts count], info];
return nil;
}
@@ -448,40 +452,40 @@ static BOOL debugSoParts = NO;
{
NSData *content;
id result, fullResult;
// We avoid using RFC822 here as the part name as it'll flag the message as Seen
fullResult = [self fetchParts: [NSArray arrayWithObject: @"BODY.PEEK[]"]];
if (fullResult == nil)
return nil;
if ([fullResult isKindOfClass: [NSException class]])
return fullResult;
/* extract fetch result */
result = [fullResult valueForKey: @"fetch"];
if (![result isKindOfClass:[NSArray class]]) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
fullResult];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason: @"unexpected IMAP4 result"];
}
if ([result count] == 0)
return nil;
result = [result objectAtIndex:0];
/* extract message */
if ((content = [[result valueForKey: @"body[]"] valueForKey: @"data"]) == nil) {
[self logWithFormat:
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
result];
return [NSException exceptionWithHTTPStatus:500 /* server error */
reason: @"unexpected IMAP4 result"];
}
return [[content copy] autorelease];
}
@@ -507,7 +511,7 @@ static BOOL debugSoParts = NO;
[s autorelease];
else
[self logWithFormat:
@"ERROR: could not convert data of length %d to string",
@"ERROR: could not convert data of length %d to string",
[content length]];
}
else
@@ -529,11 +533,11 @@ static BOOL debugSoParts = NO;
withPeek: (BOOL) withPeek
parentMultipart: (NSString *) parentMPart
{
/*
/*
This is used to collect the set of IMAP4 fetch-keys required to fetch
the basic parts of the body structure. That is, to fetch all parts which
are displayed 'inline' in a single IMAP4 fetch.
The method calls itself recursively to walk the body structure.
*/
NSArray *parts;
@@ -555,7 +559,7 @@ static BOOL debugSoParts = NO;
multipart = mimeType;
else
multipart = parentMPart;
if ([types containsObject: mimeType])
{
if ([p length] > 0)
@@ -581,9 +585,9 @@ static BOOL debugSoParts = NO;
sp = (([p length] > 0)
? (id)[p stringByAppendingFormat: @".%d", i + 1]
: (id)[NSString stringWithFormat: @"%d", i + 1]);
childInfo = [parts objectAtIndex: i];
[self addRequiredKeysOfStructure: childInfo
path: sp
toArray: keys
@@ -591,7 +595,7 @@ static BOOL debugSoParts = NO;
withPeek: withPeek
parentMultipart: multipart];
}
/* check body */
body = [info objectForKey: @"body"];
if (body)
@@ -651,7 +655,7 @@ static BOOL debugSoParts = NO;
[self addRequiredKeysOfStructure: [self bodyStructure]
path: @""
toArray: ma
toArray: ma
acceptedTypes: types
withPeek: YES];
@@ -665,7 +669,7 @@ static BOOL debugSoParts = NO;
unsigned i, count;
NSArray *results;
id result;
[self debugWithFormat: @"fetch keys: %@", _fetchKeys];
result = [self fetchParts: [_fetchKeys objectsForKey: @"key"
@@ -681,7 +685,7 @@ static BOOL debugSoParts = NO;
for (i = 0; i < count; i++) {
NSString *key;
NSData *data;
key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"];
// We'll ask for the body.peek[] but SOPE returns us body[] responses
@@ -689,19 +693,19 @@ static BOOL debugSoParts = NO;
if ([key hasPrefix: @"body.peek["])
key = [NSString stringWithFormat: @"body[%@", [key substringFromIndex: 10]];
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
objectForKey: @"data"];
if (![data isNotNull]) {
[self errorWithFormat: @"got no data for key: %@", key];
continue;
}
if ([key isEqualToString: @"body[text]"])
key = @""; // see key collector for explanation (TODO: where?)
else if ([key hasPrefix: @"body["]) {
NSRange r;
key = [key substringFromIndex:5];
r = [key rangeOfString: @"]"];
if (r.length > 0)
@@ -836,6 +840,7 @@ static BOOL debugSoParts = NO;
path, @"path",
[part objectForKey: @"encoding"], @"encoding",
[part objectForKey:@ "size"], @"size",
[part objectForKey: @"bodyId"], @"bodyId",
[NSString stringWithFormat: @"%@/%@", prefix, filenameURL], @"url",
[NSString stringWithFormat: @"%@/asAttachment/%@", prefix, filenameURL], @"urlAsAttachment",
nil];
@@ -913,13 +918,167 @@ static BOOL debugSoParts = NO;
return keys;
}
/**
* Returns an array of dictionaries with the following keys:
* - encoding
* - filename
* - mimetype
* - path
* - size
* - url
* - urlAsAttachment
* - body (NSData)
*/
- (NSArray *) fetchFileAttachments
{
unsigned int count, max;
NGHashMap *response;
NSArray *parts, *paths; //, *bodies;
NSData *body;
NSDictionary *fetch, *currentInfo, *currentBody;
NSMutableArray *attachments;
NSMutableDictionary *currentAttachment;
NSString *currentPath;
parts = [self fetchFileAttachmentKeys];
max = [parts count];
attachments = [NSMutableArray arrayWithCapacity: max];
if (max > 0)
{
paths = [parts keysWithFormat: @"BODY[%{path}]"];
response = [[self fetchParts: paths] objectForKey: @"RawResponse"];
fetch = [response objectForKey: @"fetch"];
for (count = 0; count < max; count++)
{
currentInfo = [parts objectAtIndex: count];
currentPath = [[paths objectAtIndex: count] lowercaseString];
currentBody = [fetch objectForKey: currentPath];
if (currentBody)
{
body = [currentBody objectForKey: @"data"];
body = [body bodyDataFromEncoding: [currentInfo objectForKey: @"encoding"]];
}
else
body = [NSData data];
currentAttachment = [NSMutableDictionary dictionaryWithDictionary: currentInfo];
[currentAttachment setObject: body forKey: @"body"];
[attachments addObject: currentAttachment];
}
}
return attachments;
}
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName
{
NSArray *attachments;
NSData *body, *zipContent;
NSDictionary *currentAttachment;
NSException *error;
NSFileManager *fm;
NSMutableArray *zipTaskArguments;
NSString *spoolPath, *name, *fileName, *baseName, *extension, *zipPath, *qpFileName;
NSTask *zipTask;
SOGoMailFolder *folder;
WOResponse *response;
unsigned int max, count;
if (!archiveName)
archiveName = @"attachments.zip";
folder = [self container];
spoolPath = [folder userSpoolFolderPath];
if (![folder ensureSpoolFolderPath])
{
[self errorWithFormat: @"spool directory '%@' doesn't exist", spoolPath];
error = [NSException exceptionWithHTTPStatus: 500
reason: @"spool directory does not exist"];
return (WOResponse *)error;
}
// Prepare execution of zip
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath: zipPath])
{
error = [NSException exceptionWithHTTPStatus: 500
reason: @"zip not available"];
return (WOResponse *)error;
}
zipTask = [[NSTask alloc] init];
[zipTask setCurrentDirectoryPath: spoolPath];
[zipTask setLaunchPath: zipPath];
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
[zipTaskArguments addObject: @"attachments.zip"];
// Fetch attachments and write them on disk
attachments = [self fetchFileAttachments];
max = [attachments count];
for (count = 0; count < max; count++)
{
currentAttachment = [attachments objectAtIndex: count];
body = [currentAttachment objectForKey: @"body"];
name = [[currentAttachment objectForKey: @"filename"] asSafeFilename];
fileName = [NSString stringWithFormat:@"%@/%@", spoolPath, name];
[body writeToFile: fileName atomically: YES];
[zipTaskArguments addObject: name];
}
// Zip files
[zipTask setArguments: zipTaskArguments];
[zipTask launch];
[zipTask waitUntilExit];
[zipTask release];
zipContent = [[NSData alloc] initWithContentsOfFile:
[NSString stringWithFormat: @"%@/attachments.zip", spoolPath]];
// Delete attachments from disk
max = [zipTaskArguments count];
for (count = 0; count < max; count++)
{
fileName = [zipTaskArguments objectAtIndex: count];
[fm removeFileAtPath:
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
}
// Prepare response
response = [context response];
baseName = [archiveName stringByDeletingPathExtension];
extension = [archiveName pathExtension];
if ([extension length] > 0)
extension = [@"." stringByAppendingString: extension];
else
extension = @"";
qpFileName = [NSString stringWithFormat: @"%@%@",
[baseName asQPSubjectString: @"utf-8"], extension];
[response setHeader: [NSString stringWithFormat: @"application/zip;"
@" name=\"%@\"", qpFileName]
forKey: @"content-type"];
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
qpFileName]
forKey: @"Content-Disposition"];
[response setContent: zipContent];
[zipContent release];
return response;
}
/* convert parts to strings */
- (NSString *) stringForData: (NSData *) _data
partInfo: (NSDictionary *) _info
{
NSString *charset, *s;
NSData *mailData;
if ([_data isNotNull])
{
mailData
@@ -934,13 +1093,13 @@ static BOOL debugSoParts = NO;
{
s = [NSString stringWithData: mailData usingEncodingNamed: charset];
}
// If it has failed, we try at least using UTF-8. Normally, this can NOT fail.
// Unfortunately, it seems to fail under GNUstep so we try latin1 if that's
// the case
if (!s)
s = [[[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding] autorelease];
if (!s)
s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
}
@@ -975,17 +1134,17 @@ static BOOL debugSoParts = NO;
/*
The fetched parts are NSData objects, this method converts them into
NSString objects based on the information inside the bodystructure.
The fetch-keys are body fetch-keys like: body[text] or body[1.2.3].
The keys in the result dictionary are "" for 'text' and 1.2.3 for parts.
*/
NSDictionary *datas;
if ((datas = [self fetchPlainTextParts:_fetchKeys]) == nil)
return nil;
if ([datas isKindOfClass:[NSException class]])
return datas;
return [self stringifyTextParts:datas];
}
@@ -1065,13 +1224,13 @@ static BOOL debugSoParts = NO;
acquire: (BOOL) _flag
{
id obj;
/* first check attributes directly bound to the application */
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
return obj;
/* lookup body part */
if ([self isBodyPartKey:_key]) {
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
if (debugSoParts)
@@ -1087,7 +1246,7 @@ static BOOL debugSoParts = NO;
[obj setAsAttachment];
return obj;
}
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason: @"Did not find mail method or part-reference!"];
@@ -1131,7 +1290,7 @@ static BOOL debugSoParts = NO;
newName: (NSString *) _name
inContext: (id)_ctx
{
/*
/*
Note: this is special because we create SOGoMailObject's even if they do
not exist (for performance reasons).
@@ -1181,7 +1340,7 @@ static BOOL debugSoParts = NO;
NSException *error;
WOResponse *r;
NSData *content;
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
/* check whether the mail still exists */
if (![self doesMailExist]) {
@@ -1190,7 +1349,7 @@ static BOOL debugSoParts = NO;
}
return error; /* return 304 or 416 */
}
content = [self content];
if ([content isKindOfClass:[NSException class]])
return content;
@@ -1198,7 +1357,7 @@ static BOOL debugSoParts = NO;
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason: @"did not find IMAP4 message"];
}
r = [(WOContext *)_ctx response];
[r setHeader: @"message/rfc822" forKey: @"content-type"];
[r setContent:content];
@@ -1257,19 +1416,19 @@ static BOOL debugSoParts = NO;
inContext: _ctx])
{
/* b) mark deleted */
error = [[self imap4Connection] markURLDeleted: [self imap4URL]];
if (error != nil) return error;
[self flushMailCaches];
}
return nil;
}
- (NSException *) delete
{
/*
/*
Note: delete is different to DELETEAction: for mails! The 'delete' runs
either flags a message as deleted or moves it to the Trash while
the DELETEAction: really deletes a message (by flagging it as
@@ -1279,7 +1438,7 @@ static BOOL debugSoParts = NO;
NSException *error;
// TODO: check for safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
return error;
}
@@ -1287,15 +1446,15 @@ static BOOL debugSoParts = NO;
- (id) DELETEAction: (id) _ctx
{
NSException *error;
// TODO: ensure safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
if (error != nil) return error;
error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]];
if (error != nil) return error; // TODO: unflag as deleted?
return [NSNumber numberWithBool:YES]; /* delete was successful */
}
@@ -1304,20 +1463,20 @@ static BOOL debugSoParts = NO;
- (BOOL) isMailingListMail
{
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
return [[h objectForKey: @"list-id"] isNotEmpty];
}
- (BOOL) isVirusScanned
{
NSDictionary *h;
if ((h = [self mailHeaders]) == nil)
return NO;
if (![[h objectForKey: @"x-virus-status"] isNotEmpty]) return NO;
if (![[h objectForKey: @"x-virus-scanned"] isNotEmpty]) return NO;
return YES;
@@ -1329,10 +1488,10 @@ static BOOL debugSoParts = NO;
/* Note: not very tolerant on embedded commands and <> */
// TODO: does not really belong here, should be a header-field-parser
NSRange r;
if (![_value isNotEmpty])
return nil;
if ([_value isKindOfClass:[NSArray class]]) {
NSEnumerator *e;
id value;
@@ -1344,10 +1503,10 @@ static BOOL debugSoParts = NO;
}
return nil;
}
if (![_value isKindOfClass:[NSString class]])
return nil;
/* check for commas in string values */
r = [_value rangeOfString: @","];
if (r.length > 0) {
@@ -1358,7 +1517,7 @@ static BOOL debugSoParts = NO;
/* value qualifies */
if (![(NSString *)_value hasPrefix:_prefix])
return nil;
/* unquote */
if ([_value characterAtIndex:0] == '<') {
r = [_value rangeOfString: @">"];
@@ -1456,7 +1615,7 @@ static BOOL debugSoParts = NO;
if (property)
{
parts = [NSArray arrayWithObject: property];
msgs = [self fetchParts: parts];
msgs = [msgs valueForKey: @"fetch"];
if ([msgs count]) {
@@ -1512,7 +1671,7 @@ static BOOL debugSoParts = NO;
// date already exists, but this one is the correct format
- (NSString *) davDate
{
return [[self date] rfc822DateString];
return [[self date] rfc822DateString];
}
- (BOOL) hasAttachment
@@ -1597,12 +1756,12 @@ static BOOL debugSoParts = NO;
if ([fetch count])
{
data = [fetch objectForKey: @"header"];
value = [[NSString alloc] initWithData: data
value = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
range = [value rangeOfString: @"received:"
options: NSCaseInsensitiveSearch
range: NSMakeRange (10, [value length] - 11)];
if (range.length
if (range.length
&& range.location < [value length]
&& range.length < [value length])
{
+7
View File
@@ -1305,12 +1305,19 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
{
NGLdapConnection *ldapConnection;
NGLdapEntry *entry;
EOQualifier *qualifier;
NSString *login;
login = nil;
qualifier = nil;
ldapConnection = [self _ldapConnection];
if (_filter)
qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
entry = [ldapConnection entryAtDN: theDN
qualifier: qualifier
attributes: [NSArray arrayWithObject: UIDField]];
if (entry)
login = [[entry attributeWithName: UIDField] stringValueAtIndex: 0];
+11 -2
View File
@@ -721,7 +721,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
cryptParts = [cryptString componentsSeparatedByString: @"$"];
// correct number of elements (first one is an empty string)
if ([cryptParts count] != 4)
if ([cryptParts count] < 4)
{
return [NSData data];
}
@@ -731,7 +731,16 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame)
{
// third is the salt; convert it to NSData
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
if ([cryptParts count] == 4)
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
else
{
NSString *saltWithRounds;
saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]];
return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding];
}
}
// nothing good
return [NSData data];
+5 -3
View File
@@ -302,7 +302,8 @@ static int cssEscapingCount;
{
c = buf[i];
if (c == 0x9 ||
if (c == 0x0 ||
c == 0x9 ||
c == 0xA ||
c == 0xD ||
(c >= 0x20 && c <= 0xD7FF) ||
@@ -683,8 +684,9 @@ static int cssEscapingCount;
- (NSString *) asSafeSQLString
{
return [[self stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'"];
return [[[self stringByReplacingString: @"\\" withString: @"\\\\"]
stringByReplacingString: @"'" withString: @"\\'"]
stringByReplacingString: @"\%" withString: @"\\%"];
}
- (NSUInteger) countOccurrencesOfString: (NSString *) substring
+2
View File
@@ -505,6 +505,8 @@ static memcached_st *handle = NULL;
{
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"];
}
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"LastRequestDate"];
[d setObject: count forKey: @"FailedCount"];
[self _cacheValues: [d jsonRepresentation]
+3 -7
View File
@@ -475,9 +475,8 @@ static NSArray *childRecordFields = nil;
davURL = [self realDavURL];
newPath = [NSMutableArray arrayWithArray: [[davURL path] componentsSeparatedByString: @"/"]];
[newPath insertObject: @"public" atIndex: 3];
davURL = [[NSURL alloc] initWithScheme: [davURL scheme]
host: [davURL host]
path: [newPath componentsJoinedByString: @"/"]];
davURL = [NSURL URLWithString: [newPath componentsJoinedByString: @"/"]
relativeToURL: davURL];
return davURL;
}
@@ -500,10 +499,7 @@ static NSArray *childRecordFields = nil;
[container nameInContainer],
[self realNameInContainer]];
currentDavURL = [self davURL];
realDavURL = [[NSURL alloc] initWithScheme: [currentDavURL scheme]
host: [currentDavURL host]
path: path];
[realDavURL autorelease];
realDavURL = [NSURL URLWithString: path relativeToURL: currentDavURL];
}
else
realDavURL = [self davURL];
+1 -1
View File
@@ -293,7 +293,7 @@
if (user)
{
[logins addObject: login];
[logins addObject: [user loginInDomain]];
[_members addObject: user];
}
[pool release];
+61 -21
View File
@@ -365,7 +365,7 @@ static NSString *sieveScriptName = @"sogo";
}
else
scriptError = @"Rule lacks a 'value' parameter";
return (scriptError == nil);
}
@@ -783,7 +783,7 @@ static NSString *sieveScriptName = @"sogo";
SOGoDomainDefaults *dd;
NGSieveClient *client;
NSString *filterScript, *v;
BOOL b;
BOOL b, dateCapability;
unsigned int now;
dd = [user domainDefaults];
@@ -797,7 +797,7 @@ static NSString *sieveScriptName = @"sogo";
if (!client)
return NO;
// We adjust the "methodRequirements" based on the server's
// We adjust the "methodRequirements" based on the server's
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
// potentially others) expose "imap4flags" as specified in RFC5332
if ([client hasCapability: @"imap4flags"])
@@ -806,7 +806,9 @@ static NSString *sieveScriptName = @"sogo";
[methodRequirements setObject: @"imap4flags" forKey: @"removeflag"];
[methodRequirements setObject: @"imap4flags" forKey: @"flag"];
}
dateCapability = [client hasCapability: @"date"] && [client hasCapability: @"relational"];
//
// Now let's generate the script
//
@@ -835,16 +837,22 @@ static NSString *sieveScriptName = @"sogo";
now = [[NSCalendarDate calendarDate] timeIntervalSince1970];
if (values && [[values objectForKey: @"enabled"] boolValue] &&
(![values objectForKey: @"startDateEnabled"] || [[values objectForKey: @"startDate"] intValue] < now))
(![values objectForKey: @"startDateEnabled"] ||
dateCapability || [[values objectForKey: @"startDate"] intValue] < now) &&
(![values objectForKey: @"endDateEnabled"] ||
dateCapability || [[values objectForKey: @"endDate"] intValue] > now))
{
NSCalendarDate *startDate, *endDate;
NSMutableArray *allConditions;
NSMutableString *vacation_script;
NSArray *addresses;
NSString *text, *templateFilePath, *customSubject;
SOGoTextTemplateFile *templateFile;
BOOL ignore, alwaysSend, useCustomSubject;
int days, i;
allConditions = [NSMutableArray array];
days = [[values objectForKey: @"daysBetweenResponse"] intValue];
addresses = [values objectForKey: @"autoReplyEmailAddresses"];
alwaysSend = [[values objectForKey: @"alwaysSend"] boolValue];
@@ -883,12 +891,43 @@ static NSString *sieveScriptName = @"sogo";
days = 7;
vacation_script = [NSMutableString string];
[req addObjectUniquely: @"vacation"];
// Skip mailing lists
if (ignore)
[vacation_script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) { "];
{
[allConditions addObject: @"not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"]"];
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"]"];
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\""];
}
// Start date of auto-reply
if ([[values objectForKey: @"startDateEnabled"] boolValue] && dateCapability)
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
startDate = [NSCalendarDate dateWithTimeIntervalSince1970:
[[values objectForKey: @"startDate"] intValue]];
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"ge\" \"date\" \"%@\"",
[startDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
}
// End date of auto-reply
if ([[values objectForKey: @"endDateEnabled"] boolValue] && dateCapability)
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
endDate = [NSCalendarDate dateWithTimeIntervalSince1970:
[[values objectForKey: @"endDate"] intValue]];
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"le\" \"date\" \"%@\"",
[endDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
}
// Apply conditions
if ([allConditions count])
[vacation_script appendFormat: @"if allof ( %@ ) { ",
[allConditions componentsJoinedByString: @", "]];
// Custom subject
if (useCustomSubject)
@@ -910,20 +949,21 @@ static NSString *sieveScriptName = @"sogo";
for (i = 0; i < [addresses count]; i++)
{
[vacation_script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]];
if (i == [addresses count]-1)
[vacation_script appendString: @"] "];
else
[vacation_script appendString: @", "];
}
[vacation_script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text];
if (ignore)
// Closing bracket of conditions
if ([allConditions count])
[vacation_script appendString: @"}\r\n"];
//
// See http://sogo.nu/bugs/view.php?id=2332 for details
// See https://sogo.nu/bugs/view.php?id=2332 for details
//
if (alwaysSend)
[script insertString: vacation_script atIndex: 0];
@@ -941,7 +981,7 @@ static NSString *sieveScriptName = @"sogo";
int i;
b = YES;
addresses = [values objectForKey: @"forwardAddress"];
if ([addresses isKindOfClass: [NSString class]])
addresses = [NSArray arrayWithObject: addresses];
@@ -952,11 +992,11 @@ static NSString *sieveScriptName = @"sogo";
if (v && [v length] > 0)
[script appendFormat: @"redirect \"%@\";\r\n", v];
}
if ([[values objectForKey: @"keepCopy"] boolValue])
[script appendString: @"keep;\r\n"];
}
if ([req count])
{
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
@@ -970,7 +1010,7 @@ static NSString *sieveScriptName = @"sogo";
result = [client setActiveScript: @""];
// We delete the existing Sieve script
result = [client deleteScript: sieveScriptName];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"WARNING: Could not delete Sieve script - continuing...: %@", result];
}
@@ -980,13 +1020,13 @@ static NSString *sieveScriptName = @"sogo";
if (b && [script length])
{
result = [client putScript: sieveScriptName script: script];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"Could not upload Sieve script: %@", result];
[client closeConnection];
[client closeConnection];
return NO;
}
result = [client setActiveScript: sieveScriptName];
if (![[result valueForKey:@"result"] boolValue]) {
[self logWithFormat: @"Could not enable Sieve script: %@", result];
+2
View File
@@ -94,6 +94,8 @@
- (int) maximumFailedLoginInterval;
- (int) failedLoginBlockInterval;
- (int) maximumMessageSizeLimit;
- (int) maximumMessageSubmissionCount;
- (int) maximumRecipientCount;
- (int) maximumSubmissionInterval;
+8
View File
@@ -578,6 +578,14 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return v;
}
//
//
//
- (int) maximumMessageSizeLimit
{
return [self integerForKey: @"SOGoMaximumMessageSizeLimit"];
}
//
//
//
+12 -7
View File
@@ -531,9 +531,9 @@ static Class NSNullK;
grace: (int *) _grace
useCache: (BOOL) useCache
{
NSString *dictPassword, *username, *jsonUser;
NSMutableDictionary *currentUser;
NSDictionary *failedCount;
NSString *dictPassword, *username, *jsonUser;
SOGoSystemDefaults *sd;
BOOL checkOK;
@@ -573,23 +573,25 @@ static Class NSNullK;
failedCount = [[SOGoCache sharedCache] failedCountForLogin: username];
if (failedCount)
{
unsigned int current_time, start_time, delta, block_time;
unsigned int current_time, last_request_time, start_time, delta_start, delta_last_request, block_time;
current_time = [[NSCalendarDate date] timeIntervalSince1970];
start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue];
delta = current_time - start_time;
last_request_time = [[failedCount objectForKey: @"LastRequestDate"] unsignedIntValue];
delta_start = current_time - start_time;
delta_last_request = current_time - last_request_time;
block_time = [sd failedLoginBlockInterval];
if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] &&
delta >= [sd maximumFailedLoginInterval] &&
delta <= block_time )
delta_last_request >= [sd maximumFailedLoginInterval] &&
delta_start <= block_time )
{
*_perr = PolicyAccountLocked;
return NO;
}
if (delta > block_time)
if (delta_start > block_time)
{
[[SOGoCache sharedCache] setFailedCount: 0
forLogin: username];
@@ -832,8 +834,11 @@ static Class NSNullK;
{
currentSource = [_sources objectForKey: sourceID];
// Use the provided domain during the lookup. If none is defined, use the source's one
// so if there's a match based on the source's domain, the user ID will be associated
// to the right source.
userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID
inDomain: theDomain];
inDomain: (theDomain ? theDomain : [currentSource domain])];
if (userEntry)
{
[theCurrentUser setObject: sourceID forKey: @"SOGoSource"];
+2 -2
View File
@@ -107,10 +107,10 @@ class WebDAVClient:
self.conn.request(query.method, query.url,
body, self.prepare_headers(query, body))
try:
query.set_response(self.conn.getresponse());
query.set_response(self.conn.getresponse())
except httplib.BadStatusLine, e:
print e
query.set_response(self.conn.getresponse());
query.set_response(self.conn.getresponse())
query.duration = time.time() - query.start
class HTTPSimpleQuery:
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Delegovaný je organizátor. Prosím určete jiného delegovaného.";
"delegate is a participant" = "Delegovaný je již účastníkem.";
"delegate is a group" = "Určená adresa odpovídá skupině. Delegovat můžete pouze na osobu.";
"Snooze for " = "Odložit o ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 hodinu";
"1 day" = "1 den";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Začátek";
"Due Date" = "Do dne";
"Location" = "Místo";
"Snooze" = "Odložit";
"Snooze for " = "Odložit o ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 hodinu";
"1 day" = "1 den";
/* mail labels */
"Important" = "Důležitý";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Špatné uživatelské jméno nebo heslo.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Zadejte alespoň %{minimumSearchLength} znaků";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Byl dosažen limit pro velikost nahraného souboru";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Přepnout zobrazení";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Přepnout rozsah položek";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Zobrazit nebo skrýt tuto nápovědu";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + mezerník";
/* Up arrow key */
"key_up" = "↑";
+13 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "The delegate is the organizer. Please specify a different delegate.";
"delegate is a participant" = "The delegate is already a participant.";
"delegate is a group" = "The specified address corresponds to a group. You can only delegate to a unique person.";
"Snooze for " = "Snooze for ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 hour";
"1 day" = "1 day";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Start";
"Due Date" = "Due Date";
"Location" = "Location";
"Snooze" = "Snooze";
"Snooze for " = "Snooze for ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 hour";
"1 day" = "1 day";
/* mail labels */
"Important" = "Important";
@@ -118,9 +119,12 @@
/* Authentication failed */
"Wrong username or password." = "Wrong username or password.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "File size upload limit reached";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Toggle visibility";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "L'adresse spécifiée correspond à l'organisateur. Veuillez entrer un autre délégué.";
"delegate is a participant" = "Le délégué est déjà un participant.";
"delegate is a group" = "L'adresse spécifiée correspond à un groupe. Vous ne pouvez déléguer qu'à une personne.";
"Snooze for " = "Rappel dans ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 heure";
"1 day" = "1 jour";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Début";
"Due Date" = "Échéance";
"Location" = "Lieu";
"Snooze" = "Rappel";
"Snooze for " = "Rappel dans ";
"5 minutes" = "5 minutes";
"10 minutes" = "10 minutes";
"15 minutes" = "15 minutes";
"30 minutes" = "30 minutes";
"45 minutes" = "45 minutes";
"1 hour" = "1 heure";
"1 day" = "1 jour";
/* mail labels */
"Important" = "Important";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Mauvais nom d'utilisateur ou mot de passe.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Entrez au moins %{minimumSearchLength} caractères";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Le fichier dépasse la taille limite permise";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Basculer l'affichage";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Basculer l'étendue des éléments";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Afficher ou cache cette aide";
/* Space key */
"key_space" = "espace";
/* Shift and space key */
"key_shift+space" = "maj. + espace";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Der Vertreter ist der Organisator. Bitte einen anderen Vertreter angeben.";
"delegate is a participant" = "Der Vertreter ist bereits ein Teilnehmer.";
"delegate is a group" = "Die angegebene Adresse gehört einer Gruppe. Es kann nur an eine einzelne Person delegiert werden.";
"Snooze for " = "Erneut erinnern nach ";
"5 minutes" = "5 Minuten";
"10 minutes" = "10 Minuten";
"15 minutes" = "15 Minuten";
"30 minutes" = "30 Minuten";
"45 minutes" = "45 Minuten";
"1 hour" = "1 Stunde";
"1 day" = "1 Tag";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Beginn";
"Due Date" = "Fällig";
"Location" = "Ort";
"Snooze" = "Erneut erinnern";
"Snooze for " = "Erneut erinnern nach ";
"5 minutes" = "5 Minuten";
"10 minutes" = "10 Minuten";
"15 minutes" = "15 Minuten";
"30 minutes" = "30 Minuten";
"45 minutes" = "45 Minuten";
"1 hour" = "1 Stunde";
"1 day" = "1 Tag";
/* mail labels */
"Important" = "Wichtig";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Falscher Benutzername oder falsches Passwort";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Mindestens %{minimumSearchLength} Zeichen eingeben";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Maximale Dateigröße für das Hochladen erreicht";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Sichtbarkeit umschalten";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Mehrere Punkte umschalten";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Diese Hilfe anzeigen oder ausblenden";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "Umschalt + Leerzeichen";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Wskazany delegat jest organizatorem. Wskaż innego delegata.";
"delegate is a participant" = "Delegat jest już uczestnikiem.";
"delegate is a group" = "Wskazany adres jest grupą. Możesz oddelegować tylko pojedynczną osobę.";
"Snooze for " = "Drzemka przez ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 godzinę";
"1 day" = "1 dzień";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Początek";
"Due Date" = "Termin";
"Location" = "Miejsce";
"Snooze" = "Drzemka";
"Snooze for " = "Drzemka przez ";
"5 minutes" = "5 minut";
"10 minutes" = "10 minut";
"15 minutes" = "15 minut";
"30 minutes" = "30 minut";
"45 minutes" = "45 minut";
"1 hour" = "1 godzinę";
"1 day" = "1 dzień";
/* mail labels */
"Important" = "Ważne";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Zła nazwa użytkownika lub hasło.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Wprowadź co najmniej %{minimumSearchLength} znaków";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Rozmiar pliku jest większy niż dopuszczalny limit";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Odwróć widok";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Odwróć wskazany zakres";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Pokaż lub ukryj tę podpowiedź";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + spacja";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Это организатор. Выберите другого приглашаемого.";
"delegate is a participant" = "Приглашаемый уже участник.";
"delegate is a group" = "Указанный адрес - адрес группы. Вы можете делегировать приглашение лишь отдельным людям.";
"Snooze for " = "Отложить на";
"5 minutes" = "5 минут";
"10 minutes" = "10 минут";
"15 minutes" = "15 минут";
"30 minutes" = "30 минут";
"45 minutes" = "45 минут";
"1 hour" = "1 час";
"1 day" = "1 день";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Начало";
"Due Date" = "Дата начала";
"Location" = "Место";
"Snooze" = "Отложить";
"Snooze for " = "Отложить на";
"5 minutes" = "5 минут";
"10 minutes" = "10 минут";
"15 minutes" = "15 минут";
"30 minutes" = "30 минут";
"45 minutes" = "45 минут";
"1 hour" = "1 час";
"1 day" = "1 день";
/* mail labels */
"Important" = "Важно";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Неправильный логин или пароль.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Введите не менее %{minimumSearchLength} символов";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Достигнуто ограничение на размер загружаемого файла";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Переключить видимость";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Сменить состояние нескольких элементов";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Показать или скрыть подсказку";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "Shift + Пробел";
/* Up arrow key */
"key_up" = "↑";
+19 -9
View File
@@ -69,14 +69,6 @@
"delegate is organizer" = "Delegát je organizátorom. Zadajte prosím iného delegáta.";
"delegate is a participant" = "Delegát je už účastníkom.";
"delegate is a group" = "Zadaná adresa zodpovedá skupine. Môžete ju delegovať len na jedinečnú osobu.";
"Snooze for " = "Odložiť";
"5 minutes" = "5 minút";
"10 minutes" = "10 minút";
"15 minutes" = "15 minút";
"30 minutes" = "30 minút";
"45 minutes" = "45 minút";
"1 hour" = "1 hodinu";
"1 day" = "1 deň";
/* common buttons */
"OK" = "OK";
@@ -89,6 +81,15 @@
"Start" = "Štart";
"Due Date" = "Splatnosť";
"Location" = "Umiestnenie";
"Snooze" = "Odložit";
"Snooze for " = "Odložiť do";
"5 minutes" = "5 minút";
"10 minutes" = "10 minút";
"15 minutes" = "15 minút";
"30 minutes" = "30 minút";
"45 minutes" = "45 minút";
"1 hour" = "1 hodinu";
"1 day" = "1 deň";
/* mail labels */
"Important" = "Dôležité";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Nesprávne uživateľské meno alebo heslo.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "Vložte minimálne %{minimumSearchLength} znakov";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Bol dosiahnutý limit pre veľkosť nahraného súboru";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Prepnúť zobrazenie";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Prepnúť rozsah položiek";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Zobraziť alebo skryť túto nápovedu";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "shift + medzerník";
/* Up arrow key */
"key_up" = "↑";
@@ -69,14 +69,6 @@
"delegate is organizer" = "Vekil edilen kişi etkinliği düzenleyen kişidir. Lütfen başka bir vekil belirleyin edin. ";
"delegate is a participant" = "Vekil edilen kişi zaten bu etkinliğe katılıyor.";
"delegate is a group" = "Belirtilen adres bir gruba denk geliyor. Sadece bir kişiyi vekil tayin edebilirsiniz.";
"Snooze for " = "Beklenecek süre";
"5 minutes" = "5 dakika";
"10 minutes" = "10 dakika";
"15 minutes" = "15 dakika";
"30 minutes" = "30 dakika";
"45 minutes" = "45 dakika";
"1 hour" = "1 saat";
"1 day" = "1 gün";
/* common buttons */
"OK" = "Tamam";
@@ -89,6 +81,15 @@
"Start" = "Başlangıç";
"Due Date" = "Son Tarih";
"Location" = "Konum";
"Snooze" = "Ertele";
"Snooze for " = "Beklenecek süre";
"5 minutes" = "5 dakika";
"10 minutes" = "10 dakika";
"15 minutes" = "15 dakika";
"30 minutes" = "30 dakika";
"45 minutes" = "45 dakika";
"1 hour" = "1 saat";
"1 day" = "1 gün";
/* mail labels */
"Important" = "Önemli";
@@ -118,18 +119,27 @@
/* Authentication failed */
"Wrong username or password." = "Kullanıcı adı veya şifre yanlış.";
/* Error message display bellow search field when the search string has less than the required number of characters */
/* Error message displayed bellow search field when the search string has less than the required number of characters */
"Enter at least %{minimumSearchLength} characters" = "En az %{minimumSearchLength} harf girin";
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
"File size upload limit reached" = "Yükleme üst sınırı aşıldı";
/* Toggle visibility (ex: mail account in left navigation menu) */
"Toggle visibility" = "Görünürlük geçişi";
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
"Toggle range of items" = "Ögeler arasında geçiş yap";
/* Question mark shows list of hotkeys */
"Show or hide this help" = "Bu yardımı gizle veya göster";
/* Space key */
"key_space" = "space";
/* Shift and space key */
"key_shift+space" = "üst karakter + boşluk";
/* Up arrow key */
"key_up" = "↑";
+49 -1
View File
@@ -20,12 +20,15 @@
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <SoObjects/SOGo/SOGoParentFolder.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserSettings.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSString+Utilities.h>
@@ -91,4 +94,49 @@
return response;
}
- (WOResponse *) saveFoldersActivationAction
{
NSDictionary *params;
NSEnumerator *foldersEnumerator;
NSMutableArray *folderSubscription;
NSMutableDictionary *moduleSettings;
NSString *baseFolder, *folderName;
SOGoParentFolder *clientObject;
SOGoUser *activeUser;
SOGoUserSettings *us;
WORequest *request;
BOOL makeActive;
request = [context request];
params = [[request contentAsString] objectFromJSONString];
activeUser = [context activeUser];
clientObject = [self clientObject];
baseFolder = [clientObject nameInContainer];
us = [activeUser userSettings];
moduleSettings = [us objectForKey: baseFolder];
if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary];
[us setObject: moduleSettings forKey: baseFolder];
folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"];
if (!folderSubscription)
{
folderSubscription = [NSMutableArray array];
[moduleSettings setObject: folderSubscription forKey: @"InactiveFolders"];
}
foldersEnumerator = [params keyEnumerator];
while ((folderName = [foldersEnumerator nextObject]))
{
makeActive = [[params objectForKey: folderName] boolValue];
if (makeActive)
[folderSubscription removeObject: folderName];
else
[folderSubscription addObjectUniquely: folderName];
}
[us synchronize];
return [self responseWith204];
}
@end
+5
View File
@@ -78,6 +78,11 @@
actionClass = "UIxParentFolderActions";
actionName = "createFolder";
};
saveFoldersActivation = {
protectedBy = "Change Images And Files";
actionClass = "UIxParentFolderActions";
actionName = "saveFoldersActivation";
};
};
};
SOGoFolder = {
@@ -153,8 +153,8 @@
= "Bu klasöre üye olunamıyor.";
/* acls */
"Access rights to" = "Adres defterinin erişim yetkileri";
"For user" = "Aşağıdaki kullanıcı için";
"Access rights to" = "Adres defteri";
"For user" = "Erişen";
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
"Public Access" = "Herkese Açık Erişim";
"This person can add cards to this addressbook."
+34 -16
View File
@@ -49,8 +49,17 @@
#import "UIxContactFolderActions.h"
static NSArray *photoTags = nil;
@implementation UIxContactFolderActions
+ (void) initialize
{
if (!photoTags)
{
photoTags = [[NSArray alloc] initWithObjects: @"jpegphoto", @"photo", @"thumbnailphoto", nil];
}
}
/* actions */
@@ -146,24 +155,26 @@
- (int) importLdifData: (NSString *) ldifData
{
NSMutableDictionary *entry, *encodedEntry;
SOGoContactLDIFEntry *ldifEntry;
NSArray *ldifContacts, *lines;
SOGoContactGCSFolder *folder;
NSString *key, *value;
NSArray *ldifContacts, *lines, *components;
NSMutableDictionary *entry;
NSEnumerator *keyEnumerator;
NSString *key, *uid, *line;
NGVCard *vCard;
NSString *uid;
int i,j,count,linesCount;
int rc = 0;
id value;
NSRange r;
int i, j, count, linesCount;
int rc;
folder = [self clientObject];
ldifContacts = [ldifData componentsSeparatedByString: @"\ndn"];
count = [ldifContacts count];
rc = 0;
for (i = 0; i < count; i++)
{
SOGoContactLDIFEntry *ldifEntry;
NSEnumerator *keyEnumerator;
NSMutableDictionary *encodedEntry;
encodedEntry = [NSMutableDictionary dictionary];
lines = [[ldifContacts objectAtIndex: i]
componentsSeparatedByString: @"\n"];
@@ -172,7 +183,6 @@
linesCount = [lines count];
for (j = 0; j < linesCount; j++)
{
NSString *line;
line = [lines objectAtIndex: j];
/* skip embedded comment lines */
@@ -188,17 +198,17 @@
if (key != NULL)
{
value = [[encodedEntry valueForKey: key]
stringByAppendingString: [line substringFromIndex: 1]];
stringByAppendingString: [line substringFromIndex: 1]];
[encodedEntry setValue: value forKey: key];
}
continue;
}
components = [line componentsSeparatedByString: @": "];
if ([components count] == 2)
r = [line rangeOfString: @": "];
if (r.location != NSNotFound)
{
key = [[components objectAtIndex: 0] lowercaseString];
value = [components objectAtIndex: 1];
key = [[line substringToIndex: r.location] lowercaseString];
value = [line substringFromIndex: NSMaxRange(r)];
if ([key length] == 0)
key = @"dn";
@@ -220,8 +230,16 @@
if ([key hasSuffix: @":"])
{
key = [key substringToIndex: [key length] - 1];
value = [value stringByDecodingBase64];
if ([photoTags containsObject: key])
value = [value dataByDecodingBase64];
else
value = [value stringByDecodingBase64];
}
// Standard key recognized in NGCards
if ([photoTags containsObject: key])
key = @"photo";
[entry setValue: value forKey: key];
}
+1 -522
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2005-2015 Inverse inc.
Copyright (C) 2005-2016 Inverse inc.
This file is part of SOGo.
@@ -62,73 +62,6 @@
[super dealloc];
}
/* accessors */
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// byEscapingHTMLString: (BOOL) escapeHTML
// asLinkScheme: (NSString *) scheme
// withLinkAttributes: (NSString *) attrs
// {
// NSMutableString *cardString;
// cardString = [NSMutableString stringWithCapacity: 80];
// value = [value stringByReplacingString: @"\r" withString: @""];
// if ([value length] > 0)
// {
// if (escapeHTML)
// value = [value stringByEscapingHTMLString];
// if ([scheme length] > 0)
// value = [NSString stringWithFormat: @"<a href=\"%@%@\" %@>%@</a>", scheme, value, attrs, value];
// if (label)
// [cardString appendFormat: @"<dt>%@</dt><dd>%@</dd>\n",
// [self labelForKey: label], value];
// else
// [cardString appendFormat: @"<dt></dt><dd>%@</dd>\n", value];
// }
// return cardString;
// }
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// {
// return [self _cardStringWithLabel: label
// value: value
// byEscapingHTMLString: YES
// asLinkScheme: nil
// withLinkAttributes: nil];
// }
// - (NSString *) _cardStringWithLabel: (NSString *) label
// value: (NSString *) value
// asLinkScheme: (NSString *) scheme
// {
// return [self _cardStringWithLabel: label
// value: value
// byEscapingHTMLString: YES
// asLinkScheme: scheme
// withLinkAttributes: nil];
// }
// - (NSString *) displayName
// {
// return [self _cardStringWithLabel: @"Display Name:"
// value: [card fn]];
// }
// - (NSString *) nickName
// {
// return [self _cardStringWithLabel: @"Nickname:"
// value: [card nickname]];
// }
// - (NSString *) fullName
// {
// return [card fullName];
// }
- (NSArray *) _languageContactsCategories
{
NSArray *categoryLabels;
@@ -168,346 +101,6 @@
return cats;
}
// - (NSString *) primaryEmail
// {
// NSString *email, *fn, *attrs;
// email = [card preferredEMail];
// if ([email length] > 0)
// {
// fn = [card fn];
// if ([fn length] > 0)
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
// else
// attrs = email;
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
// }
// else
// {
// attrs = nil;
// }
// return [self _cardStringWithLabel: @"Email:"
// value: email
// byEscapingHTMLString: YES
// asLinkScheme: @"mailto:"
// withLinkAttributes: attrs];
// }
// - (NSArray *) secondaryEmails
// {
// NSMutableArray *secondaryEmails;
// NSString *email, *fn, *attrs;
// NSArray *emails;
// emails = [card secondaryEmails];
// secondaryEmails = [NSMutableArray array];
// attrs = nil;
// // We might not have a preferred item but rather something like this:
// // EMAIL;TYPE=work:dd@ee.com
// // EMAIL;TYPE=home:ff@gg.com
// //
// // or:
// //
// // EMAIL;TYPE=INTERNET:a@a.com
// // EMAIL;TYPE=INTERNET,HOME:b@b.com
// //
// // In this case, we always return the entry NOT matching the primaryEmail
// if ([emails count] > 0)
// {
// int i;
// for (i = 0; i < [emails count]; i++)
// {
// email = [[emails objectAtIndex: i] flattenedValuesForKey: @""];
// if ([email length])
// {
// fn = [card fn];
// if ([fn length])
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
// else
// attrs = email;
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
// value: email
// byEscapingHTMLString: YES
// asLinkScheme: @"mailto:"
// withLinkAttributes: attrs]];
// }
// }
// }
// else
// {
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
// value: nil]];
// }
// return secondaryEmails;
// }
// - (NSString *) screenName
// {
// NSString *screenName;
// screenName = [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""];
// return [self _cardStringWithLabel: @"Screen Name:"
// value: screenName
// asLinkScheme: @"aim:goim?screenname="];
// }
// - (NSString *) preferredTel
// {
// return [self _cardStringWithLabel: @"Phone Number:"
// value: [card preferredTel] asLinkScheme: @"tel:"];
// }
// - (NSString *) preferredAddress
// {
// return @"";
// }
// - (BOOL) hasTelephones
// {
// if (!phones)
// phones = [card childrenWithTag: @"tel"];
// return ([phones count] > 0);
// }
// - (NSString *) workPhone
// {
// // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX
// // one if we do see the FAX field BEFORE the WORK number.
// return [self _cardStringWithLabel: @"Work:" value: [card workPhone] asLinkScheme: @"tel:"];
// }
// - (NSString *) homePhone
// {
// return [self _cardStringWithLabel: @"Home:" value: [card homePhone] asLinkScheme: @"tel:"];
// }
// - (NSString *) fax
// {
// return [self _cardStringWithLabel: @"Fax:" value: [card fax] asLinkScheme: @"tel:"];
// }
// - (NSString *) mobile
// {
// return [self _cardStringWithLabel: @"Mobile:" value: [card mobile] asLinkScheme: @"tel:"];
// }
// - (NSString *) pager
// {
// return [self _cardStringWithLabel: @"Pager:" value: [card pager] asLinkScheme: @"tel:"];
// }
// - (BOOL) hasHomeInfos
// {
// BOOL result;
// NSArray *elements;
// elements = [card childrenWithTag: @"adr"
// andAttribute: @"type"
// havingValue: @"home"];
// if ([elements count] > 0)
// {
// result = YES;
// homeAdr = [elements objectAtIndex: 0];
// }
// else
// result = ([[card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"home"] count] > 0);
// return result;
// }
// - (NSString *) homePobox
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 0
// forKey: @""]];
// }
// - (NSString *) homeExtendedAddress
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 1
// forKey: @""]];
// }
// - (NSString *) homeStreetAddress
// {
// return [self _cardStringWithLabel: nil
// value: [homeAdr flattenedValueAtIndex: 2
// forKey: @""]];
// }
// - (NSString *) homeCityAndProv
// {
// NSString *city, *prov;
// NSMutableString *data;
// city = [homeAdr flattenedValueAtIndex: 3 forKey: @""];
// prov = [homeAdr flattenedValueAtIndex: 4 forKey: @""];
// data = [NSMutableString string];
// [data appendString: city];
// if ([city length] > 0 && [prov length] > 0)
// [data appendString: @", "];
// [data appendString: prov];
// return [self _cardStringWithLabel: nil value: data];
// }
// - (NSString *) homePostalCodeAndCountry
// {
// NSString *postalCode, *country;
// NSMutableString *data;
// postalCode = [homeAdr flattenedValueAtIndex: 5 forKey: @""];
// country = [homeAdr flattenedValueAtIndex: 6 forKey: @""];
// data = [NSMutableString string];
// [data appendString: postalCode];
// if ([postalCode length] > 0 && [country length] > 0)
// [data appendFormat: @", "];
// [data appendString: country];
// return [self _cardStringWithLabel: nil value: data];
// }
// - (NSString *) _formattedURL: (NSString *) url
// {
// NSRange schemaR;
// NSString *schema, *data;
// if ([url length] > 0)
// {
// schemaR = [url rangeOfString: @"://"];
// if (schemaR.length > 0)
// {
// schema = [url substringToIndex: schemaR.location + schemaR.length];
// data = [url substringFromIndex: schemaR.location + schemaR.length];
// }
// else
// {
// schema = @"http://";
// data = url;
// }
// }
// else
// {
// schema = nil;
// data = nil;
// }
// return [self _cardStringWithLabel: nil
// value: data
// byEscapingHTMLString: YES
// asLinkScheme: schema
// withLinkAttributes: @"target=\"_blank\""];
// }
// - (NSString *) _urlOfType: (NSString *) aType
// {
// NSArray *elements;
// NSString *url;
// elements = [card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: aType];
// if ([elements count] > 0)
// url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// else
// url = nil;
// return [self _formattedURL: url];
// }
// - (NSString *) homeUrl
// {
// NSString *s;
// s = [self _urlOfType: @"home"];
// if (!s || [s length] == 0)
// {
// NSArray *elements;
// NSString *workURL;
// int i;
// elements = [card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"work"];
// workURL = nil;
// if ([elements count] > 0)
// workURL = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// elements = [card childrenWithTag: @"url"];
// if (workURL && [elements count] > 1)
// {
// for (i = 0; i < [elements count]; i++)
// {
// if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""]
// caseInsensitiveCompare: workURL] != NSOrderedSame)
// {
// s = [[elements objectAtIndex: i] flattenedValuesForKey: @""];
// break;
// }
// }
// }
// else if (!workURL && [elements count] > 0)
// {
// s = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
// }
// if (s && [s length] > 0)
// s = [self _formattedURL: s];
// }
// return s;
// }
// - (BOOL) hasWorkInfos
// {
// BOOL result;
// NSArray *elements;
// elements = [card childrenWithTag: @"adr"
// andAttribute: @"type"
// havingValue: @"work"];
// if ([elements count] > 0)
// {
// result = YES;
// workAdr = [elements objectAtIndex: 0];
// }
// else
// result = (([[card childrenWithTag: @"url"
// andAttribute: @"type"
// havingValue: @"work"] count] > 0)
// || [[card childrenWithTag: @"org"] count] > 0);
// return result;
// }
// - (NSString *) workTitle
// {
// return [self _cardStringWithLabel: nil value: [card title]];
// }
- (NSArray *) orgUnits
{
NSMutableArray *orgUnits;
@@ -640,120 +233,6 @@
return addresses;
}
// - (NSString *) workService
// {
// NSMutableArray *orgServices;
// NSArray *values;
// CardElement *org;
// NSString *service, *services;
// NSUInteger count, max;
// org = [card org];
// values = [org valuesForKey: @""];
// max = [values count];
// if (max > 1)
// {
// orgServices = [NSMutableArray arrayWithCapacity: max];
// for (count = 1; count < max; count++)
// {
// service = [org flattenedValueAtIndex: count forKey: @""];
// if ([service length] > 0)
// [orgServices addObject: service];
// }
// services = [orgServices componentsJoinedByString: @", "];
// }
// else
// services = nil;
// return [self _cardStringWithLabel: nil value: services];
// }
// - (NSString *) workUrl
// {
// return [self _urlOfType: @"work"];
// }
// - (BOOL) hasOtherInfos
// {
// return ([[card note] length] > 0
// || [[card bday] length] > 0
// || [[card tz] length] > 0);
// }
// - (NSString *) bday
// {
// SOGoDateFormatter *dateFormatter;
// NSCalendarDate *date;
// NSString *bday;
// date = [card birthday];
// bday = nil;
// if (date)
// {
// dateFormatter = [[[self context] activeUser] dateFormatterInContext: context];
// bday = [dateFormatter formattedDate: date];
// }
// return bday;
// //return [self _cardStringWithLabel: @"Birthday:" value: bday];
// }
// - (NSString *) tz
// {
// return [self _cardStringWithLabel: @"Timezone:" value: [card tz]];
// }
// - (NSArray *) notes
// {
// NSMutableArray *notes;
// NSString *note;
// NSUInteger count, max;
// notes = [NSMutableArray arrayWithArray: [card notes]];
// max = [notes count];
// for (count = 0; count < max; count++)
// {
// note = [notes objectAtIndex: count];
// note = [note stringByEscapingHTMLString];
// note = [note stringByReplacingString: @"\r\n"
// withString: @"<br />"];
// note = [note stringByReplacingString: @"\n"
// withString: @"<br />"];
// [notes replaceObjectAtIndex: count withObject: note];
// }
// return notes;
// }
/* hrefs */
// - (NSString *) completeHrefForMethod: (NSString *) _method
// withParameter: (NSString *) _param
// forKey: (NSString *) _key
// {
// NSString *href;
// [self setQueryParameter:_param forKey:_key];
// href = [self completeHrefForMethod:[self ownMethodName]];
// [self setQueryParameter:nil forKey:_key];
// return href;
// }
// - (NSString *)attributesTabLink {
// return [self completeHrefForMethod:[self ownMethodName]
// withParameter:@"attributes"
// forKey:@"tab"];
// }
// - (NSString *)debugTabLink {
// return [self completeHrefForMethod:[self ownMethodName]
// withParameter:@"debug"
// forKey:@"tab"];
// }
/* action */
- (id <WOActionResults>) defaultAction
@@ -168,6 +168,7 @@
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
[viewer setBodyInfo: info];
[viewer setPartPath: [self childPartPath]];
[viewer setAttachmentIds: attachmentIds];
[renderedParts addObject: [viewer renderedPart]];
}
+13 -4
View File
@@ -28,6 +28,7 @@
#include <libxml/encoding.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/Mailer/SOGoMailBodyPart.h>
@@ -908,6 +909,8 @@ static NSData* _sanitizeContent(NSData *theData)
NSObject <SaxXMLReader> *parser;
NSData *preparsedContent;
SOGoMailObject *mail;
NSString *s;
xmlCharEncoding enc;
mail = [self clientObject];
@@ -917,7 +920,7 @@ static NSData* _sanitizeContent(NSData *theData)
createXMLReaderForMimeType: @"text/html"];
handler = [_UIxHTMLMailContentHandler new];
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
[handler setAttachmentIds: attachmentIds];
// We check if we got an unsupported charset. If so
// we convert everything to UTF-16{LE,BE} so it passes
@@ -926,8 +929,6 @@ static NSData* _sanitizeContent(NSData *theData)
enc = [self _xmlCharEncoding];
if (enc == XML_CHAR_ENCODING_ERROR)
{
NSString *s;
s = [NSString stringWithData: preparsedContent
usingEncodingNamed: [[bodyInfo objectForKey:@"parameterList"]
objectForKey: @"charset"]];
@@ -954,6 +955,14 @@ static NSData* _sanitizeContent(NSData *theData)
#endif
}
// Let's sanitize the string to make sure libxml doesn't go havoc
if (enc == XML_CHAR_ENCODING_UTF8)
{
s = [[NSString alloc] initWithData: preparsedContent encoding: NSUTF8StringEncoding];
preparsedContent = [[s safeString] dataUsingEncoding: NSUTF8StringEncoding];
RELEASE(s);
}
[handler setContentEncoding: enc];
[parser setContentHandler: handler];
@@ -1038,7 +1047,7 @@ static NSData* _sanitizeContent(NSData *theData)
encoding = @"us-ascii";
handler = [_UIxHTMLMailContentHandler new];
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
[handler setAttachmentIds: attachmentIds];
// We check if we got an unsupported charset. If so
// we convert everything to UTF-16{LE,BE} so it passes
+44 -8
View File
@@ -41,6 +41,7 @@
#import <Appointments/SOGoAppointmentFolders.h>
#import <Mailer/SOGoMailObject.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSString+Utilities.h>
#import <Mailer/SOGoMailBodyPart.h>
@@ -234,20 +235,20 @@
- (WOResponse *) _changePartStatusAction: (NSString *) newStatus
withDelegate: (iCalPerson *) delegate
{
WOResponse *response;
SOGoAppointmentObject *eventObject;
WOResponse *response;
iCalEvent *chosenEvent;
iCalAlarm *alarm;
chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
if (chosenEvent)
{
// For invitations, we take the organizers's alarm to start with
alarm = [[chosenEvent alarms] lastObject];
response = (WOResponse*)[eventObject changeParticipationStatus: newStatus
withDelegate: delegate
alarm: nil
alarm: alarm
forRecurrenceId: [chosenEvent recurrenceId]];
// if (ex)
// response = ex; //[self responseWithStatus: 500];
// else
if (!response)
response = [self responseWith204];
}
@@ -380,15 +381,50 @@
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
SOGoMailObject *mailObject;
NSString *emailFrom;
iCalPerson *p;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
p = [event findAttendeeWithEmail: emailFrom];
return [event findAttendeeWithEmail: emailFrom];
// We haven't found it yet, let's look in the identities
// associated to this user
if (!p)
{
SOGoUserManager *sm;
NSString *uid;
sm = [SOGoUserManager sharedUserManager];
uid = [sm getUIDForEmail: emailFrom];
if (uid)
{
NSArray *allEmails;
NSString *email;
SOGoUser *u;
int i;
u = [SOGoUser userWithLogin: uid];
allEmails = [u allEmails];
for (i = 0; i < [allEmails count]; i++)
{
email = [allEmails objectAtIndex: i];
if ([email caseInsensitiveCompare: emailFrom] == NSOrderedSame)
continue;
p = [event findAttendeeWithEmail: email];
if (p)
break;
}
}
}
return p;
}
- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent
+54 -25
View File
@@ -43,6 +43,7 @@
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserManager.h>
#import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentObject.h>
@@ -470,31 +471,67 @@
return currentUser;
}
- (iCalPerson *) storedReplyAttendee
- (iCalPerson *) _attendeeFromMailSenderInEvent: (iCalEvent *) e
{
/*
TODO: since an attendee can have multiple email addresses, maybe we
should translate the email to an internal uid and then retrieve
all emails addresses for matching the participant.
Note: -findAttendeeWithEmail: does not parse the email!
*/
iCalEvent *e;
NSString *baseEmail;
iCalPerson *p;
p = nil;
e = [self storedEvent];
if (e)
baseEmail = [self replySenderBaseEMail];
/*
Note: -findAttendeeWithEmail: does not parse the email!
*/
p = [e findAttendeeWithEmail: baseEmail];
if (!p)
p = [e findAttendeeWithEmail:[self replySenderEMail]];
// We haven't found it yet, let's look in the identities
// associated to this user
if (!p)
{
p = [e findAttendeeWithEmail: [self replySenderBaseEMail]];
if (!p)
p = [e findAttendeeWithEmail:[self replySenderEMail]];
SOGoUserManager *sm;
NSString *uid;
sm = [SOGoUserManager sharedUserManager];
uid = [sm getUIDForEmail: baseEmail];
if (uid)
{
NSArray *allEmails;
NSString *email;
SOGoUser *u;
int i;
u = [SOGoUser userWithLogin: uid];
allEmails = [u allEmails];
for (i = 0; i < [allEmails count]; i++)
{
email = [allEmails objectAtIndex: i];
if ([email caseInsensitiveCompare: baseEmail] == NSOrderedSame)
continue;
p = [e findAttendeeWithEmail: email];
if (p)
break;
}
}
}
return p;
}
- (iCalPerson *) storedReplyAttendee
{
iCalEvent *e;
e = [self storedEvent];
if (e)
return [self _attendeeFromMailSenderInEvent: e];
return nil;
}
- (BOOL) isReplySenderAnAttendee
{
return ([self storedReplyAttendee] != nil);
@@ -502,15 +539,7 @@
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
{
NSString *emailFrom;
SOGoMailObject *mailObject;
NGImap4EnvelopeAddress *address;
mailObject = [[self clientObject] mailObject];
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail];
return [event findAttendeeWithEmail: emailFrom];
return [self _attendeeFromMailSenderInEvent: event];
}
- (BOOL) hasSenderStatusChanged
@@ -101,6 +101,7 @@
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
[viewer setBodyInfo: info];
[viewer setPartPath: [self childPartPath]];
[viewer setAttachmentIds: attachmentIds];
[renderedParts addObject: [viewer renderedPart]];
}
contentType = [NSString stringWithFormat: @"%@/%@",
+4
View File
@@ -45,6 +45,7 @@
@class NSArray;
@class NSData;
@class NSFormatter;
@class NSMutableDictionary;
@class SOGoMailBodyPart;
@@ -53,6 +54,7 @@
NSArray *partPath;
id bodyInfo;
NSData *flatContent;
NSDictionary *attachmentIds;
}
/* accessors */
@@ -66,6 +68,8 @@
- (SOGoMailBodyPart *) clientPart;
- (id) renderedPart;
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds;
- (NSData *)flatContent;
- (NSData *)decodedFlatContent;
- (NSString *)flatContentAsString;
+15
View File
@@ -42,6 +42,16 @@
@implementation UIxMailPartViewer
- (id) init
{
if ((self = [super init]))
{
attachmentIds = nil;
}
return self;
}
- (void) dealloc
{
[flatContent release];
@@ -153,6 +163,11 @@
nil];
}
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
{
attachmentIds = newAttachmentIds;
}
- (NSData *) content
{
return [[self clientObject] fetchBLOB];
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Vítejte v SOGo Mailer. Využijte stromu složek nalevo pro procházení Vašimi mailovými účty!";
"Read messages" = "Číst zprávy";
/* Tooltip for fab button */
"Write a new message" = "Napsat zprávu";
/* Tooltip for fab button */
"Write a message in new window" = "Napsat zprávu v novém okně";
"Share" = "Sdílet";
"Account" = "Účet";
"Shared Account" = "Sdílený účet";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Převést na úkol";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Stáhnout všechny přílohy";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "přílohy";
"Print..." = "Tisk...";
"Delete Message" = "Smazat zprávu";
"Delete Selected Messages" = "Smazat označené zprávy";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Při přenosu souboru \"%{0}\" došlo k chybě:";
"There is an active file upload. Closing the window will interrupt it." = "Přenáší se soubor. Zavření okna způsobí přerušení přenosu.";
"Message is too big" = "Zpráva je příliš velká";
/* Appears while sending the message */
"Sending" = "Odesílání zprávy";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!";
"Read messages" = "Read messages";
/* Tooltip for fab button */
"Write a new message" = "Write a new message";
/* Tooltip for fab button */
"Write a message in new window" = "Write a message in new window";
"Share" = "Share";
"Account" = "Account";
"Shared Account" = "Shared Account";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Convert To Task";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Download all attachments";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "attachments";
"Print..." = "Print...";
"Delete Message" = "Delete Message";
"Delete Selected Messages" = "Delete Selected Messages";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Error while uploading the file \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "There is an active file upload. Closing the window will interrupt it.";
"Message is too big" = "Message is too big";
/* Appears while sending the message */
"Sending" = "Sending";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Bienvenue dans votre webmail. Utilisez l'arborescence sur votre gauche pour parcourir vos boites mails.";
"Read messages" = "Lire les messages";
/* Tooltip for fab button */
"Write a new message" = "Écrire un nouveau message";
/* Tooltip for fab button */
"Write a message in new window" = "Écrire un message dans une nouvelle fenêtre";
"Share" = "Partage";
"Account" = "Compte";
"Shared Account" = "Compte partagé";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Convertir en tâche";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Télécharger les pièces jointes";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "pièces-jointes";
"Print..." = "Imprimer...";
"Delete Message" = "Supprimer le message";
"Delete Selected Messages" = "Supprimer les messages sélectionnés";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Une erreur est survenue lors du téléversement du fichier « %{0} » :";
"There is an active file upload. Closing the window will interrupt it." = "Un transfert est actif. La fermeture de la fenêtre causera son interruption.";
"Message is too big" = "Le message dépasse la limite permise";
/* Appears while sending the message */
"Sending" = "Envoi en cours";
+14 -1
View File
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Willkommen zum SOGo Mailer. Verwenden Sie den Ordnerbaum auf der linken Seite, um in Ihrem E-Mail-Konto zu stöbern!";
"Read messages" = "Nachrichten lesen";
/* Tooltip for fab button */
"Write a new message" = "Eine neue Nachricht schreiben";
/* Tooltip for fab button */
"Write a message in new window" = "Nachricht in einem neuen Fenster schreiben";
"Share" = "Benutzerrechte";
"Account" = "Konto";
"Shared Account" = "Gemeinsames Konto";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "In Aufgabe umwandeln";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Alle Anhänge herunterladen";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "Anhänge";
"Print..." = "Drucken...";
"Delete Message" = "Nachricht löschen";
"Delete Selected Messages" = "Gewählte Nachricht(en) löschen";
@@ -280,7 +292,7 @@
"%{0} message(s) copied" = "%{0} Nachrichten kopiert";
/* Notification on the number of messages successfuly movied */
"%{0} message(s) moved" = "%{0} Nachrichten verschoben";
"%{0} message(s) moved" = "%{0} Nachricht(en) verschoben";
"Please select a message." = "Sie müssen eine Nachricht auswählen.";
"Please select a message to print." = "Sie müssen eine Nachricht zum Drucken auswählen.";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Fehler beim Hochladen der Datei \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "Es wird gerade eine Datei hochgeladen. Das Schließen des Fensters wird dies abbrechen.";
"Message is too big" = "Nachricht ist zu groß";
/* Appears while sending the message */
"Sending" = "Sende";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Witaj w programie SOGo Mailer. Używaj drzewa folderów po lewej stronie by przeglądać swoje konta pocztowe!";
"Read messages" = "Wiadomości przeczytane";
/* Tooltip for fab button */
"Write a new message" = "Napisz nową wiadomość";
/* Tooltip for fab button */
"Write a message in new window" = "Utwórz wiadomość w nowym oknie";
"Share" = "Udostępnianie";
"Account" = "Konto";
"Shared Account" = "Udostępnione konto";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Zamień na zadanie";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Pobierz wszystkie załączniki";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "załączniki";
"Print..." = "Drukuj...";
"Delete Message" = "Usuń wiadomość";
"Delete Selected Messages" = "Usuń zaznaczone wiadomości";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Błąd w trakcie wysyłania pliku \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "Trwa przesyłanie pliku. Zamknięcie okna przerwie tą transmisję.";
"Message is too big" = "Wiadomość jest zbyt duża";
/* Appears while sending the message */
"Sending" = "Wysyłanie";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Добро пожаловать в почту SOGo! Для просмотра сообщений воспользуйтесь деревом папок слева.";
"Read messages" = "Читать сообщения";
/* Tooltip for fab button */
"Write a new message" = "Составить новое сообщение";
/* Tooltip for fab button */
"Write a message in new window" = "Составить сообщение в новом окне";
"Share" = "Совместное использование";
"Account" = "Пользователь";
"Shared Account" = "Общая учетная запись";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Преобразовать в задание";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Загрузить все вложения";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "вложения";
"Print..." = "Печать...";
"Delete Message" = "Удалить сообщение";
"Delete Selected Messages" = "Удалить выделенные сообщения";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Ошибка при загрузке файла \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "Сейчас выполняется загрузка файла. Закрытие окна прервет её.";
"Message is too big" = "Слишком большое сообщение";
/* Appears while sending the message */
"Sending" = "Отправляется";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Vitajte v SOGo Mailer. Použite zoznam zložiek vľavo na prehliadanie Vášho emailového účtu!";
"Read messages" = "Čítať správu";
/* Tooltip for fab button */
"Write a new message" = "Nápísať správu";
/* Tooltip for fab button */
"Write a message in new window" = "Napísať správu v novom okne";
"Share" = "Zdielať";
"Account" = "Účet";
"Shared Account" = "Zdielaný účet";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Zmeniť na úlohu";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Stiahnuť všetky prílohy";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "prílohy";
"Print..." = "Tlačiť";
"Delete Message" = "Zmazať správu";
"Delete Selected Messages" = "Zmazať vybrané správy";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = "Nastala chyba počas nahrávania súboru \"%{0}\":";
"There is an active file upload. Closing the window will interrupt it." = "Práve prebieha nahrávanie súboru. Zatvorením okna nahrávanie prerušíte.";
"Message is too big" = "Správa je príliš veľká";
/* Appears while sending the message */
"Sending" = "Odosielam správu";
@@ -53,7 +53,13 @@
/* Mail account main windows */
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "SOGo Postalayıcısına hoş geldiniz. E-posta hesaplarınızı görmek için soldaki klasör ağacını kullanın!";
"Read messages" = "İletileri oku";
/* Tooltip for fab button */
"Write a new message" = "Yeni ileti yaz";
/* Tooltip for fab button */
"Write a message in new window" = "Yeni pencerede bir ileti yaz";
"Share" = "Paylaşım";
"Account" = "Hesap";
"Shared Account" = "Paylaşılan Hesap";
@@ -96,7 +102,7 @@
"To" = "Alıcı";
"Cc" = "Karbon Kopya";
"Bcc" = "Gizli Karbon Kopya";
"Reply-To" = "Yanıtlama adresi";
"Reply-To" = "Yanıt adresi";
"Add address" = "Adresleri ekle";
"Body" = "Gövde";
"Open" = "Aç";
@@ -111,7 +117,7 @@
"bcc" = "Gizli Karbon Kopya";
"Add a recipient" = "Bir alıcı ekleyin";
"Edit Draft..." = "Taslağı Düzenle...";
"Load Images" = "Resimleri yükle";
"Load Images" = "Resimleri göster";
"Return Receipt" = "Alındı Raporu";
"The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "Bu iletiyi gönderen kişi, iletiyi okuduğunuzdan haberdar olmak istedi. Gönderene alındı raporu yollamak ister misiniz?";
"Return Receipt (displayed) - %@"= "Alındı Raporu (görüntülendi) - %@";
@@ -155,10 +161,10 @@
"Untitled" = "Başlıksız";
/* Tree */
"SentFolderName" = "Gönderilmiş";
"SentFolderName" = "Giden";
"TrashFolderName" = "Çöp";
"InboxFolderName" = "Gelen";
"DraftsFolderName" = "Taslaklar";
"DraftsFolderName" = "Taslak";
"JunkFolderName" = "İstenmeyen";
"SieveFolderName" = "Süzgeçler";
"Folders" = "Klasörler"; /* title line */
@@ -193,8 +199,8 @@
"Delegation..." = "Yetkili Tayini...";
/* Use This Folder menu */
"Sent Messages" = "Gönderilmiş İletiler";
"Drafts" = "Taslaklar";
"Sent Messages" = "Giden İletiler";
"Drafts" = "Taslak";
"Deleted Messages" = "Silinmiş İletiler";
"Junk Messages" = "İstenmeyen İletiler";
@@ -217,6 +223,12 @@
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Göreve Dönüştür";
/* Message view "more" menu: download all attachments as a zip archive */
"Download all attachments" = "Tüm eklentileri indir";
/* Filename prefix when downloading all attachments as a zip archive */
"attachments" = "eklentiler";
"Print..." = "Yazdır...";
"Delete Message" = "İletiyi Sil";
"Delete Selected Messages" = "Seçili İletileri Sil";
@@ -284,7 +296,7 @@
"Please select a message." = "Lütfen bir ileti seçin.";
"Please select a message to print." = "Lütfen yazdırılacak iletiyi seçin.";
"Please select only one message to print." = "Lütfen yazdırmak için sadece bir ileti seçin.";
"Please select only one message to print." = "Yazdırmak için lütfen sadece bir ileti seçin.";
"The message you have selected doesn't exist anymore." = "Seçtiğiniz ileti artık mevcut değil.";
"The folder with name \"%{0}\" could not be created."
= "\"%{0}\" adındaki klasör oluşturulamıyor.";
@@ -315,6 +327,7 @@
/* Error when uploading a file attachment */
"Error while uploading the file \"%{0}\":" = " \"%{0}\" dosyasını yüklerken hata oluştu:";
"There is an active file upload. Closing the window will interrupt it." = "Devam eden bir dosya yükleme işlemi var. Pencereyi kapanırsa yükleme işlemi duracak.";
"Message is too big" = "İleti çok büyük";
/* Appears while sending the message */
"Sending" = "Gönderiliyor";
@@ -340,8 +353,8 @@
"Rename" = "Yeniden Adlandır";
"Compact" = "Sıkıştır";
"Export" = "Dışarı Aktar";
"Set as Drafts" = "Taslaklar klasörü olarak tanımla";
"Set as Sent" = "Gönderilmiş klasörü olarak tanımla";
"Set as Drafts" = "Taslak klasörü olarak tanımla";
"Set as Sent" = "Giden klasörü olarak tanımla";
"Set as Trash" = "Çöp klasörü olarak tanımla";
/* Set the folder as the one holding Junk mails */
+2 -3
View File
@@ -661,10 +661,9 @@ static NSArray *infoKeys = nil;
{
NSDictionary *d;
d = [NSDictionary dictionaryWithObjectsAndKeys: msg, @"textStatus", nil];
d = [NSDictionary dictionaryWithObjectsAndKeys: msg, @"message", nil];
return [self responseWithStatus: 500
andString: [d jsonRepresentation]];
return [self responseWithStatus: 500 andJSONRepresentation: d];
}
/* attachment helper */
+31
View File
@@ -213,6 +213,25 @@ static NSString *mailETag = nil;
viewer = [[context mailRenderingContext] viewerForBodyInfo: info];
[viewer setBodyInfo: info];
NSMutableDictionary *attachmentIds;
NSDictionary *attributes;
NSString *filename;
unsigned int count, max;
max = [[self attachmentAttrs] count];
attachmentIds = [NSMutableDictionary dictionaryWithCapacity: max];
for (count = 0; count < max; count++)
{
attributes = [[self attachmentAttrs] objectAtIndex: count];
filename = [NSString stringWithFormat: @"<%@>", [attributes objectForKey: @"filename"]];
[attachmentIds setObject: [attributes objectForKey: @"url"]
forKey: filename];
if ([[attributes objectForKey: @"bodyId"] length])
[attachmentIds setObject: [attributes objectForKey: @"url"]
forKey: [attributes objectForKey: @"bodyId"]];
}
[viewer setAttachmentIds: attachmentIds];
return viewer;
}
@@ -312,6 +331,18 @@ static NSString *mailETag = nil;
return response;
}
- (id <WOActionResults>) archiveAttachmentsAction
{
NSString *name;
SOGoMailObject *co;
co = [self clientObject];
name = [NSString stringWithFormat: @"%@-%@.zip",
[self labelForKey: @"attachments"], [co nameInContainer]];
return [co archiveAllFilesinArchiveNamed: name];
}
/* MDN */
- (BOOL) _userHasEMail: (NSString *) email
+5
View File
@@ -244,6 +244,11 @@
pageName = "UIxMailView";
actionName = "sendMDN";
};
archiveAttachments = {
protectedBy = "View";
pageName = "UIxMailView";
actionName = "archiveAttachments";
};
viewsource = {
protectedBy = "View";
actionClass = "UIxMailSourceView";
+1 -1
View File
@@ -1,7 +1,7 @@
/* ChineseTaiwan */
{
NSLanguageName = "ChineseTaiwan";
NSFormalName = "ChineseTaiwan";
NSFormalName = "正體中文";
NSLocaleCode = "zh"; /* ISO 639-1 */
NSLanguageCode = "zho"; /* ISO 639-2 */
NSParentContext = "";
+5 -4
View File
@@ -40,6 +40,7 @@
#import <SOGo/SOGoWebAuthenticator.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserProfile.h>
#import <SOGo/SOGoSession.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/NSCalendarDate+SOGo.h>
@@ -535,14 +536,14 @@
{
NSString *uid;
NSDictionary *contact;
NSString *contactInfo, *login;
NSString *contactInfo, *owner;
NSMutableArray *jsonResponse;
NSMutableDictionary *jsonLine;
NSArray *allUsers;
int count, max;
BOOL activeUserIsInDomain;
login = [[context activeUser] login];
owner = [[self clientObject] ownerInContext: context];
activeUserIsInDomain = ([domain length] == 0 || [[[context activeUser] domain] isEqualToString: domain]);
// We sort our array - this is pretty useful for the Web
@@ -556,8 +557,8 @@
contact = [allUsers objectAtIndex: count];
uid = [contact objectForKey: @"c_uid"];
// We do NOT return the current authenticated user
if (!activeUserIsInDomain || ![uid isEqualToString: login])
// We do NOT return the owner from which the search is performed
if (!activeUserIsInDomain || ![uid isEqualToString: owner])
{
jsonLine = [NSMutableDictionary dictionary];
if ([domain length])
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Text podpisu";
"Please specify a valid sender address." = "Prosím zadejte správnou adresu odesílatele.";
"Please specify a valid reply-to address." = "Prosím zadejte správnou adresu pro odpověď.";
"Specify a hostname other than the local host" = "Zadefinujte jiné doménové jméno než local host";
/* Additional Parameters */
"Additional Parameters" = "Dodatečné parametry";
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Please enter your signature below";
"Please specify a valid sender address." = "Please specify a valid sender address.";
"Please specify a valid reply-to address." = "Please specify a valid reply-to address.";
"Specify a hostname other than the local host" = "Specify a hostname other than the local host";
/* Additional Parameters */
"Additional Parameters" = "Additional Parameters";
@@ -35,16 +35,16 @@
"contacts_category_labels" = "Ami, Client, Collègue, Concurrent, Famille, Fournisseur, Partenaire d'affaire, Presse, VIP";
/* vacation (auto-reply) */
"Enable vacation auto reply" = "Activer message d'absence prolongée";
"Enable vacation auto reply" = "Activer les réponses automatiques d'absence prolongée";
"Enable custom auto reply subject" = "Activer un sujet personnalisé";
"Auto reply subject" = "Sujet personnalisé";
"You can write ${subject} to insert the original subject" = "Inscrivez ${subject} pour insérer le sujet original";
"Auto reply message" = "Message de réponse automatique";
"Auto reply subject" = "Modifier le sujet de la réponse automatique";
"You can write ${subject} to insert the original subject" = "Inscrire ${subject} pour insérer le sujet original";
"Auto reply message" = "Contenu de la réponse automatique";
"Email addresses (separated by commas)" = "Adresses courriels (séparées par des virgules)";
"Add default email addresses" = "Ajouter les adresses par défaut";
"Days between responses" = "Nombre de jours entre réponses";
"Days between responses" = "Nombre de jours entre les réponses d'absence";
"Do not send responses to mailing lists" = "Ne pas envoyer les réponses aux listes de diffusion";
"Enable auto reply on" = "Activer la réponse automatique le";
"Enable auto reply on" = "Débuter la réponse automatique le";
"Disable auto reply on" = "Désactiver la réponse automatique le";
"Always send vacation message response" = "Toujours envoyer le message de vacances prolongées";
"Please specify your message and your email addresses for which you want to enable auto reply."
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Introduisez votre signature ci-dessous";
"Please specify a valid sender address." = "Veuillez définir une adresse d'expédition valide.";
"Please specify a valid reply-to address." = "Veuillez définir une adresse de retour valide.";
"Specify a hostname other than the local host" = "Veuillez utiliser un nom d'hôte autre que l'hôte local";
/* Additional Parameters */
"Additional Parameters" = "Paramètres supplémentaires";
@@ -148,7 +148,7 @@
"Label" = "Schlagwort";
"New label" = "Neue Bezeichnung";
"Show subscribed mailboxes only" = "Nur abonnierte Ordner anzeigen";
"Synchronize only default mail folders (EAS)" = "Nur Standard-Mail-Ordner synchronisieren (EAS)";
"Synchronize only default mail folders (EAS)" = "Nur Standard-E-Mail-Ordner synchronisieren (EAS)";
"Sort messages by threads" = "Nachrichten nach Thema sortieren";
"When sending mail, add unknown recipients to my" = "Unbekannte Empfänger meiner E-Mails hinzufügen zu";
"Address Book" = "Adressbuch";
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Bitte fügen Sie die Signatur hier ein";
"Please specify a valid sender address." = "Bitte geben Sie eine gültige Absenderadresse an.";
"Please specify a valid reply-to address." = "Bitte geben Sie eine gültige \"Antworten An\"-Adresse (Reply-To) an.";
"Specify a hostname other than the local host" = "Geben Sie einen anderen Servernamen an, als den Lokalen";
/* Additional Parameters */
"Additional Parameters" = "Zusätzliche Einstellungen";
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Wprowadź swoją sygnaturę";
"Please specify a valid sender address." = "Wprowadź poprawny adres nadawcy.";
"Please specify a valid reply-to address." = "Wprowadź poprawny adres \"Odpowiedź do\"";
"Specify a hostname other than the local host" = "Podaj nazwę serwera inną niż host lokalny";
/* Additional Parameters */
"Additional Parameters" = "Dodatkowe parametry";
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Введите Вашу подпись ниже";
"Please specify a valid sender address." = "Пожалуйста, укажите правильный адрес отправителя.";
"Please specify a valid reply-to address." = "Пожалуйста, укажите правильный адрес для ответа.";
"Specify a hostname other than the local host" = "Укажите имя хоста, отличное от localhost";
/* Additional Parameters */
"Additional Parameters" = "Дополнительные параметры";
@@ -194,6 +194,7 @@
"Please enter your signature below" = "Prosím vložte svoj podpis nižšie";
"Please specify a valid sender address." = "Prosím zvoľte platnú adresu odosielateľa";
"Please specify a valid reply-to address." = "Prosím zvoľte platnú adresu pre odpoveď.";
"Specify a hostname other than the local host" = "Zadajte iné doménové meno ako local host";
/* Additional Parameters */
"Additional Parameters" = "Dalšie parametre";
@@ -38,14 +38,14 @@
"Enable vacation auto reply" = "Otomatik tatil yanıtını etkinleştir";
"Enable custom auto reply subject" = "Özelleştirilmiş otomatik yanıt konusunu etkinleştir";
"Auto reply subject" = "Otomatik yanıt konusu";
"You can write ${subject} to insert the original subject" = "Orjinal konuyu iliştirmek için ${subject} yazabilirsiniz";
"You can write ${subject} to insert the original subject" = "Asıl konuyu eklemek için ${subject} yazabilirsiniz";
"Auto reply message" = "Otomatik yanıt iletisi";
"Email addresses (separated by commas)" = "E-posta adresleri (virgülle ayrılmış)";
"Add default email addresses" = "Varsayılan e-posta adresi ekle";
"Days between responses" = "Yanıtlar arasındaki gün sayısı";
"Do not send responses to mailing lists" = "Yanıtları e-posta listelerine gönderme";
"Enable auto reply on" = "Otomatik yanıtlamayı şu tarihlerde etkinleştir";
"Disable auto reply on" = "Otomatik yanıtlamayı şu tarihlerde devre dışı bırak";
"Enable auto reply on" = "Otomatik yanıtı şu tarihlerde etkinleştir";
"Disable auto reply on" = "Otomatik yanıtı şu tarihlerde devre dışı bırak";
"Always send vacation message response" = "Her zaman tatil yanıtı iletisi gönder";
"Please specify your message and your email addresses for which you want to enable auto reply."
= "Lütfen otomatik yanıtı etkinleştireceğiniz e-posta adreslerini ve yanıt iletinizi belirleyin.";
@@ -150,14 +150,14 @@
"Show subscribed mailboxes only" = "Sadece üye olunan e-posta klasörlerini göster";
"Synchronize only default mail folders (EAS)" = "Sadece varsayılan e-posta klasörlerini eşitle (EAS)";
"Sort messages by threads" = "İletileri diziler halinde sırala";
"When sending mail, add unknown recipients to my" = "İleti gönderirken, kayıtlı olmayan alıcıları aşağıdaki adres defterime ekle";
"When sending mail, add unknown recipients to my" = "Gönderilen iletideki alıcıları aşağıdaki defterime ekle";
"Address Book" = "Adres Defteri";
"Forward messages" = "Başkasına İletilen e-postayı";
"messageforward_inline" = "İletinin İçine Ekle";
"messageforward_attached" = "Dosya Olarak Ekle";
"When replying to a message" = "İletiyi yanıtlarken";
"replyplacement_above" = "Yanıtımı orjinal yazının üzerinde başlat";
"replyplacement_below" = "Yanıtımı orjinal yazının altında başlat";
"replyplacement_above" = "Yanıtımı asıl yazının üzerinde başlat";
"replyplacement_below" = "Yanıtımı asıl yazının altında başlat";
"And place my signature" = "Ve imzamı";
"signatureplacement_above" = "yanıtımın altına yerleştir";
"signatureplacement_below" = "yazının altına yerleştir";
@@ -168,15 +168,15 @@
/* Base font size for messages composed in HTML */
"Default font size" = "Varsayılan yazı tipi boyutu";
"Display remote inline images" = "İleti içinindeki harici resimleri";
"displayremoteinlineimages_never" = "Hiç bir zaman gösterme";
"displayremoteinlineimages_always" = "Her zaman göster";
"Auto save every" = "Yazdığım iletiyi şu aralıklarla kaydet";
"Display remote inline images" = "İletiye gömülü harici resimleri";
"displayremoteinlineimages_never" = "Gösterme";
"displayremoteinlineimages_always" = "Göster";
"Auto save every" = "Yazılan iletinin otomatik kaydedilme aralığı";
"minutes" = "dakika";
/* Contact */
"Personal Address Book" = "Kişisel Adres Defteri";
"Collected Address Book" = "Otomatik Toplanan Adresler";
"Collected Address Book" = "Otomatik Eklenen Adresler";
/* IMAP Accounts */
"Mail Account" = "E-posta Hesabı";
@@ -193,7 +193,8 @@
"(Click to create)" = "(Oluşturmak için tıklayın)";
"Please enter your signature below" = "Lütfen aşağıya imzanızı yazın";
"Please specify a valid sender address." = "Lütfen geçeril bir gönderici adresi belirtin.";
"Please specify a valid reply-to address." = "Lütfen geçeril bir yanıtlama adresi belirtin.";
"Please specify a valid reply-to address." = "Lütfen geçeril bir yanıt adresi belirtin.";
"Specify a hostname other than the local host" = "Yerel sunucu adından farklı bir sunucu adı belirtin";
/* Additional Parameters */
"Additional Parameters" = "Diğer Özellikler";
@@ -278,13 +279,13 @@
/* Return receipts */
"When I receive a request for a return receipt" = "Alındı bildirim isteği aldığımda";
"Never send a return receipt" = "Alındı bildirimi gönderme";
"Allow return receipts for some messages" = "Bazı iletiler için alındı bildirimi gönder";
"Never send a return receipt" = "Gönderme";
"Allow return receipts for some messages" = "Aşağıdaki durumlarda alındı bildirimi gönder";
"If I'm not in the To or Cc of the message" = "Eğer alıcı veya karbon kopya alanında değilsem";
"If the sender is outside my domain" = "Eğer gönderen adresi alan adımın dışında ise";
"In all other cases" = "diğer her koşullarda";
"Never send" = "Hiç bir zaman gönderme";
"Always send" = "Her zaman gönder";
"Never send" = "Gönderme";
"Always send" = "Gönder";
"Ask me" = "Sor";
/* Filters - UIxPreferences */
+9 -8
View File
@@ -1622,14 +1622,6 @@ static NSArray *reminderValues = nil;
userCanChangePassword];
}
//
// Used wox by template
//
- (NSString *) sieveFolderEncoding
{
return [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
}
// - (NSString *) localeCode
// {
// // WARNING : NSLocaleCode is not defined in <Foundation/NSUserDefaults.h>
@@ -1835,6 +1827,15 @@ static NSArray *reminderValues = nil;
return [self labelForKey: item];
}
- (NSDictionary *) languageLocale
{
WOResourceManager *rm;
rm = [self pageResourceManager];
return [rm localeForLanguageNamed: item];
}
//
// Used by wox template
//
@@ -72,22 +72,22 @@
"Forbidden" = "Yasaklanmış";
/* acls */
"Access rights to" = "Takvimin erişim yetkileri";
"For user" = "Aşağıdaki kullanıcı için";
"Access rights to" = "Takvim";
"For user" = "Erişen";
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
"Public Access" = "Herkese Açık Erişim";
"label_Public" = "Herkese Açık";
"label_Private" = "Kişisel";
"label_Confidential" = "Gizli";
"label_Viewer" = "Hepsini Görüntüle";
"label_Viewer" = "Hepsini Görüntüler";
"label_DAndTViewer" = "Günü ve Saati Görüntüler";
"label_Modifier" = "Değiştir";
"label_Responder" = "Yanıtlanan Kişi";
"label_Modifier" = "Değiştirir";
"label_Responder" = "Yanıtlar";
"label_None" = "Hiç biri";
"View All" = "Hepsini Görüntüler";
"View the Date & Time" = "Günü ve Saati Görüntüler";
"Modify" = "Değiştirir";
"Respond To" = "Yanıtlanan Kişi";
"Respond To" = "Yanıtlar";
"None" = "Hiç biri";
"This person can create objects in my calendar."
= "Bu kişi bu takvimimde öge oluşturabilir.";
@@ -100,7 +100,7 @@
"New calendar" = "Yeni Takvim";
"Name of the Calendar" = "Takvimin Adı";
"new" = "Yeni";
"Print view" = "Yazdırma görünümü";
"Print view" = "Yazdırma Görünümü";
"edit" = "Düzenle";
"delete" = "Sil";
"proposal" = "Teklif";
@@ -14,12 +14,14 @@
<main class="view"
layout="row" layout-fill="layout-fill"
ui-view="administration"
ng-controller="navController">
<!-- administration -->
</main>
ng-controller="navController"><!-- administration --></main>
<script type="text/ng-template" id="administration.html">
<md-sidenav id="left-sidenav" class="md-sidenav-left md-whiteframe-z1" md-component-id="left" md-is-locked-open="$mdMedia('gt-md')" layout="column">
<!-- Sidenav -->
<md-sidenav class="md-sidenav-left md-whiteframe-z1" layout="column"
md-component-id="left" md-is-locked-open="isGtMedium"
ng-class="{ 'sg-close': leftIsClose }">
<var:component className="UIxSidenavToolbarTemplate" />
<md-content class="md-flex" layout="column" md-scroll-y="md-scroll-y"
md-colors="::{ backgroundColor: 'default-background-300' }">
@@ -74,14 +74,10 @@
ng-class="{ 'sg-expanded': user.uid == acl.selectedUid }">
<a class="md-flex md-button" ng-click="acl.selectUser(user, $event)">
<div layout="row" layout-align="start center">
<span class="card-picture" ng-switch="user.userClass">
<div ng-switch-when="normal-user">
<sg-avatar-image class="md-avatar"
sg-email="user.c_email"
size="40"><!-- avatar --></sg-avatar-image>
</div>
<div ng-switch-when="normal-group" class="md-avatar sg-avatar-list"><!-- normal-group --></div>
<div ng-switch-when="public-user" class="md-avatar sg-avatar-list"><!-- public-user --></div>
<span class="card-picture">
<sg-avatar-image class="md-avatar"
sg-email="user.c_email"
size="40">{{ user.$avatarIcon }}</sg-avatar-image>
</span>
<div class="sg-tile-content">
<div class="sg-md-subhead"><div>{{user.cn}}</div></div>
@@ -26,7 +26,7 @@
ng-disabled="properties.addressbook.id == 'personal'"
ng-true-value="1"
ng-false-value="0">
<var:string label:value="Synchronize"/>
<var:string label:value="Synchronize"/> (Microsoft Enterprise ActiveSync)
</md-checkbox>
</md-dialog-content>
Loaded 100 of 237 files, more files were not shown because too many files have changed in this diff. Show more