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 <Foundation/NSException.h>
23 #import <Foundation/NSUserDefaults.h>
25 #import <NGExtensions/NSObject+Logs.h>
26 #import <NGExtensions/NSNull+misc.h>
27 #import <NGCards/iCalCalendar.h>
28 #import <NGCards/iCalToDo.h>
29 #import <NGCards/iCalEventChanges.h>
30 #import <NGCards/iCalPerson.h>
31 #import <SOGo/LDAPUserManager.h>
33 #import <SoObjects/SOGo/SOGoMailer.h>
35 #import "NSArray+Appointments.h"
36 #import "SOGoAptMailNotification.h"
38 #import "SOGoTaskObject.h"
40 @interface SOGoTaskObject (PrivateAPI)
42 - (NSString *) homePageURLForPerson: (iCalPerson *) _person;
46 @implementation SOGoTaskObject
48 static NSString *mailTemplateDefaultLanguage = nil;
52 static BOOL didInit = NO;
57 ud = [NSUserDefaults standardUserDefaults];
58 mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
60 if (!mailTemplateDefaultLanguage)
61 mailTemplateDefaultLanguage = @"French";
64 - (NSString *) componentTag
71 - (NSArray *)attendeeUIDsFromTask:(iCalToDo *)_task {
76 NSString *email, *uid;
78 if (![_task isNotNull])
81 if ((attendees = [_task attendees]) == nil)
83 count = [attendees count];
84 uids = [NSMutableArray arrayWithCapacity:count + 1];
86 um = [LDAPUserManager sharedUserManager];
90 email = [[_task organizer] rfc822Email];
91 if ([email isNotNull]) {
92 uid = [um getUIDForEmail:email];
93 if ([uid isNotNull]) {
97 [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
102 for (i = 0; i < count; i++) {
105 person = [attendees objectAtIndex:i];
106 email = [person rfc822Email];
107 if (![email isNotNull]) continue;
109 uid = [um getUIDForEmail:email];
110 if (![uid isNotNull]) {
111 [self logWithFormat:@"Note: got no uid for email: '%@'", email];
114 if (![uids containsObject:uid])
115 [uids addObject:uid];
121 /* folder management */
123 - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
124 // TODO: what does this do? lookup the home of the organizer?
125 return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
128 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
129 return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
132 /* store in all the other folders */
134 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
137 NSException *allErrors = nil;
139 e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
141 while ((folder = [e nextObject]) != nil) {
143 SOGoTaskObject *task;
145 if (![folder isNotNull]) /* no folder was found for given UID */
148 task = [folder lookupName:[self nameInContainer] inContext: context
150 if ([task isKindOfClass: [NSException class]])
152 [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
153 [self nameInContainer], folder];
154 [self logWithFormat:@"the exception reason was: %@",
155 [(NSException *) task reason]];
159 if (![task isNotNull]) {
160 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
161 [self nameInContainer], folder];
165 if ((error = [task primarySaveContentString:_iCal]) != nil) {
166 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
167 // TODO: make compound
173 - (NSException *)deleteInUIDs:(NSArray *)_uids {
176 NSException *allErrors = nil;
178 e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
180 while ((folder = [e nextObject])) {
182 SOGoTaskObject *task;
184 task = [folder lookupName: [self nameInContainer]
187 if (![task isNotNull]) {
188 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
189 [self nameInContainer], folder];
192 if ([task isKindOfClass: [NSException class]]) {
193 [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
197 if ((error = [task primaryDelete]) != nil) {
198 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
199 // TODO: make compound
206 /* "iCal multifolder saves" */
208 - (NSException *) saveContentString: (NSString *) _iCal
209 baseSequence: (int) _v
212 Note: we need to delete in all participants folders and send iMIP messages
213 for all external accounts.
216 - fetch stored content
218 - check if sequence matches (or if 0=ignore)
219 - extract old attendee list + organizer (make unique)
220 - parse new content (ensure that sequence is increased!)
221 - extract new attendee list + organizer (make unique)
222 - make a diff => new, same, removed
224 - delete in removed folders
225 - send iMIP mail for all folders not found
227 // LDAPUserManager *um;
228 // iCalCalendar *calendar;
229 // iCalToDo *oldApt, *newApt;
230 // // iCalToDoChanges *changes;
231 // iCalPerson *organizer;
232 // NSString *oldContent, *uid;
233 // NSArray *uids, *props;
234 // NSMutableArray *attendees, *storeUIDs, *removedUIDs;
235 NSException *storeError, *delError;
236 // BOOL updateForcesReconsider;
238 // updateForcesReconsider = NO;
240 // if ([_iCal length] == 0) {
241 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
242 // reason:@"got no iCalendar content to store!"];
245 // um = [LDAPUserManager sharedUserManager];
247 // /* handle old content */
249 // oldContent = [self contentAsString]; /* if nil, this is a new task */
250 // if ([oldContent length] == 0)
253 // [self debugWithFormat:@"saving new task: %@", _iCal];
258 // calendar = [iCalCalendar parseSingleFromSource: oldContent];
259 // oldApt = [self firstTaskFromCalendar: calendar];
262 // /* compare sequence if requested */
269 // /* handle new content */
271 // calendar = [iCalCalendar parseSingleFromSource: _iCal];
272 // newApt = [self firstTaskFromCalendar: calendar];
273 // if (newApt == nil) {
274 // return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
275 // reason:@"could not parse iCalendar content!"];
280 // changes = [iCalToDoChanges changesFromEvent: oldApt
283 // uids = [um getUIDsForICalPersons:[changes deletedAttendees]
284 // applyStrictMapping:NO];
285 // removedUIDs = [NSMutableArray arrayWithArray:uids];
287 // uids = [um getUIDsForICalPersons:[newApt attendees]
288 // applyStrictMapping:NO];
289 // storeUIDs = [NSMutableArray arrayWithArray:uids];
290 // props = [changes updatedProperties];
292 // /* detect whether sequence has to be increased */
293 // if ([changes hasChanges])
294 // [newApt increaseSequence];
296 // /* preserve organizer */
298 // organizer = [newApt organizer];
299 // uid = [um getUIDForICalPerson:organizer];
301 // if (![storeUIDs containsObject:uid])
302 // [storeUIDs addObject:uid];
303 // [removedUIDs removeObject:uid];
306 // /* organizer might have changed completely */
308 // if (oldApt && ([props containsObject: @"organizer"])) {
309 // uid = [um getUIDForICalPerson:[oldApt organizer]];
311 // if (![storeUIDs containsObject:uid]) {
312 // if (![removedUIDs containsObject:uid]) {
313 // [removedUIDs addObject:uid];
319 // [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
320 // storeUIDs, removedUIDs];
322 // /* if time did change, all participants have to re-decide ...
323 // * ... exception from that rule: the organizer
326 // if (oldApt != nil &&
327 // ([props containsObject:@"startDate"] ||
328 // [props containsObject:@"endDate"] ||
329 // [props containsObject:@"duration"]))
332 // unsigned i, count;
334 // ps = [newApt attendees];
335 // count = [ps count];
336 // for (i = 0; i < count; i++) {
339 // p = [ps objectAtIndex:i];
340 // if (![p hasSameEmailAddress:organizer])
341 // [p setParticipationStatus:iCalPersonPartStatNeedsAction];
343 // _iCal = [[newApt parent] versitString];
344 // updateForcesReconsider = YES;
347 // /* perform storing */
349 storeError = [self primarySaveContentString: _iCal];
351 // storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
352 // delError = [self deleteInUIDs:removedUIDs];
354 // TODO: make compound
355 if (storeError != nil) return storeError;
356 // if (delError != nil) return delError;
358 /* email notifications */
359 // if ([self sendEMailNotifications])
361 // attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
362 // [attendees removePerson:organizer];
363 // [self sendInvitationEMailForTask:newApt
364 // toAttendees:attendees];
366 // if (updateForcesReconsider) {
367 // attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
368 // [attendees removeObjectsInArray:[changes insertedAttendees]];
369 // [attendees removePerson:organizer];
370 // [self sendEMailUsingTemplateNamed: @"Update"
371 // forOldObject: oldApt
372 // andNewObject: newApt
373 // toAttendees: attendees];
376 // attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
377 // [attendees removePerson: organizer];
378 // if ([attendees count]) {
379 // iCalToDo *cancelledApt;
381 // cancelledApt = [newApt copy];
382 // [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
383 // [self sendEMailUsingTemplateNamed: @"Removal"
385 // andNewObject: cancelledApt
386 // toAttendees: attendees];
387 // [cancelledApt release];
394 - (NSException *)deleteWithBaseSequence:(int)_v {
396 Note: We need to delete in all participants folders and send iMIP messages
397 for all external accounts.
398 Delete is basically identical to save with all attendees and the
399 organizer being deleted.
402 - fetch stored content
404 - check if sequence matches (or if 0=ignore)
405 - extract old attendee list + organizer (make unique)
406 - delete in removed folders
407 - send iMIP mail for all folders not found
410 NSArray *removedUIDs;
411 NSMutableArray *attendees;
413 /* load existing content */
415 task = (iCalToDo *) [self component: NO];
417 /* compare sequence if requested */
423 removedUIDs = [self attendeeUIDsFromTask:task];
425 if ([self sendEMailNotifications])
427 /* send notification email to attendees excluding organizer */
428 attendees = [NSMutableArray arrayWithArray:[task attendees]];
429 [attendees removePerson:[task organizer]];
431 /* flag task as being cancelled */
432 [(iCalCalendar *) [task parent] setMethod: @"cancel"];
433 [task increaseSequence];
435 /* remove all attendees to signal complete removal */
436 [task removeAllAttendees];
438 /* send notification email */
439 [self sendEMailUsingTemplateNamed: @"Deletion"
442 toAttendees: attendees];
447 return [self deleteInUIDs:removedUIDs];
450 - (NSException *)saveContentString:(NSString *)_iCalString {
451 return [self saveContentString:_iCalString baseSequence:0];
456 - (NSString *)outlookMessageClass {
460 @end /* SOGoTaskObject */