Compare commits

...
165 Commits
Author SHA1 Message Date
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
Francis Lachapelle 277c6e901e Update ChangeLog 2016-12-01 12:02:23 -05:00
Francis Lachapelle 2c24901803 (js/css) Update generated files 2016-12-01 12:01:50 -05:00
Francis Lachapelle 8f90aa96fe (css,js) Update md to v1.1.1-master-b8357dc 2016-12-01 12:00:00 -05:00
Francis Lachapelle ee5b60c544 Preparation for release 3.2.4 2016-12-01 11:59:16 -05:00
Francis Lachapelle d2b94eba09 (js) Fix detection of session expiration 2016-12-01 11:56:07 -05:00
Francis Lachapelle c9467e1ce3 Validate custom subject for vacation auto-reply 2016-12-01 11:21:59 -05:00
Francis Lachapelle b4e9b7b60c (css) Fix sidenav background color of Preferences 2016-12-01 11:14:38 -05:00
Francis Lachapelle 66710f1b17 (i18n) Update translations 2016-12-01 11:07:28 -05:00
Francis Lachapelle a61ea24cdb (obj-c) Indentation 2016-12-01 10:28:26 -05:00
Francis Lachapelle 3e448d2c2c New sogo-tool cleanup user feature (cont'd) 2016-12-01 10:27:22 -05:00
Ludovic Marcotte 9aa00d049d Updated NEWS 2016-12-01 09:42:03 -05:00
Ludovic Marcotte fdea54f5e7 (fix) properly escape all GAL responses (fixes #3923) 2016-12-01 09:40:24 -05:00
InverseBot 3c098ceca5 (js/css) Update generated files 2016-12-01 01:18:40 -05:00
Francis Lachapelle f7dd835f92 Fix mail attribute when importing an LDIF file
Fixes #3878
2016-11-30 16:06:09 -05:00
Francis Lachapelle 7705c11cf7 New sogo-tool cleanup user feature 2016-11-30 15:32:29 -05:00
Francis Lachapelle 52cdb71d1f Add sort by start date for the tasks list
Fixes #3840
2016-11-30 12:13:48 -05:00
Francis Lachapelle a1746cd386 (css) Fix display of tabs with oversized content 2016-11-30 12:09:11 -05:00
InverseBot cd08ab81f1 (js/css) Update generated files 2016-11-30 02:47:47 -05:00
Francis Lachapelle d293fe79ed Add handling of BYSETPOS for BYDAY in repeat rule 2016-11-29 17:47:14 -05:00
Francis Lachapelle 6f732ba535 Fix MSExchangeFreeBusySOAPRequest.wod 2016-11-29 16:14:42 -05:00
Francis Lachapelle b27c868271 Fix freebusy data fetching of MS Exchange contacts 2016-11-29 15:48:15 -05:00
InverseBot e0b0a1d783 (js/css) Update generated files 2016-11-29 03:13:53 -05:00
Francis Lachapelle e059a6043e (js) Fix possible cross-origin exception
When SOGo is launched from an external link, either using a different
"target" or using window.open(), the Mail composer could throw a
JavaScript exception when trying to save or send a message that would
completely stop the operation.

Fixes #3900
2016-11-28 15:36:03 -05:00
Francis Lachapelle e253f5735d (js) Don't test pristine state of Message composer 2016-11-28 11:45:09 -05:00
Francis Lachapelle 328d67ec3a Improve sort validation in Contacts module 2016-11-28 10:33:38 -05:00
Francis Lachapelle c3376a4ddf Fix some compilation warnings 2016-11-28 10:14:23 -05:00
Francis Lachapelle 220fb3d607 (js) Update alarms after dragging an event 2016-11-28 09:37:31 -05:00
InverseBot fb873fcae9 (js/css) Update generated files 2016-11-26 02:52:49 -05:00
Ludovic Marcotte d2e03386d2 (fix) use GMT + adjust bias when querying Exchange's FB info 2016-11-25 15:14:24 -05:00
Francis Lachapelle 35a3a8ef1d Update ChangeLog 2016-11-25 10:57:26 -05:00
Francis Lachapelle 309846c894 (js/css) Update generated files 2016-11-25 10:52:36 -05:00
Francis Lachapelle f84d0e5f30 Preparation for release 3.2.3 2016-11-25 10:45:07 -05:00
InverseBot 9bb22bd67a (js/css) Update generated files 2016-11-25 02:43:18 -05:00
Francis Lachapelle dc7cb6792a (js) Improve alarms dialog
Fixes #3909
2016-11-24 19:14:02 -05:00
Ludovic Marcotte a76f8dca04 Updated NEWS 2016-11-24 16:07:47 -05:00
Ludovic Marcotte 06e92bbde5 Updated NEWS 2016-11-24 15:54:34 -05:00
Ludovic Marcotte 022c4bac98 (fix) wrong check for delete permission 2016-11-24 15:49:10 -05:00
Ludovic Marcotte 0e82802082 (fix) also filter out recurrence-id 2016-11-24 15:43:37 -05:00
Francis Lachapelle 0a4fa9aada (css) Update default theme 2016-11-24 15:09:59 -05:00
Ludovic Marcotte 71ca4a7b47 (fix) don't check Dovecot conditions if we're using IMAP annotations 2016-11-24 11:39:05 -05:00
Francis Lachapelle 2878665af4 Merge pull request #227 from andryyy/patch-5
Update SOGoACLGermanModificationAdvisory.wox
2016-11-24 06:34:27 -05:00
Francis Lachapelle 9f5a6707bb Merge pull request #226 from andryyy/patch-4
Update SOGoACLGermanRemovalAdvisory.wox
2016-11-24 06:33:52 -05:00
InverseBot 0e07844108 (js/css) Update generated files 2016-11-24 03:11:27 -05:00
André Peters 7c9d8b19b9 Update SOGoACLGermanModificationAdvisory.wox
Fix umlauts in messages like "... Zugang zu seinem Order "Persönlicher Kalender".
2016-11-24 08:50:43 +01:00
André Peters 1f9ef6fea7 Update SOGoACLGermanRemovalAdvisory.wox
Fix umlauts in messages like "... Zugang zu seinem Order "Persönlicher Kalender".
2016-11-24 08:50:42 +01:00
Ludovic Marcotte 836fdb1722 (feat) added photo support for LDAP-based address books 2016-11-23 16:30:41 -05:00
Francis Lachapelle cb960fae02 Restore attributes of base64-encoded img tags
Fixes #3814
2016-11-23 14:57:24 -05:00
Francis Lachapelle b8df16cd8c Improve SOGoDefaults.plist to avoid warning 2016-11-23 14:52:10 -05:00
Francis Lachapelle 84e591cdb9 (js) Update CKEditor to version 4.6.0 2016-11-23 13:57:49 -05:00
262 changed files with 8918 additions and 4416 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];
+12 -3
View File
@@ -367,7 +367,12 @@ 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];
}
// Update syncCache
@@ -588,7 +593,11 @@ 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];
else
[sogoObject saveComponent: o];
if ([syncCache objectForKey: serverId])
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
@@ -748,7 +757,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
roles = [theCollection aclsForUser:[[context activeUser] login]];
// We check ACLs on the collection and not on the SOGo object itself, as the add/delete rights are on the collection itself
if (![roles containsObject: SOGoRole_ObjectEraser] || ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
if (![roles containsObject: SOGoRole_ObjectEraser] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
{
// This will trigger an add-command to re-add the component to the client
[sogoObject touch];
+55 -37
View File
@@ -947,6 +947,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 +1098,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 +1817,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 +2656,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 +2703,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 +2727,8 @@ void handle_eas_terminate(int signum)
[s appendFormat: @"%c", c];
}
[s appendString: @"</MergedFreeBusy>"];
[s appendString: @"</Availability>"];
[s appendString: @"</Recipient>"];
[s appendString: @"</Response>"];
}
@@ -2826,31 +2825,31 @@ void handle_eas_terminate(int signum)
[s appendString: @"<Properties>"];
if ((o = [contact objectForKey: @"displayname"]))
[s appendFormat: @"<DisplayName xmlns=\"Gal:\">%@</DisplayName>", o];
[s appendFormat: @"<DisplayName xmlns=\"Gal:\">%@</DisplayName>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"title"]))
[s appendFormat: @"<Title xmlns=\"Gal:\">%@</Title>", o];
[s appendFormat: @"<Title xmlns=\"Gal:\">%@</Title>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"givenname"]))
[s appendFormat: @"<FirstName xmlns=\"Gal:\">%@</FirstName>", o];
[s appendFormat: @"<FirstName xmlns=\"Gal:\">%@</FirstName>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"sn"]))
[s appendFormat: @"<LastName xmlns=\"Gal:\">%@</LastName>", o];
[s appendFormat: @"<LastName xmlns=\"Gal:\">%@</LastName>", [o activeSyncRepresentationInContext: context]];
if ([current_mail length] > 0)
[s appendFormat: @"<EmailAddress xmlns=\"Gal:\">%@</EmailAddress>", current_mail];
if ((o = [contact objectForKey: @"telephonenumber"]))
[s appendFormat: @"<Phone xmlns=\"Gal:\">%@</Phone>", o];
[s appendFormat: @"<Phone xmlns=\"Gal:\">%@</Phone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"homephone"]))
[s appendFormat: @"<HomePhone xmlns=\"Gal:\">%@</HomePhone>", o];
[s appendFormat: @"<HomePhone xmlns=\"Gal:\">%@</HomePhone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"mobile"]))
[s appendFormat: @"<MobilePhone xmlns=\"Gal:\">%@</MobilePhone>", o];
[s appendFormat: @"<MobilePhone xmlns=\"Gal:\">%@</MobilePhone>", [o activeSyncRepresentationInContext: context]];
if ((o = [contact objectForKey: @"o"]))
[s appendFormat: @"<Company xmlns=\"Gal:\">%@</Company>", o];
[s appendFormat: @"<Company xmlns=\"Gal:\">%@</Company>", [o activeSyncRepresentationInContext: context]];
[s appendString: @"</Properties>"];
[s appendString: @"</Result>"];
@@ -3167,6 +3166,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;
}
//
//
//
@@ -3296,20 +3317,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 +4001,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 +4064,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"];
+1 -1
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";
+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);
}
+1735
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 -1
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
@@ -1232,6 +1237,7 @@ mapping = {
2+h|Other
|Birthday |birthyear-birthmonth-birthday
|Note |description
|Photo |photo
|===
Authenticating using C.A.S.
+3 -3
View File
@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 3.2.2 - November 2016</releaseinfo>
<subtitle>for version 3.2.2</subtitle>
<date>2016-11-23</date>
<releaseinfo>Version 3.2.5 - January 2017</releaseinfo>
<subtitle>for version 3.2.5</subtitle>
<date>2017-01-10</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.2
:release_version: 3.2.5
// 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;
+85 -1
View File
@@ -1,10 +1,94 @@
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 attemps 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)
------------------
New features
- [core] new sogo-tool cleanup user feature
Enhancements
- [core] added handling of BYSETPOS for BYDAY in recurrence rules
- [web] added sort by start date for tasks (#3840)
Bug fixes
- [web] fixed JavaScript exception when SOGo is launched from an external link (#3900)
- [web] restored fetching of freebusy information of MS Exchange contacts
- [web] fixed mail attribute when importing an LDIF file (#3878)
- [web] don't save empty custom auto-reply subject
- [web] fixed detection of session expiration
- [eas] properly escape all GAL responses (#3923)
3.2.3 (2016-11-25)
------------------
New features
- [core] added photo support for LDAP-based address books (#747, #2184)
Enhancements
- [web] updated CKEditor to version 4.6.0
- [web] updated Angular to version 1.5.9
Bug fixes
- [web] restore attributes when rewriting base64-encoded img tags (#3814)
- [web] improve alarms dialog (#3909)
- [eas] fixed EAS delete operation
3.2.2 (2016-11-23)
------------------
New features
- [core] support repetitive email alarms on tasks and events (#1053)
- [web] allow to hide center column on large screens (#3861)
- [eas] relaxed permission requirements for subscription synchronizations (#3118 and #3180)
- [eas] relaxed permission requirements for subscription synchronizations (#3118, #3180)
Enhancements
- [core] added sha256-crypt and sha512-crypt password support
+2
View File
@@ -123,6 +123,7 @@
- (NSException *) deleteContentWithName: (NSString *) _name;
- (NSException *) deleteAllContent;
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days;
- (NSException *) touchContentWithName: (NSString *) _name;
- (NSException *) deleteFolder;
@@ -144,6 +145,7 @@
- (void) deleteAclWithSpecification: (EOFetchSpecification *) _fs;
- (unsigned int) recordsCountByExcludingDeleted: (BOOL) includeDeleted;
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days;
- (NSCalendarDate *) lastModificationDate;
+162 -70
View File
@@ -68,7 +68,7 @@ static GCSStringFormatter *stringFormatter = nil;
+ (void) initialize
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"];
doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
@@ -91,7 +91,7 @@ static GCSStringFormatter *stringFormatter = nil;
NSEnumerator *fields;
GCSFieldInfo *field;
NSString *fieldName;
if ((self = [super init])) {
folderManager = [_fm retain];
folderInfo = [_ftype retain];
@@ -114,7 +114,7 @@ static GCSStringFormatter *stringFormatter = nil;
if (![fieldName isEqualToString: @"c_name"])
[contentFieldNames addObject: fieldName];
}
folderId = [_folderId copy];
folderName = [[_path lastPathComponent] copy];
path = [_path copy];
@@ -124,7 +124,7 @@ static GCSStringFormatter *stringFormatter = nil;
folderTypeName = [_ftname copy];
ofFlags.requiresFolderSelect = 0;
ofFlags.sameTableForQuick =
ofFlags.sameTableForQuick =
[location isEqualTo:quickLocation] ? 1 : 0;
}
return self;
@@ -133,7 +133,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (id) init
{
return [self initWithPath:nil primaryKey:nil
folderTypeName:nil folderType:nil
folderTypeName:nil folderType:nil
location:nil quickLocation:nil
aclLocation:nil
folderManager:nil];
@@ -271,13 +271,13 @@ static GCSStringFormatter *stringFormatter = nil;
NSDictionary *ui;
ui = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:_base],
[NSNumber numberWithUnsignedInt:_base],
@"GCSExpectedVersion",
[NSNumber numberWithUnsignedInt:_store],
@"GCSStoredVersion",
self, @"GCSFolder",
nil];
return [NSException exceptionWithName:@"GCSVersionMismatch"
reason:@"Transaction conflict during a GCS modification."
userInfo:ui];
@@ -369,7 +369,7 @@ static GCSStringFormatter *stringFormatter = nil;
requirement: (GCSTableRequirement) requirement
{
NSString *selectedFields;
if (requirement == bothTableRequired && [fields containsObject: @"c_name"])
selectedFields = [self _dottedFields: fields];
else
@@ -381,7 +381,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (NSString *) _sqlForQualifier: (EOQualifier *) qualifier
{
NSMutableString *ms;
if (qualifier)
{
ms = [NSMutableString stringWithCapacity:32];
@@ -399,7 +399,7 @@ static GCSStringFormatter *stringFormatter = nil;
if ((count = [_so count]) == 0)
return nil;
sql = [NSMutableString stringWithCapacity:(count * 16)];
for (i = 0; i < count; i++) {
EOSortOrdering *so;
@@ -409,9 +409,9 @@ static GCSStringFormatter *stringFormatter = nil;
so = [_so objectAtIndex:i];
sel = [so selector];
column = [so key];
if (i > 0) [sql appendString:@", "];
if (sel_eq(sel, EOCompareAscending)) {
[sql appendString:column];
[sql appendString:@" ASC"];
@@ -558,21 +558,21 @@ static GCSStringFormatter *stringFormatter = nil;
error = [channel evaluateExpressionX:sql];
if (error)
{
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
results = nil;
}
else
{
/* fetch results */
results = [NSMutableArray arrayWithCapacity: 64];
attrs = [channel describeResults: NO /* do not beautify names */];
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
[results addObject: row];
/* release channels */
}
[self releaseChannel: channel];
// NSLog(@"/running query");
@@ -582,7 +582,7 @@ static GCSStringFormatter *stringFormatter = nil;
[self errorWithFormat:@" could not open storage channel!"];
results = nil;
}
return results;
}
@@ -764,7 +764,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute;
id value;
unsigned i, count;
if (_row == nil || _table == nil)
return nil;
@@ -774,7 +774,7 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@"UPDATE "];
[sql appendString:_table];
[sql appendString:@" SET "];
for (i = 0, count = [keys count]; i < count; i++) {
fieldName = [keys objectAtIndex:i];
@@ -823,7 +823,7 @@ andAttribute: (EOAttribute *)_attribute
{
EOAttribute *attribute;
EOEntity *entity;
entity = AUTORELEASE([[EOEntity alloc] init]);
[entity setName: _name];
[entity setExternalName: _name];
@@ -841,7 +841,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute;
EOEntity *entity;
entity = [self _entityWithName: [self storeTableName]];
entity = [self _entityWithName: [self storeTableName]];
attribute = AUTORELEASE([[EOAttribute alloc] init]);
[attribute setName: @"c_version"];
@@ -873,7 +873,7 @@ andAttribute: (EOAttribute *)_attribute
- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname
isEqualTo:(id)_value
andColumn:(NSString *)_colname2
andColumn:(NSString *)_colname2
isEqualTo:(id)_value2
entity: (EOEntity *)_entity
withAdaptor: (EOAdaptor *)_adaptor
@@ -970,7 +970,7 @@ andAttribute: (EOAttribute *)_attribute
NSArray *rows;
NSException *error;
/* check preconditions */
/* check preconditions */
if (_name)
{
if (_content)
@@ -979,10 +979,10 @@ andAttribute: (EOAttribute *)_attribute
error = nil;
nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
rows = [self fetchFields: [NSArray arrayWithObjects:
@"c_version",
@"c_deleted",
@@ -1035,16 +1035,16 @@ andAttribute: (EOAttribute *)_attribute
if (quickRow)
{
[quickRow setObject:_name forKey:@"c_name"];
if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow];
/* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow];
[contentRow setObject:_name forKey:@"c_name"];
[contentRow setObject:now forKey:@"c_lastmodified"];
if (isNewRecord)
@@ -1058,7 +1058,7 @@ andAttribute: (EOAttribute *)_attribute
[NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"];
[contentRow setObject:_content forKey:@"c_content"];
/* open channels */
storeChannel = [self acquireStoreChannel];
if (storeChannel)
@@ -1080,14 +1080,14 @@ andAttribute: (EOAttribute *)_attribute
/* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll
be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate]
hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[quickChannel adaptorContext] beginTransaction];
[[storeChannel adaptorContext] beginTransaction];
quickTableEntity = [self _quickTableEntity];
storeTableEntity = [self _storeTableEntity];
@@ -1097,10 +1097,10 @@ andAttribute: (EOAttribute *)_attribute
error = (hasInsertDelegate
? [quickChannel insertRowX: quickRow forEntity: quickTableEntity]
: [quickChannel
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
adaptor: [[quickChannel adaptorContext] adaptor]
tableName: [self quickTableName]]]);
if (!error)
error = (hasInsertDelegate
? [storeChannel insertRowX: contentRow forEntity: storeTableEntity]
@@ -1138,7 +1138,7 @@ andAttribute: (EOAttribute *)_attribute
andColumn: (*_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (*_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: *_baseVersion] : (NSNumber *)nil)]]);
}
if (error)
{
[[storeChannel adaptorContext] rollbackTransaction];
@@ -1193,19 +1193,19 @@ andAttribute: (EOAttribute *)_attribute
NSException *error;
NSString *delsql;
NSCalendarDate *nowDate;
/* check preconditions */
if (_name == nil) {
return [NSException exceptionWithName:@"GCSDeleteException"
reason:@"no content filename was provided"
userInfo:nil];
}
if (doLogStore)
[self logWithFormat:@"should delete content: '%@'", _name];
/* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open storage channel!"];
return nil;
@@ -1242,7 +1242,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete content '%@': %@",
@"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
}
else if (!ofFlags.sameTableForQuick) {
@@ -1256,19 +1256,19 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat:
@"%s: cannot delete quick row '%@': %@",
@"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, delsql, error];
/*
/*
Note: we now have a "broken" record, needs to be periodically GCed by
a script!
*/
}
}
/* release channels and return */
[adaptorCtx commitTransaction];
[self releaseChannel:storeChannel];
if (!ofFlags.sameTableForQuick) {
[[quickChannel adaptorContext] commitTransaction];
[self releaseChannel:quickChannel];
@@ -1307,7 +1307,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self storeTableName]];
error = [storeChannel evaluateExpressionX:query];
if (error)
[self errorWithFormat: @"%s: cannot delete content '%@': %@",
[self errorWithFormat: @"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, query, error];
else if (!ofFlags.sameTableForQuick) {
/* content row deleted, now delete the quick row */
@@ -1317,7 +1317,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self quickTableName]];
error = [quickChannel evaluateExpressionX: query];
if (error)
[self errorWithFormat: @"%s: cannot delete quick row '%@': %@",
[self errorWithFormat: @"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, query, error];
}
@@ -1333,6 +1333,49 @@ andAttribute: (EOAttribute *)_attribute
return error;
}
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days
{
NSString *delSql, *table;
EOAdaptorContext *adaptorCtx;
EOAdaptorChannel *channel;
NSCalendarDate *nowDate;
unsigned int delta;
channel = [self acquireStoreChannel];
if ((channel = [self acquireStoreChannel]) == nil)
return [NSException exceptionWithName: @"GCSChannelException"
reason: @"could not open storage channel!"
userInfo: nil];
adaptorCtx = [channel adaptorContext];
[adaptorCtx beginTransaction];
table = [self storeTableName];
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta > [nowDate timeIntervalSince1970])
return [NSException exceptionWithName: @"GCSArgumentException"
reason: @"number of days is too high!"
userInfo: nil];
delta = [nowDate timeIntervalSince1970] - delta;
if ([GCSFolderManager singleStoreMode])
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_folder_id = %@ AND c_deleted = 1 AND c_lastmodified < %u",
table, folderId, delta];
else
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_deleted = 1 AND c_lastmodified < %u",
table, delta];
[channel evaluateExpressionX: delSql];
[[channel adaptorContext] commitTransaction];
[self releaseChannel: channel];
return nil;
}
- (NSException *) touchContentWithName: (NSString *) _name
{
NSString *touchSql, *table;
@@ -1389,14 +1432,14 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *channel;
NSString *delsql;
NSString *table;
/* open channels */
if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open channel!"];
return nil;
}
/* delete rows */
[[channel adaptorContext] beginTransaction];
table = [self storeTableName];
@@ -1423,7 +1466,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql];
}
[[channel adaptorContext] commitTransaction];
[self releaseChannel:channel];
@@ -1437,7 +1480,7 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *quickChannel;
EOAdaptorContext *adaptorCtx;
NSException *error;
quickChannel = [self acquireQuickChannel];
adaptorCtx = [quickChannel adaptorContext];
[adaptorCtx beginTransaction];
@@ -1500,15 +1543,15 @@ andAttribute: (EOAttribute *)_attribute
NSArray *attrs;
NSMutableArray *results;
NSDictionary *row;
qualifier = [_fs qualifier];
sortOrderings = [_fs sortOrderings];
#if 0
[self logWithFormat:@"FETCH: %@", _flds];
[self logWithFormat:@" MATCH: %@", _q];
#endif
/* generate SQL */
sql = [NSMutableString stringWithCapacity:256];
@@ -1536,34 +1579,34 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@" LIMIT "]; // count
[sql appendString:@" OFFSET "]; // index from 0
#endif
/* open channel */
if ((channel = [self acquireAclChannel]) == nil) {
[self errorWithFormat:@"could not open acl channel!"];
return nil;
}
/* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
results = [NSMutableArray arrayWithCapacity:64];
attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[results addObject:row];
/* release channels */
[self releaseChannel: channel];
return results;
}
@@ -1602,7 +1645,7 @@ andAttribute: (EOAttribute *)_attribute
NSException *error;
NSMutableString *sql;
NSString *qSql;
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"DELETE FROM "];
[sql appendString:[self aclTableName]];
@@ -1620,16 +1663,16 @@ andAttribute: (EOAttribute *)_attribute
[self errorWithFormat:@"could not open acl channel!"];
return;
}
/* run SQL */
[[channel adaptorContext] beginTransaction];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return;
}
[[channel adaptorContext] commitTransaction];
[self releaseChannel:channel];
}
@@ -1647,11 +1690,11 @@ andAttribute: (EOAttribute *)_attribute
if ([GCSFolderManager singleStoreMode])
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@ WHERE c_folder_id = %@",
@"SELECT COUNT(*) AS cnt FROM %@ WHERE c_folder_id = %@",
[self storeTableName], folderId];
else
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@",
@"SELECT COUNT(*) AS cnt FROM %@",
[self storeTableName]];
if (excludeDeleted)
{
@@ -1666,7 +1709,56 @@ andAttribute: (EOAttribute *)_attribute
{
error = [channel evaluateExpressionX: sqlString];
if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error];
else
{
attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL];
count = [[row objectForKey: @"cnt"] unsignedIntValue];
[channel cancelFetch];
}
[self releaseChannel: channel];
}
return count;
}
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days
{
EOAdaptorChannel *channel;
NSArray *attrs;
NSCalendarDate *nowDate;
NSDictionary *row;
NSException *error;
NSMutableString *sqlString;
unsigned int delta, count;
count = 0;
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta < [nowDate timeIntervalSince1970])
delta = [nowDate timeIntervalSince1970] - delta;
else
delta = 0;
if ([GCSFolderManager singleStoreMode])
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS cnt FROM %@"
@" WHERE c_folder_id = %@ AND c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], folderId, delta];
else
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS cnt FROM %@"
@" WHERE c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], delta];
channel = [self acquireStoreChannel];
if (channel)
{
error = [channel evaluateExpressionX: sqlString];
if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error];
else
{
@@ -1716,7 +1808,7 @@ andAttribute: (EOAttribute *)_attribute
- (NSString *)description {
NSMutableString *ms;
id tmp;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
@@ -1729,7 +1821,7 @@ andAttribute: (EOAttribute *)_attribute
if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
if ((tmp = [self location]))
[ms appendFormat:@" loc=%@", [tmp absoluteString]];
[ms appendString:@">"];
return ms;
}
+1 -3
View File
@@ -1,8 +1,6 @@
/* NGVCardPhoto.h - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
+1 -3
View File
@@ -1,8 +1,6 @@
/* NGVCardPhoto.m - this file is part of NGCards
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* NGCards is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
+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
+8 -1
View File
@@ -466,9 +466,16 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
- (iCalByDayMask *) byDayMask
{
NSArray *setPos;
NSString *day;
if (dayMask == nil && [[self byDay] length])
{
dayMask = [iCalByDayMask byDayMaskWithRuleString: [self byDay]];
day = [self byDay];
setPos = [self bySetPos];
if ([setPos count])
day = [NSString stringWithFormat: @"%@%@", [setPos lastObject], day];
dayMask = [iCalByDayMask byDayMaskWithRuleString: day];
[dayMask retain];
}
+1 -1
View File
@@ -1,6 +1,6 @@
/* MSExchangeFreeBusy.m - this file is part of SOGo
*
* Copyright (C) 2012-2014 Inverse inc.
* Copyright (C) 2012-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,5 +1,5 @@
/*
Copyright (C) 2012 Inverse inc.
Copyright (C) 2012-2016 Inverse inc.
This file is part of SOGo.
@@ -1,5 +1,5 @@
/*
Copyright (C) 2012 Inverse inc.
Copyright (C) 2012-2016 Inverse inc.
This file is part of SOGo.
@@ -28,6 +28,8 @@
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import "MSExchangeFreeBusySOAPRequest.h"
@@ -110,4 +112,19 @@
return [NSString stringWithFormat: @"%i", interval];
}
- (NSString *) bias
{
#if 0
NSTimeZone *userTimeZone;
NSInteger secs;
userTimeZone = [[[context activeUser] userDefaults] timeZone];
secs = [userTimeZone secondsFromGMT];
return [NSString stringWithFormat: @"%i", (secs/60)];
#else
return @"0";
#endif
}
@end
@@ -7,21 +7,21 @@
<GetUserAvailabilityRequest xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<t:TimeZone xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
<Bias>0</Bias>
<StandardTime>
<Bias>0</Bias>
<Time>00:00:00</Time>
<DayOrder>1</DayOrder>
<Month>1</Month>
<DayOfWeek>Sunday</DayOfWeek>
</StandardTime>
<DaylightTime>
<Bias>0</Bias>
<Time>00:00:00</Time>
<DayOrder>1</DayOrder>
<Month>1</Month>
<DayOfWeek>Sunday</DayOfWeek>
</DaylightTime>
<Bias><#bias/></Bias>
<StandardTime>
<Bias>0</Bias>
<Time>02:00:00</Time>
<DayOrder>1</DayOrder>
<Month>11</Month>
<DayOfWeek>Sunday</DayOfWeek>
</StandardTime>
<DaylightTime>
<Bias>-60</Bias>
<Time>02:00:00</Time>
<DayOrder>2</DayOrder>
<Month>3</Month>
<DayOfWeek>Sunday</DayOfWeek>
</DaylightTime>
</t:TimeZone>
<MailboxDataArray>
<t:MailboxData>
@@ -3,6 +3,11 @@ serverVersion: WOString {
escapeHTML = NO;
}
bias: WOString {
value = bias;
escapeHTML = NO;
}
address: WOString {
value = address;
escapeHTML = NO;
+25 -14
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)
{
@@ -976,7 +982,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSCalendarDate *recurrenceId, *masterEndDate, *endDate;
NSMutableDictionary *newRecord;
NGCalendarDateRange *newRecordRange;
NSComparisonResult compare;
NSNumber *dateSecs;
id master;
@@ -1437,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 */
@@ -2620,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)
@@ -3238,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];
@@ -3317,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];
@@ -3401,8 +3413,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
timezone: timezone]))
{
imported++;
[uids setValue: uid
forKey: originalUid];
[uids setValue: uid forKey: originalUid];
}
}
+212 -131
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.
@@ -823,11 +870,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 +931,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 +1011,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 +1216,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 +1237,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 +1320,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 +1340,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 +1527,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 +1608,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 +1619,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 +1648,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 +1668,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 +1857,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 +1994,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 +2028,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[self _adjustEventsInRequestCalendar: calendar];
[self adjustClassificationInRequestCalendar: calendar];
[self _adjustPartStatInRequestCalendar: calendar];
[self _adjustFloatingTimeInRequestCalendar: calendar];
}
//
@@ -1984,13 +2039,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 +2055,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 +2207,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 +2214,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
@@ -232,7 +232,7 @@
type, classification]
inContext: context];
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", nil];
tags = [NSArray arrayWithObjects: @"DTSTAMP", @"DTSTART", @"DTEND", @"DUE", @"EXDATE", @"EXRULE", @"RRULE", @"RECURRENCE-ID", nil];
uid = [[component uid] asCryptedPassUsingScheme: @"ssha256"
withSalt: [[settings userSalt] dataUsingEncoding: NSASCIIStringEncoding]
andEncoding: encHex];
@@ -32,10 +32,6 @@
+ (id) sharedEMailAlarmsManager;
/* PUT */
- (void) handleAlarmsInCalendar: (iCalCalendar *) calendar
fromComponent: (SOGoCalendarComponent *) component;
/* DELETE */
- (void) deleteAlarmsFromComponent: (SOGoCalendarComponent *) component;
@@ -25,6 +25,8 @@
#import <NGExtensions/NSObject+Logs.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
@@ -34,6 +36,9 @@
#import <GDLContentStore/GCSFolder.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentFolders.h"
#import "SOGoCalendarComponent.h"
+1
View File
@@ -21,6 +21,7 @@
#import <Foundation/NSCalendarDate.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -35,9 +35,9 @@ vtodo_class2 = "(Gizli görev)";
= "%{Organizer} %{SentByText}şu etkinliği iptal etti: %{Summary}.\n\nBaşlangıç: %{StartDate} saat %{StartTime}\nBitiş: %{EndDate} saat %{EndTime}\nTanım: %{Description}";
/* Update */
"The appointment \"%{Summary}\" for the %{OldStartDate} has changed"
= "\"%{Summary}\" isimli, %{OldStartDate} tarihli çalışma değişti";
= "%{OldStartDate} tarihindeki \"%{Summary}\" çalışması değişti";
"The appointment \"%{Summary}\" for the %{OldStartDate} at %{OldStartTime} has changed"
= "\"%{Summary}\" isimli, %{OldStartDate} tarihli ve %{OldStartTime} saatli çalışma değişti";
= " %{OldStartDate} tarihli ve %{OldStartTime} başlangıç saatli \"%{Summary}\" çalışması değişti";
"The following parameters have changed in the \"%{Summary}\" meeting:"
= "\"%{Summary}\" buluşmasının şu özellikleri değişti:";
"Please accept or decline those changes."
@@ -45,16 +45,16 @@ vtodo_class2 = "(Gizli görev)";
/* Reply */
"Accepted invitation: \"%{Summary}\"" = "Davet kabul edilidi: \"%{Summary}\"";
"Declined invitation: \"%{Summary}\"" = "Davet Geri Çevrildi: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Temsilci atanmış davet: \"%{Summary}\"";
"Delegated invitation: \"%{Summary}\"" = "Vekalet edilen davet: \"%{Summary}\"";
"Not yet decided on invitation: \"%{Summary}\"" = "Davet hakkında henüz karar verilmedi: \"%{Summary}\"";
"%{Attendee} %{SentByText}has accepted your event invitation."
= "%{Attendee} %{SentByText}etkinlik davetinizi kabul etti.";
"%{Attendee} %{SentByText}has declined your event invitation."
= "%{Attendee} %{SentByText}davetinizi reddetti.";
"%{Attendee} %{SentByText}has delegated the invitation to %{Delegate}."
= "%{Attendee} %{SentByText} davete temsilci atadı: %{Delegate} ";
= "%{Attendee} %{SentByText} tarafından %{Delegate} davete vekil olarak atadı.";
"%{Attendee} %{SentByText}has not yet decided upon your event invitation."
= "%{Attendee} %{SentByText}henüz etkinlik davetinizle ilgili kararını vermedi.";
/* Resources */
"Cannot access resource: \"%{Cn} %{SystemEmail}\"" = "Kaynağa ulaşılamıyor: \"%{Cn} %{SystemEmail}\"";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" kanağı için maksimum eşzamanlı kayıt adedine (%{NumberOfSimultaneousBookings}) ulaşıldı. Çakışan \"%{EventTitle}\" etkinlik, %{StartDate} tarihinde başlıyor.";
"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}." = "\"%{Cn} %{SystemEmail}\" kaynağı için maksimum eşzamanlı kayıt adedine (%{NumberOfSimultaneousBookings}) ulaşıldı. Çakışan \"%{EventTitle}\" etkinlik, %{StartDate} tarihinde başlıyor.";
@@ -140,7 +140,7 @@ NSNumber *iCalDistantFutureNumber = nil;
[currentAttendee rfc822Email], @"email",
([[currentAttendee cnWithoutQuotes] length] ? [currentAttendee cnWithoutQuotes] : [currentAttendee rfc822Email]) , @"name",
nil];
if ((uid = [currentAttendee uid]))
if ((uid = [currentAttendee uidInContext: context]))
{
[attendeeData setObject: uid forKey: @"uid"];
}
@@ -156,12 +156,12 @@ NSNumber *iCalDistantFutureNumber = nil;
if ([source conformsToProtocol: @protocol (SOGoDNSource)] &&
[[(NSObject <SOGoDNSource>*) source MSExchangeHostname] length])
{
uid = [NSString stringWithFormat: @"%@:%@", [[context activeUser] login],
[contactData valueForKey: @"c_uid"]];
[attendeeData setObject: uid forKey: @"uid"];
[attendeeData setObject: [NSNumber numberWithInt: 1] forKey: @"isMSExchange"];
[attendeeData setObject: [contactData valueForKey: @"c_uid"] forKey: @"uid"];
}
}
}
[attendeeData setObject: [[currentAttendee partStat] lowercaseString] forKey: @"partstat"];
[attendeeData setObject: [[currentAttendee role] lowercaseString] forKey: @"role"];
if ([[currentAttendee delegatedTo] length])
@@ -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;
+12
View File
@@ -21,6 +21,7 @@
#import <Foundation/NSTimeZone.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGCards/NSArray+NGCards.h>
@@ -235,12 +236,19 @@ 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:]
[self addElementWithTag: @"email"
ofType: @"work"
withValue: [ldifRecord objectForKey: @"c_emails"]];
// When importing an LDIF file, add the default mail attribute
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"]
@@ -358,6 +366,10 @@ convention:
else
[self setCategories: [o componentsSeparatedByString: @","]];
// Photo
if ([ldifRecord objectForKey: @"photo"])
[self setPhoto: [[ldifRecord objectForKey: @"photo"] stringByEncodingBase64]];
[self cleanupEmptyChildren];
}
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSDictionary+LDIF.h - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSDictionary+LDIF.m - this file is part of SOGo
*
* Copyright (C) 2011-2012 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSString+LDIF.h - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* NSString+LDIF.m - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoContactEntryPhoto.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+15 -8
View File
@@ -1,8 +1,6 @@
/* SOGoContactEntryPhoto.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,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;
+1 -1
View File
@@ -1,6 +1,6 @@
/* SOGoContactGCSEntry.h - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+2 -2
View File
@@ -1,6 +1,6 @@
/* SOGoContactGCSEntry.h - this file is part of SOGo
/* SOGoContactGCSEntry.m - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+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
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoContactGCSList.h - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1 -2
View File
@@ -1,7 +1,6 @@
/* SOGoContactLDIFEntry.h - this file is part of SOGo
* Copyright (C) 2006-2011 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+24 -2
View File
@@ -1,6 +1,6 @@
/* SOGoContactLDIFEntry.m - this file is part of SOGo
*
* Copyright (C) 2006-2014 Inverse inc.
* Copyright (C) 2006-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
#import <SOGo/SOGoPermissions.h>
#import "NGVCard+SOGo.h"
#import "SOGoContactEntryPhoto.h"
#import "SOGoContactGCSEntry.h"
#import "SOGoContactLDIFEntry.h"
#import "SOGoContactSourceFolder.h"
@@ -141,7 +142,7 @@
- (BOOL) hasPhoto
{
return NO;
return ([ldifEntry objectForKey: @"photo"] != nil);
}
- (NSString *) davEntityTag
@@ -227,4 +228,25 @@
return [newContact saveComponent: newCard];
}
- (id) lookupName: (NSString *) lookupName
inContext: (id) localContext
acquire: (BOOL) acquire
{
id obj;
if ([lookupName isEqualToString: @"photo"])
{
if ([self hasPhoto])
obj = [SOGoContactEntryPhoto objectWithName: lookupName
inContainer: self];
else
obj = nil;
}
else
obj = [super lookupName: lookupName inContext: localContext
acquire: acquire];
return obj;
}
@end
@@ -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 -3
View File
@@ -1,8 +1,6 @@
/* SOGoUserFolder+Appointments.h - this file is part of SOGo
*
* Copyright (C) 2009 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2009-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
+1
View File
@@ -33,6 +33,7 @@
fromIndex: (int) start;
- (int) indexOf: (unichar) _c;
- (NSString *) decodedHeader;
- (NSString *) asSafeFilename;
@end
+50 -15
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;
@@ -289,7 +289,7 @@
//
if ([value length] > 5 && [[value substringToIndex: 5] caseInsensitiveCompare: @"data:"] == NSOrderedSame)
{
NSString *uniqueId, *mimeType, *encoding;
NSString *uniqueId, *mimeType, *encoding, *attrName;
NGMimeBodyPart *bodyPart;
NGMutableHashMap *map;
NSData *data;
@@ -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,17 +335,30 @@
[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];
[result appendFormat: @"<img src=\"cid:%@\" type=\"%@\"", uniqueId, mimeType];
// Restore img attributes
for (i = 0; i < [attributes count]; i++)
{
attrName = [[attributes rawNameAtIndex: i] lowercaseString];
if (![attrName isEqualToString: @"src"] && ![attrName isEqualToString: @"type"])
{
value = [attributes valueAtIndex: i];
[result appendFormat: @" %@=\"%@\"", attrName, value];
}
}
[result appendString: @"/>"];
}
}
else if (voidTags)
@@ -643,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
@@ -671,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;
+14 -3
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];
}
@@ -797,8 +807,9 @@ static NSString *inboxFolderName = @"INBOX";
// * LIST (\NonExistent \HasChildren) "/" shared
// * LIST (\NonExistent \HasChildren) "/" shared/jdoe@example.com
// * LIST (\HasNoChildren) "/" shared/jdoe@example.com/INBOX
else if (([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound &&
[[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound))
else if (!hasAnnotatemore &&
([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound &&
[[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound))
guid = [NSString stringWithFormat: @"%@", currentFolder];
else
{
+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 */
+247 -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)
@@ -913,13 +917,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 +1092,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 +1133,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 +1223,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 +1245,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 +1289,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 +1339,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 +1348,7 @@ static BOOL debugSoParts = NO;
}
return error; /* return 304 or 416 */
}
content = [self content];
if ([content isKindOfClass:[NSException class]])
return content;
@@ -1198,7 +1356,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 +1415,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 +1437,7 @@ static BOOL debugSoParts = NO;
NSException *error;
// TODO: check for safe HTTP method
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
return error;
}
@@ -1287,15 +1445,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 +1462,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 +1487,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 +1502,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 +1516,7 @@ static BOOL debugSoParts = NO;
/* value qualifies */
if (![(NSString *)_value hasPrefix:_prefix])
return nil;
/* unquote */
if ([_value characterAtIndex:0] == '<') {
r = [_value rangeOfString: @">"];
@@ -1456,7 +1614,7 @@ static BOOL debugSoParts = NO;
if (property)
{
parts = [NSArray arrayWithObject: property];
msgs = [self fetchParts: parts];
msgs = [msgs valueForKey: @"fetch"];
if ([msgs count]) {
@@ -1512,7 +1670,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 +1755,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])
{
+8 -3
View File
@@ -1211,7 +1211,6 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
{
ldapConnection = [self _ldapConnection];
qualifier = [self _qualifierForFilter: match];
// attributes = [self _searchAttributes];
attributes = [NSArray arrayWithObject: @"*"];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
@@ -1237,10 +1236,9 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) qualifier
{
NGLdapConnection *ldapConnection;
NSArray *attributes;
NSEnumerator *entries;
NSArray *attributes;
// attributes = [self _searchAttributes];
ldapConnection = [self _ldapConnection];
attributes = [NSArray arrayWithObject: @"*"];
@@ -1307,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]
+1 -1
View File
@@ -104,7 +104,7 @@
SOGoTrashFolderName = "Trash";
SOGoJunkFolderName = "Junk";
SOGoMailComposeMessageType = "html";
SOGoMailComposeFontSize = 0;
SOGoMailComposeFontSize = "0";
SOGoMailDisplayRemoteInlineImages = "never";
SOGoMailAutoSave = "5";
+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];
+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"];
+1
View File
@@ -20,6 +20,7 @@ $(SOGO_TOOL)_OBJC_FILES += \
SOGoToolRemove.m \
SOGoToolRemoveDoubles.m \
SOGoToolRenameUser.m \
SOGoToolCleanupUser.m \
SOGoToolRestore.m \
SOGoToolCreateFolder.m \
SOGoToolUserPreferences.m \
+52 -51
View File
@@ -94,9 +94,9 @@
- (void) usage
{
fprintf (stderr, "backup directory ALL|user1 [user2] ...\n\n"
" directory the target directory where backup files will be stored\n"
" user the user of whom to save the data or ALL for everybody\n\n"
"Example: sogo-tool backup /tmp/foo ALL\n");
" directory the target directory where backup files will be stored\n"
" user the user of whom to save the data or ALL for everybody\n\n"
"Example: sogo-tool backup /tmp/foo ALL\n");
}
- (BOOL) checkDirectory
@@ -137,7 +137,6 @@
int count, max;
lm = [SOGoUserManager sharedUserManager];
pool = [[NSAutoreleasePool alloc] init];
max = [users count];
user = [users objectAtIndex: 0];
@@ -156,61 +155,63 @@
folderLocation = [fm folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
allSqlUsers = [NSMutableArray new];
sql
= [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
{
allSqlUsers = [NSMutableArray new];
sql
= [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
users = allSqlUsers;
max = [users count];
}
users = allSqlUsers;
max = [users count];
[allSqlUsers autorelease];
}
}
pool = [[NSAutoreleasePool alloc] init];
allUsers = [NSMutableArray new];
for (count = 0; count < max; count++)
{
if (count > 0 && count%100 == 0)
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
user = [users objectAtIndex: count];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
[allUsers addObject: infos];
else
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
r = [user rangeOfString: @"@"];
r = [user rangeOfString: @"@"];
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
}
[allUsers autorelease];
@@ -261,9 +262,9 @@
if (fc)
{
sql
= [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
@" WHERE c_path = '%@'"),
[folderLocation gcsTableName], folder];
= [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
@" WHERE c_path = '%@'"),
[folderLocation gcsTableName], folder];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
row = [fc fetchAttributes: attrs withZone: NULL];
@@ -341,7 +342,7 @@
forKey: @"acl"];
[folderRecord setObject: tableRecord forKey: folder];
rc = YES;
}
}
else
{
NSLog(@"Unable to extract records for folder %@", folder);
@@ -398,11 +399,11 @@
currentSource = [lm sourceWithID: sourceID];
userEntry = [currentSource lookupContactEntry: uid inDomain: domain];
if (userEntry)
{
{
[userRecord setObject: [userEntry ldifRecordAsString]
forKey: @"ldif_record"];
done = YES;
}
}
}
return YES;
+294
View File
@@ -0,0 +1,294 @@
/* SOGoToolCleanup.m - this file is part of SOGo
*
* Copyright (C) 2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
#import <GDLAccess/EOAdaptorChannel.h>
#import <GDLContentStore/GCSChannelManager.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <GDLContentStore/GCSFolder.h>
#import <GDLContentStore/NSURL+GCS.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoSystemDefaults.h>
#import "SOGoTool.h"
@interface SOGoToolCleanup : SOGoTool
{
NSArray *usersToCleanup;
unsigned int days;
}
@end
@implementation SOGoToolCleanup
+ (NSString *) command
{
return @"cleanup";
}
+ (NSString *) description
{
return @"cleanup deleted elements of user(s)";
}
- (id) init
{
if ((self = [super init]))
{
usersToCleanup = nil;
days = 0;
}
return self;
}
- (void) dealloc
{
[usersToCleanup release];
[super dealloc];
}
- (void) usage
{
fprintf (stderr, "cleanup [days] [user]...\n\n"
" days the age of deleted records to purge in days\n"
" user the user to purge the records or ALL for everybody\n\n"
"Example: sogo-tool cleanup jdoe\n");
}
- (BOOL) fetchUserIDs: (NSArray *) users
{
NSAutoreleasePool *pool;
SOGoUserManager *lm;
NSDictionary *infos;
NSString *user;
id allUsers;
int count, max;
lm = [SOGoUserManager sharedUserManager];
max = [users count];
user = [users objectAtIndex: 0];
if (max == 1 && [user isEqualToString: @"ALL"])
{
GCSFolderManager *fm;
GCSChannelManager *cm;
NSURL *folderLocation;
EOAdaptorChannel *fc;
NSArray *attrs;
NSMutableArray *allSqlUsers;
NSString *sql;
fm = [GCSFolderManager defaultFolderManager];
cm = [fm channelManager];
folderLocation = [fm folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
allSqlUsers = [NSMutableArray new];
sql = [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
users = allSqlUsers;
max = [users count];
[allSqlUsers autorelease];
}
}
pool = [[NSAutoreleasePool alloc] init];
allUsers = [NSMutableArray new];
for (count = 0; count < max; count++)
{
if (count > 0 && count%100 == 0)
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
user = [users objectAtIndex: count];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
r = [user rangeOfString: @"@"];
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
}
[allUsers autorelease];
ASSIGN (usersToCleanup, allUsers);
DESTROY(pool);
return ([usersToCleanup count] > 0);
}
- (BOOL) parseArguments
{
BOOL rc;
NSRange usersRange;
int max;
max = [arguments count];
if (max > 1)
{
days = [[arguments objectAtIndex: 0] intValue];
usersRange.location = 1;
usersRange.length = max - 1;
rc = [self fetchUserIDs: [arguments subarrayWithRange: usersRange]];
}
else
{
[self usage];
rc = NO;
}
return rc;
}
- (BOOL) cleanupFolder: (NSString *) folder
withFM: (GCSFolderManager *) fm
{
GCSFolder *gcsFolder;
NSException *error;
BOOL rc;
unsigned int count;
gcsFolder = [fm folderAtPath: folder];
count = [gcsFolder recordsCountDeletedBefore: days];
error = nil;
if (count > 0)
error = [gcsFolder purgeDeletedRecordsBefore: days];
if (error)
{
NSLog(@"Unable to purge records of folder %@", folder);
rc = NO;
}
else
{
NSLog(@"Purged %u records from folder %@", count, folder);
rc = YES;
}
return rc;
}
- (BOOL) cleanupUserFolders: (NSString *) uid
{
GCSFolderManager *fm;
NSArray *folders;
int count, max;
NSString *basePath, *folder;
fm = [GCSFolderManager defaultFolderManager];
basePath = [NSString stringWithFormat: @"/Users/%@", uid];
folders = [fm listSubFoldersAtPath: basePath recursive: YES];
max = [folders count];
for (count = 0; count < max; count++)
{
folder = [NSString stringWithFormat: @"%@/%@", basePath, [folders objectAtIndex: count]];
//NSLog (@"folder %d: %@", count, folder);
[self cleanupFolder: folder withFM: fm];
}
return YES;
}
- (BOOL) cleanupUser: (NSDictionary *) theUser
{
NSString *gcsUID, *domain;
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
domain = [theUser objectForKey: @"c_domain"];
gcsUID = [theUser objectForKey: @"c_uid"];
if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound)
gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, domain];
return [self cleanupUserFolders: gcsUID];
}
- (BOOL) proceed
{
NSAutoreleasePool *pool;
int count, max;
BOOL rc;
rc = YES;
pool = [NSAutoreleasePool new];
max = [usersToCleanup count];
for (count = 0; rc && count < max; count++)
{
rc = [self cleanupUser: [usersToCleanup objectAtIndex: count]];
if ((count % 10) == 0)
[pool emptyPool];
}
[pool release];
return rc;
}
- (BOOL) run
{
return ([self parseArguments] && [self proceed]);
}
@end
+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" = "↑";
@@ -65,10 +65,10 @@
= "Bu modül veya sisteme erişim yetkiniz yok. Lütfen, sistem yöneticisiyle iletişime geçin.";
"You don't have the required privileges to perform the operation."
= "Bu işlemi gerçekleştirmek için gereken yetkilere sahip değilsiniz.";
"noEmailForDelegation" = "Davetinize temsilci atayacağınız adresleri belirtmelisiniz.";
"delegate is organizer" = "Temsilci etkinliği düzenleyen kişidir. Lütfen başka bir temsilci seçin. ";
"delegate is a participant" = "Temsilci zaten bu etkinliğe katılıyor.";
"delegate is a group" = "Belirtilen adres bir gruba denk geliyor. Sadece bir kişiyi temsilci atayabilirsiniz.";
"noEmailForDelegation" = "Davetinize vekil atayacağınız kişinin adresini belirtmelisiniz.";
"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";
+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 = {
@@ -246,7 +246,7 @@
"Add Screen Name" = "Додади прекар";
"Synchronization" = "Синхронизација";
"Synchronize" = "Синхронизирај";
"Sucessfully subscribed to address book" = "Успешно сте претплатени на адресарот";
"Sucessfully subscribed to address book" = "Успешно се претплативте на адресната книга";
/* Aria label for scope of search on contacts */
"Search scope" = "Предмет на пребарување";
+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
+12 -3
View File
@@ -79,10 +79,18 @@
{
NSString *s;
WORequest *rq;
static NSArray *sortKeys = nil;
if (!sortKeys)
{
sortKeys = [NSArray arrayWithObjects: @"c_cn", @"c_sn", @"c_givenname", @"c_mail",
@"c_screenname", @"c_o", @"c_telephonenumber", nil];
[sortKeys retain];
}
rq = [context request];
s = [rq formValueForKey: @"sort"];
if (![s length])
if (![s length] || ![sortKeys containsObject: s])
s = [self defaultSortKey];
return s;
@@ -98,7 +106,8 @@
ascending = [[context request] formValueForKey: @"asc"];
if ([sort length])
{
{
sort = [self sortKey];
us = [[context activeUser] userSettings];
contactSettings = [us objectForKey: @"Contact"];
// Must create if it doesn't exist
@@ -1,7 +1,7 @@
ACCEPTED = "kabul edildi";
COMPLETED = "tamamlandı";
DECLINED = "reddedildi";
DELEGATED = "temsilci atanmış";
DELEGATED = "yetkilendirildi";
"IN-PROCESS" = "işleniyor";
"NEEDS-ACTION" = "eyleme ihtiyaç var";
TENTATIVE = "geçici";
@@ -16,7 +16,7 @@ Appointment = "Çalışma";
was = "önceden";
Organizer = "Düzenleyen";
Time = "Süre";
Time = "Zaman";
Attendees = "Katılımcılar";
request_info = "sizi toplantıya katılmaya davet ediyor. ";
"Add to calendar" = "Takvime ekle";
@@ -25,10 +25,10 @@ request_info = "sizi toplantıya katılmaya davet ediyor. ";
Accept = "Kabul et";
Decline = "Reddet";
Tentative = "Geçici";
"Delegate ..." = "Temsilci Ata ...";
"Delegated to" = "Temsilci atanan";
"Delegate ..." = "Yetkili Ata ...";
"Delegated to" = "Yetkilendirilen";
"Update status in calendar" = "Takvimdeki durumu güncelle";
"delegated from" = "tarafından temsilci atandı";
"delegated from" = "Yetkilendiren";
reply_info_no_attendee = "Bir etkinlik programına yanıt aldınız fakat yanıtı gönderen kişi katılımcı değil.";
reply_info = "Yapmış olduğunuz etkinlik davetinin yanıtıdır.";
"to" = "alıcı";
+11 -2
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];
@@ -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];
+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
@@ -217,6 +217,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 +321,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";
+18 -1
View File
@@ -269,6 +269,10 @@
"quotasFormat" = "%{0}% käytetty %{1} Mt:sta";
"Unable to move/delete folder." = "Hakemiston siirto/poisto ei onnistu.";
/* Alternative operation when folder cannot be deleted */
"The mailbox could not be moved to the trash folder. Would you like to delete it immediately?"
= "Postilaatikon vienti roskakoriin epäonniestui. Haluatko poistaa sen välittömästi?";
/* Confirmation message when deleting multiple messages */
"Are you sure you want to delete the selected messages?" = "Haluatko varmasti poistaa valitut viestit?";
@@ -350,6 +354,7 @@
"Copy messages" = "Kopioi viestit";
"More messages options" = "Lisää viestiasetuksia";
"Mark as Unread" = "Merkitse lukemattomaksi";
"Mark as Read" = "Merkitse luetuksi";
"Closing Window ..." = "Ikkuna sulkeutuu...";
"Tried to send too many mails. Please wait." = "Yritetty lähettää liian monta viestiä. Ole hyvä ja odota.";
"View Mail" = "Näytä viesti";
@@ -373,5 +378,17 @@
/* Hotkey to write a new message */
"hotkey_compose" = "w";
/* Hotkey to mark selected message(s) as junk */
"hotkey_junk" = "j";
/* Hotkey to flag a message */
"hotkey_flag" = "f";
"hotkey_flag" = "*";
/* Hotkey to reply to a message */
"hotkey_reply" = "r";
/* Hotkey to reply to all recipients of a message */
"hotkey_replyall" = "a";
/* Hotkey to forward to a message */
"hotkey_forward" = "f";
@@ -217,6 +217,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 toutes 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 +321,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";
@@ -217,6 +217,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";
@@ -315,6 +321,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";
@@ -77,7 +77,7 @@
"List and see this folder" = "Излистај и погледни ја оваа папка";
"Read mails from this folder" = "Читај ги пораките од оваа папка";
"Mark mails read and unread" = "Означи ги пораките како прочитани и непрочитани";
"Modify the flags of the mails in this folder" = "Промени ги знаменцата на пораките во оваа папка";
"Modify the flags of the mails in this folder" = "Промени ги ознаките на пораките во оваа папка";
"Insert, copy and move mails into this folder" = "Внеси, копирај и префрли пораки во оваа папка";
"Post mails" = "Испрати пораки";
"Add subfolders to this folder" = "Додади под папка во оваа папка";
@@ -242,7 +242,7 @@
"Thread As Read" = "Преписката како прочитана";
"As Read By Date..." = "Прочитано по датум...";
"All Read" = "Сето прочитано";
"Flag" = "Знаменце";
"Flag" = "Ознака";
"As Junk" = "Како отпад";
"As Not Junk" = "Не е отпад";
"Run Junk Mail Controls" = "Изврши ги контролите за отпад";
@@ -269,6 +269,10 @@
"quotasFormat" = "%{0}% used on %{1} MB";
"Unable to move/delete folder." = "Не е можно да ја префрлите/избришете папката.";
/* Alternative operation when folder cannot be deleted */
"The mailbox could not be moved to the trash folder. Would you like to delete it immediately?"
= "Сандачето не може да биде преместено во кантата за ѓубре. Дали би сакале веднаш да го избришам?";
/* Confirmation message when deleting multiple messages */
"Are you sure you want to delete the selected messages?" = "Дали сте сигурни дека сакате да ги избришете селектираните пораки?";
@@ -350,6 +354,7 @@
"Copy messages" = "Копирај ја пораката";
"More messages options" = "Повеќе опции за пораките";
"Mark as Unread" = "Означи како непрочитана";
"Mark as Read" = "Означи како прочитано";
"Closing Window ..." = "Затварање на прозорците ...";
"Tried to send too many mails. Please wait." = "Се обидов да пратам премногу пораки. Ве молам почекајте.";
"View Mail" = "Види пошта";
@@ -373,6 +378,9 @@
/* Hotkey to write a new message */
"hotkey_compose" = "w";
/* Hotkey to mark selected message(s) as junk */
"hotkey_junk" = "ј";
/* Hotkey to flag a message */
"hotkey_flag" = "*";
@@ -217,6 +217,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 +321,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";
@@ -269,6 +269,10 @@
"quotasFormat" = "%{0}% искоришћено од %{1} МБ";
"Unable to move/delete folder." = "Не могу да преместим/обришем директоријум.";
/* Alternative operation when folder cannot be deleted */
"The mailbox could not be moved to the trash folder. Would you like to delete it immediately?"
= "Поруке не могу бити премештене у директоријум канте. Да ли желите да их одмах обришете?";
/* Confirmation message when deleting multiple messages */
"Are you sure you want to delete the selected messages?" = "Да ли сте сигурни да желите да обришете одабране поруке?";
@@ -350,6 +354,7 @@
"Copy messages" = "Копирај поруке";
"More messages options" = "Више опција порука";
"Mark as Unread" = "Означи као непрочитано";
"Mark as Read" = "Означи као прочитано";
"Closing Window ..." = "Затварам прозор...";
"Tried to send too many mails. Please wait." = "Покушавам да пошаљем превише поште. Молимо сачекајте.";
"View Mail" = "Види пошту";
@@ -373,6 +378,9 @@
/* Hotkey to write a new message */
"hotkey_compose" = "w";
/* Hotkey to mark selected message(s) as junk */
"hotkey_junk" = "j";
/* Hotkey to flag a message */
"hotkey_flag" = "*";
@@ -2,7 +2,7 @@
/* Icon's label */
"Create" = "Oluştur";
"Empty Trash" = "Çöp Kutusunu Boşalt";
"Empty Trash" = "Çöp Klasörünü Boşalt";
"Delete" = "Sil";
"Expunge" = "Silip At";
"Forward" = "İlet";
@@ -68,7 +68,7 @@
/* Compact Folder success message */
"Folder compacted" = "Klasör sıkıştırıldı";
/* Empty Trash success message */
"Trash emptied" = "Çöp Kutusu boşaltıldı";
"Trash emptied" = "Çöp boşaltıldı";
/* acls */
"Access rights to" = "Klasörün erişim yetkileri";
@@ -76,7 +76,7 @@
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
"List and see this folder" = "Klasörü listele ve görüntüle";
"Read mails from this folder" = "Klasördeki iletileri okur";
"Mark mails read and unread" = "Okumuş ve okunmamış iletileri işaretle";
"Mark mails read and unread" = "Okumuş ve okunmamış iletileri işaretler";
"Modify the flags of the mails in this folder" = "Bu klasördeki iletilerin yıldızlarını değiştirir";
"Insert, copy and move mails into this folder" = "İletiler bu klasöre yerleştirir, kopyalar ve taşır";
"Post mails" = "İletileri postalar";
@@ -141,7 +141,7 @@
"Date" = "Tarih";
"View" = "Görüntüle";
"All" = "Hepsi";
"No message" = "İleti yok";
"No message" = "Klasör boş";
"messages" = "ileti";
"Yesterday" = "Dün";
"first" = "İlk";
@@ -156,8 +156,8 @@
/* Tree */
"SentFolderName" = "Gönderilmiş";
"TrashFolderName" = "Çöp Kutusu";
"InboxFolderName" = "Gelen Kutusu";
"TrashFolderName" = "Çöp";
"InboxFolderName" = "Gelen";
"DraftsFolderName" = "Taslaklar";
"JunkFolderName" = "İstenmeyen";
"SieveFolderName" = "Süzgeçler";
@@ -190,11 +190,11 @@
"Use This Folder For" = "Bu Klasörü Şunun İçin Kullan";
"Get Messages for Account" = "Hesaptaki İletileri Al";
"Properties..." = "Özellikler...";
"Delegation..." = "Temsilci Atama...";
"Delegation..." = "Yetkili Tayini...";
/* Use This Folder menu */
"Sent Messages" = "Gönderilmiş İletiler";
"Drafts" = "Taslak İletiler";
"Drafts" = "Taslaklar";
"Deleted Messages" = "Silinmiş İletiler";
"Junk Messages" = "İstenmeyen İletiler";
@@ -263,7 +263,7 @@
"Enter the new name of your folder"
="Yeni klasörünüzün adını yazın";
"Do you really want to move this folder into the trash ?"
= "Bu klasörü çöp kutusuna taşımak istediğinizden emin misiniz?";
= "Bu klasörü çöpe taşımak istediğinizden emin misiniz?";
"Operation failed" = "İşlem başarısiz oldu";
"Quota" = "Kota:";
"quotasFormat" = "%{0}% kullanılıyor, toplam %{1} MB";
@@ -271,7 +271,7 @@
/* Alternative operation when folder cannot be deleted */
"The mailbox could not be moved to the trash folder. Would you like to delete it immediately?"
= "E-posta klasörü çöp kutusuna taşınamıyor. Klasörü hemen silmek ister misiniz?";
= "Klasör çöpe taşınamıyor. Klasörü hemen silmek ister misiniz?";
/* Confirmation message when deleting multiple messages */
"Are you sure you want to delete the selected messages?" = "Seçili iletileri silmek istediğinizden emin misiniz?";
@@ -293,7 +293,7 @@
"The folder could not be deleted."
= "Klasör silinemiyor.";
"The trash could not be emptied."
= "Çöp Kutusu boşaltılamıyor.";
= "Çöp boşaltılamıyor.";
"The folder functionality could not be changed."
= "Klasörün işlevi değişitirilemiyor.";
"You need to choose a non-virtual folder!" = "Sanal olmayan bir klasör seçmelisiniz.";
@@ -304,7 +304,7 @@
/* Message operations */
"The messages could not be moved to the trash folder. Would you like to delete them immediately?"
= "İletiler çöp kutusuna taşınamıyor. Bu iletileri hemen silmek ister misiniz?";
= "İletiler çöpe taşınamıyor. Bu iletileri hemen silmek ister misiniz?";
/* Message editing */
"error_missingsubject" = "İletiye bir konu başlığı yazılmadı. İletiyi konusuz olarak göndermek istediğinizden emin misiniz?";
@@ -329,7 +329,7 @@
/* Contacts list in mail editor */
"Email" = "E-posta";
"More mail options" = "Diğer e-posta seçenekleri";
"Delegation" = "Temsilci Atama";
"Delegation" = "Yetkili Tayini";
"Add User" = "Kullanıcı Ekleyin";
"Add a tag" = "Etiket ekle";
"reply" = "yanıtla";
@@ -340,12 +340,12 @@
"Rename" = "Yeniden Adlandır";
"Compact" = "Sıkıştır";
"Export" = "Dışarı Aktar";
"Set as Drafts" = "Taslaklar olarak tanımla";
"Set as Sent" = "Gönderilmiş olarak tanımla";
"Set as Trash" = "Çöp Kutusu olarak tanımla";
"Set as Drafts" = "Taslaklar klasörü olarak tanımla";
"Set as Sent" = "Gönderilmiş klasörü olarak tanımla";
"Set as Trash" = "Çöp klasörü olarak tanımla";
/* Set the folder as the one holding Junk mails */
"Set as Junk" = "İstenmeyen olarak belirle";
"Set as Junk" = "İstenmeyen klasörü olarak tanımla";
"Sort" = "Sıralandır";
"Order Received" = "Alınan Tarih";
+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 */
+12
View File
@@ -312,6 +312,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";
@@ -15,6 +15,7 @@
"Authentication Failed" = "Tunnistautuminen epäonnistui";
"Wrong username or password." = "Väärä käyttäjätunnus tai salasana.";
"Retry" = "Yritä uudelleen";
"cookiesNotEnabled" = "Et voi kirjautua koska selaimesi evästeet eivät ole käytössä. Salli evästeet selaimen asetuksista ja yritä uudelleen.";
"browserNotCompatible" = "Olemme havainneet että selaimesi versiota ei tällä hetkellä tueta tällä sivustolla. Suosituksemme on käyttää Firefoxia. Napsauta alla olevaa linkkiä ladataksesi uusimman version tästä selaimesta.";
"alternativeBrowsers" = "Vaihtoehtoisesti voit myös käyttää seuraavia yhteensopivia selaimia";
@@ -15,6 +15,7 @@
"Authentication Failed" = "Неуспешна автентификација";
"Wrong username or password." = "Погрешно корисничко име или лозинка.";
"Retry" = "Обиди се повторно";
"cookiesNotEnabled" = "Не можете да се најавите поради тоа што колачињата на вашиот прелистувач не се активирани. Ве молиме да ги овозможите колачињата во опциите на вашиот прелистувач и да се обидете повторно.";
"browserNotCompatible" = "Забележивме дека вашиот прелистувач во моментов не е поддржан од овој сајт. Наша препорака е да користите Firefox. Клинете врз овој линк да ја преземете последната верзија на овој прелистувач.";
"alternativeBrowsers" = "Како алтернатива исто така можете да користите еден од компатибилните прелистувачи";
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff. Show more