]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoTaskObject.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1118 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Appointments / SOGoTaskObject.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #import "SOGoTaskObject.h"
23
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"
33 #import "common.h"
34
35 #import "NSArray+Appointments.h"
36
37 @interface SOGoTaskObject (PrivateAPI)
38
39 - (NSString *) homePageURLForPerson: (iCalPerson *) _person;
40   
41 @end
42
43 @implementation SOGoTaskObject
44
45 static NSString                  *mailTemplateDefaultLanguage = nil;
46
47 + (void)initialize {
48   NSUserDefaults      *ud;
49   static BOOL         didInit = NO;
50   
51   if (didInit) return;
52   didInit = YES;
53   
54   ud = [NSUserDefaults standardUserDefaults];
55   mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
56                                      retain];
57   if (!mailTemplateDefaultLanguage)
58     mailTemplateDefaultLanguage = @"French";
59 }
60
61 - (NSString *) componentTag
62 {
63   return @"vtodo";
64 }
65
66 /* iCal handling */
67
68 - (NSArray *)attendeeUIDsFromTask:(iCalToDo *)_task {
69   LDAPUserManager *um;
70   NSMutableArray    *uids;
71   NSArray  *attendees;
72   unsigned i, count;
73   NSString *email, *uid;
74   
75   if (![_task isNotNull])
76     return nil;
77   
78   if ((attendees = [_task attendees]) == nil)
79     return nil;
80   count = [attendees count];
81   uids = [NSMutableArray arrayWithCapacity:count + 1];
82   
83   um = [LDAPUserManager sharedUserManager];
84   
85   /* add organizer */
86   
87   email = [[_task organizer] rfc822Email];
88   if ([email isNotNull]) {
89     uid = [um getUIDForEmail:email];
90     if ([uid isNotNull]) {
91       [uids addObject:uid];
92     }
93     else
94       [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
95   }
96
97   /* add attendees */
98   
99   for (i = 0; i < count; i++) {
100     iCalPerson *person;
101     
102     person = [attendees objectAtIndex:i];
103     email  = [person rfc822Email];
104     if (![email isNotNull]) continue;
105     
106     uid = [um getUIDForEmail:email];
107     if (![uid isNotNull]) {
108       [self logWithFormat:@"Note: got no uid for email: '%@'", email];
109       continue;
110     }
111     if (![uids containsObject:uid])
112       [uids addObject:uid];
113   }
114   
115   return uids;
116 }
117
118 /* folder management */
119
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];
123 }
124
125 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
126   return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
127 }
128
129 /* store in all the other folders */
130
131 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
132   NSEnumerator *e;
133   id           folder;
134   NSException  *allErrors = nil;
135
136   e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
137              objectEnumerator];
138   while ((folder = [e nextObject]) != nil) {
139     NSException           *error;
140     SOGoTaskObject *task;
141     
142     if (![folder isNotNull]) /* no folder was found for given UID */
143       continue;
144     
145     task = [folder lookupName:[self nameInContainer] inContext: context
146                   acquire:NO];
147     if ([task isKindOfClass: [NSException class]])
148       {
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]];
153         continue;
154       }
155
156     if (![task isNotNull]) {
157       [self logWithFormat:@"Note: did not find '%@' in folder: %@",
158               [self nameInContainer], folder];
159       continue;
160     }
161     
162     if ((error = [task primarySaveContentString:_iCal]) != nil) {
163       [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
164       // TODO: make compound
165       allErrors = error;
166     }
167   }
168   return allErrors;
169 }
170 - (NSException *)deleteInUIDs:(NSArray *)_uids {
171   NSEnumerator *e;
172   id           folder;
173   NSException  *allErrors = nil;
174   
175   e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
176              objectEnumerator];
177   while ((folder = [e nextObject])) {
178     NSException           *error;
179     SOGoTaskObject *task;
180     
181     task = [folder lookupName: [self nameInContainer]
182                    inContext: context
183                    acquire: NO];
184     if (![task isNotNull]) {
185       [self logWithFormat:@"Note: did not find '%@' in folder: %@",
186               [self nameInContainer], folder];
187       continue;
188     }
189     if ([task isKindOfClass: [NSException class]]) {
190       [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
191       continue;
192     }
193     
194     if ((error = [task primaryDelete]) != nil) {
195       [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
196       // TODO: make compound
197       allErrors = error;
198     }
199   }
200   return allErrors;
201 }
202
203 /* "iCal multifolder saves" */
204
205 - (NSException *) saveContentString: (NSString *) _iCal
206                        baseSequence: (int) _v
207 {
208   /* 
209      Note: we need to delete in all participants folders and send iMIP messages
210            for all external accounts.
211      
212      Steps:
213      - fetch stored content
214      - parse old 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
220      - write to new, same
221      - delete in removed folders
222      - send iMIP mail for all folders not found
223   */
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;
234   
235 //   updateForcesReconsider = NO;
236
237 //   if ([_iCal length] == 0) {
238 //     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
239 //                      reason:@"got no iCalendar content to store!"];
240 //   }
241
242 //   um = [LDAPUserManager sharedUserManager];
243
244 //   /* handle old content */
245   
246 //   oldContent = [self contentAsString]; /* if nil, this is a new task */
247 //   if ([oldContent length] == 0)
248 //     {
249 //     /* new task */
250 //       [self debugWithFormat:@"saving new task: %@", _iCal];
251 //       oldApt = nil;
252 //     }
253 //   else
254 //     {
255 //       calendar = [iCalCalendar parseSingleFromSource: oldContent];
256 //       oldApt = [self firstTaskFromCalendar: calendar];
257 //     }
258   
259 //   /* compare sequence if requested */
260
261 //   if (_v != 0) {
262 //     // TODO
263 //   }
264   
265   
266 //   /* handle new content */
267   
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!"];
273 //   }
274   
275 //   /* diff */
276   
277 //   changes = [iCalToDoChanges changesFromEvent: oldApt
278 //                               toEvent: newApt];
279
280 //   uids        = [um getUIDsForICalPersons:[changes deletedAttendees]
281 //                     applyStrictMapping:NO];
282 //   removedUIDs = [NSMutableArray arrayWithArray:uids];
283
284 //   uids        = [um getUIDsForICalPersons:[newApt attendees]
285 //                     applyStrictMapping:NO];
286 //   storeUIDs   = [NSMutableArray arrayWithArray:uids];
287 //   props       = [changes updatedProperties];
288
289 //   /* detect whether sequence has to be increased */
290 //   if ([changes hasChanges])
291 //     [newApt increaseSequence];
292
293 //   /* preserve organizer */
294
295 //   organizer = [newApt organizer];
296 //   uid       = [um getUIDForICalPerson:organizer];
297 //   if (uid) {
298 //     if (![storeUIDs containsObject:uid])
299 //       [storeUIDs addObject:uid];
300 //     [removedUIDs removeObject:uid];
301 //   }
302
303 //   /* organizer might have changed completely */
304
305 //   if (oldApt && ([props containsObject: @"organizer"])) {
306 //     uid = [um getUIDForICalPerson:[oldApt organizer]];
307 //     if (uid) {
308 //       if (![storeUIDs containsObject:uid]) {
309 //         if (![removedUIDs containsObject:uid]) {
310 //           [removedUIDs addObject:uid];
311 //         }
312 //       }
313 //     }
314 //   }
315
316 //   [self debugWithFormat:@"UID ops:\n  store: %@\n  remove: %@",
317 //                         storeUIDs, removedUIDs];
318
319 //   /* if time did change, all participants have to re-decide ...
320 //    * ... exception from that rule: the organizer
321 //    */
322
323 //   if (oldApt != nil &&
324 //       ([props containsObject:@"startDate"] ||
325 //        [props containsObject:@"endDate"]   ||
326 //        [props containsObject:@"duration"]))
327 //   {
328 //     NSArray  *ps;
329 //     unsigned i, count;
330     
331 //     ps    = [newApt attendees];
332 //     count = [ps count];
333 //     for (i = 0; i < count; i++) {
334 //       iCalPerson *p;
335       
336 //       p = [ps objectAtIndex:i];
337 //       if (![p hasSameEmailAddress:organizer])
338 //         [p setParticipationStatus:iCalPersonPartStatNeedsAction];
339 //     }
340 //     _iCal = [[newApt parent] versitString];
341 //     updateForcesReconsider = YES;
342 //   }
343
344 //   /* perform storing */
345
346   storeError = [self primarySaveContentString: _iCal];
347
348 //   storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
349 //   delError   = [self deleteInUIDs:removedUIDs];
350
351   // TODO: make compound
352   if (storeError != nil) return storeError;
353 //   if (delError   != nil) return delError;
354
355   /* email notifications */
356 //   if ([self sendEMailNotifications])
357 //     {
358 //   attendees = [NSMutableArray arrayWithArray:[changes insertedAttendees]];
359 //   [attendees removePerson:organizer];
360 //   [self sendInvitationEMailForTask:newApt
361 //         toAttendees:attendees];
362
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];
371 //   }
372
373 //   attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
374 //   [attendees removePerson: organizer];
375 //   if ([attendees count]) {
376 //     iCalToDo *canceledApt;
377     
378 //     canceledApt = [newApt copy];
379 //     [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
380 //           [self sendEMailUsingTemplateNamed: @"Removal"
381 //                 forOldObject: nil
382 //                 andNewObject: canceledApt
383 //                 toAttendees: attendees];
384 //     [canceledApt release];
385 //   }
386 // }
387
388   return nil;
389 }
390
391 - (NSException *)deleteWithBaseSequence:(int)_v {
392   /* 
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.
397
398      Steps:
399      - fetch stored content
400      - parse old 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
405   */
406   iCalToDo *task;
407   NSArray         *removedUIDs;
408   NSMutableArray  *attendees;
409
410   /* load existing content */
411   
412   task = (iCalToDo *) [self component: NO];
413   
414   /* compare sequence if requested */
415
416   if (_v != 0) {
417     // TODO
418   }
419   
420   removedUIDs = [self attendeeUIDsFromTask:task];
421
422   if ([self sendEMailNotifications])
423     {
424       /* send notification email to attendees excluding organizer */
425       attendees = [NSMutableArray arrayWithArray:[task attendees]];
426       [attendees removePerson:[task organizer]];
427   
428       /* flag task as being canceled */
429       [(iCalCalendar *) [task parent] setMethod: @"cancel"];
430       [task increaseSequence];
431
432       /* remove all attendees to signal complete removal */
433       [task removeAllAttendees];
434
435       /* send notification email */
436       [self sendEMailUsingTemplateNamed: @"Deletion"
437             forOldObject: nil
438             andNewObject: task
439             toAttendees: attendees];
440     }
441
442   /* perform */
443   
444   return [self deleteInUIDs:removedUIDs];
445 }
446
447 - (NSException *)saveContentString:(NSString *)_iCalString {
448   return [self saveContentString:_iCalString baseSequence:0];
449 }
450
451 /* message type */
452
453 - (NSString *)outlookMessageClass {
454   return @"IPM.Task";
455 }
456
457 @end /* SOGoTaskObject */