2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import "SOGoTaskObject.h"
24 #import <NGCards/iCalCalendar.h>
25 #import <NGCards/iCalToDo.h>
26 #import <NGCards/iCalEventChanges.h>
27 #import <NGCards/iCalPerson.h>
28 #import <SOGo/LDAPUserManager.h>
29 #import <NGMime/NGMime.h>
30 #import <NGMail/NGMail.h>
31 #import <NGMail/NGSendMail.h>
32 #import "SOGoAptMailNotification.h"
35 #import "NSArray+Appointments.h"
37 @interface SOGoTaskObject (PrivateAPI)
39 - (NSString *) homePageURLForPerson: (iCalPerson *) _person;
43 @implementation SOGoTaskObject
45 static NSString *mailTemplateDefaultLanguage = nil;
49 static BOOL didInit = NO;
54 ud = [NSUserDefaults standardUserDefaults];
55 mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
57 if (!mailTemplateDefaultLanguage)
58 mailTemplateDefaultLanguage = @"French";
61 - (NSString *) componentTag
68 - (NSArray *)attendeeUIDsFromTask:(iCalToDo *)_task {
73 NSString *email, *uid;
75 if (![_task isNotNull])
78 if ((attendees = [_task attendees]) == nil)
80 count = [attendees count];
81 uids = [NSMutableArray arrayWithCapacity:count + 1];
83 um = [LDAPUserManager sharedUserManager];
87 email = [[_task organizer] rfc822Email];
88 if ([email isNotNull]) {
89 uid = [um getUIDForEmail:email];
90 if ([uid isNotNull]) {
94 [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
99 for (i = 0; i < count; i++) {
102 person = [attendees objectAtIndex:i];
103 email = [person rfc822Email];
104 if (![email isNotNull]) continue;
106 uid = [um getUIDForEmail:email];
107 if (![uid isNotNull]) {
108 [self logWithFormat:@"Note: got no uid for email: '%@'", email];
111 if (![uids containsObject:uid])
112 [uids addObject:uid];
118 /* folder management */
120 - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
121 // TODO: what does this do? lookup the home of the organizer?
122 return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
125 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
126 return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
129 /* store in all the other folders */
131 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
134 NSException *allErrors = nil;
136 e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
138 while ((folder = [e nextObject]) != nil) {
140 SOGoTaskObject *task;
142 if (![folder isNotNull]) /* no folder was found for given UID */
145 task = [folder lookupName:[self nameInContainer] inContext: context
147 if ([task isKindOfClass: [NSException class]])
149 [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
150 [self nameInContainer], folder];
151 [self logWithFormat:@"the exception reason was: %@",
152 [(NSException *) task reason]];
156 if (![task isNotNull]) {
157 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
158 [self nameInContainer], folder];
162 if ((error = [task primarySaveContentString:_iCal]) != nil) {
163 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
164 // TODO: make compound
170 - (NSException *)deleteInUIDs:(NSArray *)_uids {
173 NSException *allErrors = nil;
175 e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
177 while ((folder = [e nextObject])) {
179 SOGoTaskObject *task;
181 task = [folder lookupName: [self nameInContainer]
184 if (![task isNotNull]) {
185 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
186 [self nameInContainer], folder];
189 if ([task isKindOfClass: [NSException class]]) {
190 [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
194 if ((error = [task primaryDelete]) != nil) {
195 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
196 // TODO: make compound
203 /* "iCal multifolder saves" */
205 - (NSException *) saveContentString: (NSString *) _iCal
206 baseSequence: (int) _v
209 Note: we need to delete in all participants folders and send iMIP messages
210 for all external accounts.
213 - fetch stored content
215 - check if sequence matches (or if 0=ignore)
216 - extract old attendee list + organizer (make unique)
217 - parse new content (ensure that sequence is increased!)
218 - extract new attendee list + organizer (make unique)
219 - make a diff => new, same, removed
221 - delete in removed folders
222 - send iMIP mail for all folders not found
224 // LDAPUserManager *um;
225 // iCalCalendar *calendar;
226 // iCalToDo *oldApt, *newApt;
227 // // iCalToDoChanges *changes;
228 // iCalPerson *organizer;
229 // NSString *oldContent, *uid;
230 // NSArray *uids, *props;
231 // NSMutableArray *attendees, *storeUIDs, *removedUIDs;
232 NSException *storeError, *delError;
233 // BOOL updateForcesReconsider;
235 // updateForcesReconsider = NO;
237 // if ([_iCal length] == 0) {
238 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
239 // reason:@"got no iCalendar content to store!"];
242 // um = [LDAPUserManager sharedUserManager];
244 // /* handle old content */
246 // oldContent = [self contentAsString]; /* if nil, this is a new task */
247 // if ([oldContent length] == 0)
250 // [self debugWithFormat:@"saving new task: %@", _iCal];
255 // calendar = [iCalCalendar parseSingleFromSource: oldContent];
256 // oldApt = [self firstTaskFromCalendar: calendar];
259 // /* compare sequence if requested */
266 // /* handle new content */
268 // calendar = [iCalCalendar parseSingleFromSource: _iCal];
269 // newApt = [self firstTaskFromCalendar: calendar];
270 // if (newApt == nil) {
271 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
272 // reason:@"could not parse iCalendar content!"];
277 // changes = [iCalToDoChanges changesFromEvent: oldApt
280 // uids = [um getUIDsForICalPersons:[changes deletedAttendees]
281 // applyStrictMapping:NO];
282 // removedUIDs = [NSMutableArray arrayWithArray:uids];
284 // uids = [um getUIDsForICalPersons:[newApt attendees]
285 // applyStrictMapping:NO];
286 // storeUIDs = [NSMutableArray arrayWithArray:uids];
287 // props = [changes updatedProperties];
289 // /* detect whether sequence has to be increased */
290 // if ([changes hasChanges])
291 // [newApt increaseSequence];
293 // /* preserve organizer */
295 // organizer = [newApt organizer];
296 // uid = [um getUIDForICalPerson:organizer];
298 // if (![storeUIDs containsObject:uid])
299 // [storeUIDs addObject:uid];
300 // [removedUIDs removeObject:uid];
303 // /* organizer might have changed completely */
305 // if (oldApt && ([props containsObject: @"organizer"])) {
306 // uid = [um getUIDForICalPerson:[oldApt organizer]];
308 // if (![storeUIDs containsObject:uid]) {
309 // if (![removedUIDs containsObject:uid]) {
310 // [removedUIDs addObject:uid];
316 // [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
317 // storeUIDs, removedUIDs];
319 // /* if time did change, all participants have to re-decide ...
320 // * ... exception from that rule: the organizer
323 // if (oldApt != nil &&
324 // ([props containsObject:@"startDate"] ||
325 // [props containsObject:@"endDate"] ||
326 // [props containsObject:@"duration"]))
329 // unsigned i, count;
331 // ps = [newApt attendees];
332 // count = [ps count];
333 // for (i = 0; i < count; i++) {
336 // p = [ps objectAtIndex:i];
337 // if (![p hasSameEmailAddress:organizer])
338 // [p setParticipationStatus:iCalPersonPartStatNeedsAction];
340 // _iCal = [[newApt parent] versitString];
341 // updateForcesReconsider = YES;
344 // /* perform storing */
346 storeError = [self primarySaveContentString: _iCal];
348 // storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
349 // delError = [self deleteInUIDs:removedUIDs];
351 // TODO: make compound
352 if (storeError != nil) return storeError;
353 // if (delError != nil) return delError;
355 /* email notifications */
356 // if ([self sendEMailNotifications])
358 // attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
359 // [attendees removePerson:organizer];
360 // [self sendInvitationEMailForTask:newApt
361 // toAttendees:attendees];
363 // if (updateForcesReconsider) {
364 // attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
365 // [attendees removeObjectsInArray:[changes insertedAttendees]];
366 // [attendees removePerson:organizer];
367 // [self sendEMailUsingTemplateNamed: @"Update"
368 // forOldObject: oldApt
369 // andNewObject: newApt
370 // toAttendees: attendees];
373 // attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
374 // [attendees removePerson: organizer];
375 // if ([attendees count]) {
376 // iCalToDo *canceledApt;
378 // canceledApt = [newApt copy];
379 // [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
380 // [self sendEMailUsingTemplateNamed: @"Removal"
382 // andNewObject: canceledApt
383 // toAttendees: attendees];
384 // [canceledApt release];
391 - (NSException *)deleteWithBaseSequence:(int)_v {
393 Note: We need to delete in all participants folders and send iMIP messages
394 for all external accounts.
395 Delete is basically identical to save with all attendees and the
396 organizer being deleted.
399 - fetch stored content
401 - check if sequence matches (or if 0=ignore)
402 - extract old attendee list + organizer (make unique)
403 - delete in removed folders
404 - send iMIP mail for all folders not found
407 NSArray *removedUIDs;
408 NSMutableArray *attendees;
410 /* load existing content */
412 task = (iCalToDo *) [self component: NO];
414 /* compare sequence if requested */
420 removedUIDs = [self attendeeUIDsFromTask:task];
422 if ([self sendEMailNotifications])
424 /* send notification email to attendees excluding organizer */
425 attendees = [NSMutableArray arrayWithArray:[task attendees]];
426 [attendees removePerson:[task organizer]];
428 /* flag task as being canceled */
429 [(iCalCalendar *) [task parent] setMethod: @"cancel"];
430 [task increaseSequence];
432 /* remove all attendees to signal complete removal */
433 [task removeAllAttendees];
435 /* send notification email */
436 [self sendEMailUsingTemplateNamed: @"Deletion"
439 toAttendees: attendees];
444 return [self deleteInUIDs:removedUIDs];
447 - (NSException *)saveContentString:(NSString *)_iCalString {
448 return [self saveContentString:_iCalString baseSequence:0];
453 - (NSString *)outlookMessageClass {
457 @end /* SOGoTaskObject */