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