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