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/AgenorUserManager.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 {
69 AgenorUserManager *um;
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 = [AgenorUserManager 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];
124 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
125 return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
128 /* store in all the other folders */
130 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
133 NSException *allErrors = nil;
136 ctx = [[WOApplication application] context];
138 e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
140 while ((folder = [e nextObject]) != nil) {
142 SOGoTaskObject *task;
144 if (![folder isNotNull]) /* no folder was found for given UID */
147 task = [folder lookupName:[self nameInContainer] inContext:ctx
149 if ([task isKindOfClass: [NSException class]])
151 [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
152 [self nameInContainer], folder];
153 [self logWithFormat:@"the exception reason was: %@",
154 [(NSException *) task reason]];
158 if (![task isNotNull]) {
159 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
160 [self nameInContainer], folder];
164 if ((error = [task primarySaveContentString:_iCal]) != nil) {
165 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
166 // TODO: make compound
172 - (NSException *)deleteInUIDs:(NSArray *)_uids {
175 NSException *allErrors = nil;
178 ctx = [[WOApplication application] context];
180 e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
182 while ((folder = [e nextObject])) {
184 SOGoTaskObject *task;
186 task = [folder lookupName:[self nameInContainer] inContext:ctx
188 if (![task isNotNull]) {
189 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
190 [self nameInContainer], folder];
193 if ([task isKindOfClass: [NSException class]]) {
194 [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
198 if ((error = [task primaryDelete]) != nil) {
199 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
200 // TODO: make compound
207 /* "iCal multifolder saves" */
209 - (NSException *) saveContentString: (NSString *) _iCal
210 baseSequence: (int) _v
213 Note: we need to delete in all participants folders and send iMIP messages
214 for all external accounts.
217 - fetch stored content
219 - check if sequence matches (or if 0=ignore)
220 - extract old attendee list + organizer (make unique)
221 - parse new content (ensure that sequence is increased!)
222 - extract new attendee list + organizer (make unique)
223 - make a diff => new, same, removed
225 - delete in removed folders
226 - send iMIP mail for all folders not found
228 // AgenorUserManager *um;
229 // iCalCalendar *calendar;
230 // iCalToDo *oldApt, *newApt;
231 // // iCalToDoChanges *changes;
232 // iCalPerson *organizer;
233 // NSString *oldContent, *uid;
234 // NSArray *uids, *props;
235 // NSMutableArray *attendees, *storeUIDs, *removedUIDs;
236 NSException *storeError, *delError;
237 // BOOL updateForcesReconsider;
239 // updateForcesReconsider = NO;
241 // if ([_iCal length] == 0) {
242 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
243 // reason:@"got no iCalendar content to store!"];
246 // um = [AgenorUserManager sharedUserManager];
248 // /* handle old content */
250 // oldContent = [self contentAsString]; /* if nil, this is a new task */
251 // if ([oldContent length] == 0)
254 // [self debugWithFormat:@"saving new task: %@", _iCal];
259 // calendar = [iCalCalendar parseSingleFromSource: oldContent];
260 // oldApt = [self firstTaskFromCalendar: calendar];
263 // /* compare sequence if requested */
270 // /* handle new content */
272 // calendar = [iCalCalendar parseSingleFromSource: _iCal];
273 // newApt = [self firstTaskFromCalendar: calendar];
274 // if (newApt == nil) {
275 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
276 // reason:@"could not parse iCalendar content!"];
281 // changes = [iCalToDoChanges changesFromEvent: oldApt
284 // uids = [um getUIDsForICalPersons:[changes deletedAttendees]
285 // applyStrictMapping:NO];
286 // removedUIDs = [NSMutableArray arrayWithArray:uids];
288 // uids = [um getUIDsForICalPersons:[newApt attendees]
289 // applyStrictMapping:NO];
290 // storeUIDs = [NSMutableArray arrayWithArray:uids];
291 // props = [changes updatedProperties];
293 // /* detect whether sequence has to be increased */
294 // if ([changes hasChanges])
295 // [newApt increaseSequence];
297 // /* preserve organizer */
299 // organizer = [newApt organizer];
300 // uid = [um getUIDForICalPerson:organizer];
302 // if (![storeUIDs containsObject:uid])
303 // [storeUIDs addObject:uid];
304 // [removedUIDs removeObject:uid];
307 // /* organizer might have changed completely */
309 // if (oldApt && ([props containsObject: @"organizer"])) {
310 // uid = [um getUIDForICalPerson:[oldApt organizer]];
312 // if (![storeUIDs containsObject:uid]) {
313 // if (![removedUIDs containsObject:uid]) {
314 // [removedUIDs addObject:uid];
320 // [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
321 // storeUIDs, removedUIDs];
323 // /* if time did change, all participants have to re-decide ...
324 // * ... exception from that rule: the organizer
327 // if (oldApt != nil &&
328 // ([props containsObject:@"startDate"] ||
329 // [props containsObject:@"endDate"] ||
330 // [props containsObject:@"duration"]))
333 // unsigned i, count;
335 // ps = [newApt attendees];
336 // count = [ps count];
337 // for (i = 0; i < count; i++) {
340 // p = [ps objectAtIndex:i];
341 // if (![p hasSameEmailAddress:organizer])
342 // [p setParticipationStatus:iCalPersonPartStatNeedsAction];
344 // _iCal = [[newApt parent] versitString];
345 // updateForcesReconsider = YES;
348 // /* perform storing */
350 storeError = [self primarySaveContentString: _iCal];
352 // storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
353 // delError = [self deleteInUIDs:removedUIDs];
355 // TODO: make compound
356 if (storeError != nil) return storeError;
357 // if (delError != nil) return delError;
359 /* email notifications */
360 // if ([self sendEMailNotifications])
362 // attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
363 // [attendees removePerson:organizer];
364 // [self sendInvitationEMailForTask:newApt
365 // toAttendees:attendees];
367 // if (updateForcesReconsider) {
368 // attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
369 // [attendees removeObjectsInArray:[changes insertedAttendees]];
370 // [attendees removePerson:organizer];
371 // [self sendEMailUsingTemplateNamed: @"Update"
372 // forOldObject: oldApt
373 // andNewObject: newApt
374 // toAttendees: attendees];
377 // attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
378 // [attendees removePerson: organizer];
379 // if ([attendees count]) {
380 // iCalToDo *canceledApt;
382 // canceledApt = [newApt copy];
383 // [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
384 // [self sendEMailUsingTemplateNamed: @"Removal"
386 // andNewObject: canceledApt
387 // toAttendees: attendees];
388 // [canceledApt release];
395 - (NSException *)deleteWithBaseSequence:(int)_v {
397 Note: We need to delete in all participants folders and send iMIP messages
398 for all external accounts.
399 Delete is basically identical to save with all attendees and the
400 organizer being deleted.
403 - fetch stored content
405 - check if sequence matches (or if 0=ignore)
406 - extract old attendee list + organizer (make unique)
407 - delete in removed folders
408 - send iMIP mail for all folders not found
411 NSArray *removedUIDs;
412 NSMutableArray *attendees;
414 /* load existing content */
416 task = (iCalToDo *) [self component];
418 /* compare sequence if requested */
424 removedUIDs = [self attendeeUIDsFromTask:task];
426 if ([self sendEMailNotifications])
428 /* send notification email to attendees excluding organizer */
429 attendees = [NSMutableArray arrayWithArray:[task attendees]];
430 [attendees removePerson:[task organizer]];
432 /* flag task as being canceled */
433 [(iCalCalendar *) [task parent] setMethod: @"cancel"];
434 [task increaseSequence];
436 /* remove all attendees to signal complete removal */
437 [task removeAllAttendees];
439 /* send notification email */
440 [self sendEMailUsingTemplateNamed: @"Deletion"
443 toAttendees: attendees];
448 return [self deleteInUIDs:removedUIDs];
451 - (NSException *)saveContentString:(NSString *)_iCalString {
452 return [self saveContentString:_iCalString baseSequence:0];
455 - (NSException *)changeParticipationStatus:(NSString *)_status
460 NSString *newContent;
464 // TODO: do we need to use SOGoTask? (prefer iCalToDo?)
465 task = (iCalToDo *) [self component];
468 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
469 reason:@"unable to parse task record"];
472 myEMail = [[_ctx activeUser] email];
473 if ((p = [task findParticipantWithEmail:myEMail]) == nil) {
474 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
475 reason:@"user does not participate in this "
479 [p setPartStat:_status];
480 newContent = [[task parent] versitString];
482 // TODO: send iMIP reply mails?
484 // [task release]; task = nil;
486 if (newContent == nil) {
487 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
488 reason:@"Could not generate iCalendar data ..."];
491 if ((ex = [self saveContentString:newContent]) != nil) {
492 // TODO: why is the exception wrapped?
493 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
497 return nil /* means: no error */;
503 - (NSString *)outlookMessageClass {
507 /* EMail Notifications */
509 - (NSString *)homePageURLForPerson:(iCalPerson *)_person {
510 static AgenorUserManager *um = nil;
511 static NSString *baseURL = nil;
516 NSArray *traversalObjects;
518 um = [[AgenorUserManager sharedUserManager] retain];
520 /* generate URL from traversal stack */
521 ctx = [[WOApplication application] context];
522 traversalObjects = [ctx objectTraversalStack];
523 if ([traversalObjects count] >= 1) {
524 baseURL = [[[traversalObjects objectAtIndex:0] baseURLInContext:ctx]
528 [self warnWithFormat:@"Unable to create baseURL from context!"];
529 baseURL = @"http://localhost/";
532 uid = [um getUIDForEmail:[_person rfc822Email]];
533 if (!uid) return nil;
534 return [NSString stringWithFormat:@"%@%@", baseURL, uid];
537 @end /* SOGoTaskObject */