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/NSCalendarDate.h>
24 #import <NGObjWeb/NSException+HTTP.h>
25 #import <NGObjWeb/WOContext.h>
26 #import <NGExtensions/NSNull+misc.h>
27 #import <NGExtensions/NSObject+Logs.h>
28 #import <NGCards/iCalCalendar.h>
29 #import <NGCards/iCalEvent.h>
30 #import <NGCards/iCalEventChanges.h>
31 #import <NGCards/iCalPerson.h>
33 #import <SoObjects/SOGo/LDAPUserManager.h>
34 #import <SoObjects/SOGo/SOGoObject.h>
35 #import <SoObjects/SOGo/SOGoPermissions.h>
36 #import <SoObjects/SOGo/WORequest+SOGo.h>
38 #import "NSArray+Appointments.h"
39 #import "SOGoAppointmentFolder.h"
41 #import "SOGoAppointmentObject.h"
43 @implementation SOGoAppointmentObject
45 - (NSString *) componentTag
51 - (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
57 NSString *email, *uid;
59 if (![_apt isNotNull])
62 if ((attendees = [_apt attendees]) == nil)
64 count = [attendees count];
65 uids = [NSMutableArray arrayWithCapacity:count + 1];
67 um = [LDAPUserManager sharedUserManager];
71 email = [[_apt organizer] rfc822Email];
72 if ([email isNotNull]) {
73 uid = [um getUIDForEmail: email];
74 if ([uid isNotNull]) {
78 [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
83 for (i = 0; i < count; i++)
87 person = [attendees objectAtIndex:i];
88 email = [person rfc822Email];
89 if (![email isNotNull]) continue;
91 uid = [um getUIDForEmail:email];
92 if (![uid isNotNull]) {
93 [self logWithFormat:@"Note: got no uid for email: '%@'", email];
96 if (![uids containsObject:uid])
103 /* store in all the other folders */
105 - (NSException *) saveContentString: (NSString *) _iCal
106 inUIDs: (NSArray *) _uids
110 NSException *allErrors = nil;
112 SOGoAppointmentObject *apt;
114 e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
116 while ((folder = [e nextObject]))
118 apt = [SOGoAppointmentObject objectWithName: nameInContainer
119 inContainer: folder];
120 error = [apt primarySaveContentString:_iCal];
123 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
124 // TODO: make compound
132 - (NSException *) deleteInUIDs: (NSArray *) _uids
136 NSException *allErrors = nil;
138 SOGoAppointmentObject *apt;
140 e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
142 while ((folder = [e nextObject]))
144 apt = [folder lookupName: [self nameInContainer]
147 if ([apt isKindOfClass: [NSException class]]) {
148 [self logWithFormat: @"%@", [(NSException *) apt reason]];
152 if ((error = [apt primaryDelete]) != nil) {
153 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
154 // TODO: make compound
162 /* "iCal multifolder saves" */
163 - (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
167 now = [NSCalendarDate calendarDate];
169 return ([[appointment endDate] earlierDate: now] == now);
172 - (NSException *) saveContentString: (NSString *) _iCal
173 baseSequence: (int) _v
176 Note: we need to delete in all participants folders and send iMIP messages
177 for all external accounts.
180 - fetch stored content
182 - check if sequence matches (or if 0=ignore)
183 - extract old attendee list + organizer (make unique)
184 - parse new content (ensure that sequence is increased!)
185 - extract new attendee list + organizer (make unique)
186 - make a diff => new, same, removed
188 - delete in removed folders
189 - send iMIP mail for all folders not found
192 iCalEvent *oldApt, *newApt;
193 iCalEventChanges *changes;
194 iCalPerson *organizer;
195 NSString *oldContent, *uid;
196 NSArray *uids, *props;
197 NSMutableArray *attendees, *storeUIDs, *removedUIDs;
198 NSException *storeError, *delError;
199 BOOL updateForcesReconsider;
201 if ([[context request] handledByDefaultHandler])
203 updateForcesReconsider = NO;
205 if ([_iCal length] == 0)
206 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
207 reason: @"got no iCalendar content to store!"];
209 um = [LDAPUserManager sharedUserManager];
211 /* handle old content */
213 oldContent = [self contentAsString]; /* if nil, this is a new appointment */
214 if ([oldContent length] == 0)
216 /* new appointment */
217 [self debugWithFormat:@"saving new appointment: %@", _iCal];
221 oldApt = (iCalEvent *) [self component: NO];
223 /* compare sequence if requested */
228 /* handle new content */
230 newApt = (iCalEvent *) [self component: NO];
232 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
233 reason: @"could not parse iCalendar content!"];
237 changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
238 uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
239 removedUIDs = [NSMutableArray arrayWithArray: uids];
241 uids = [self getUIDsForICalPersons: [newApt attendees]];
242 storeUIDs = [NSMutableArray arrayWithArray: uids];
243 props = [changes updatedProperties];
245 /* detect whether sequence has to be increased */
246 if ([changes hasChanges])
247 [newApt increaseSequence];
249 /* preserve organizer */
251 organizer = [newApt organizer];
252 uid = [self getUIDForICalPerson: organizer];
254 uid = [self ownerInContext: nil];
256 if (![storeUIDs containsObject:uid])
257 [storeUIDs addObject:uid];
258 [removedUIDs removeObject:uid];
261 /* organizer might have changed completely */
263 if (oldApt && ([props containsObject: @"organizer"])) {
264 uid = [self getUIDForICalPerson:[oldApt organizer]];
266 if (![storeUIDs containsObject:uid]) {
267 if (![removedUIDs containsObject:uid]) {
268 [removedUIDs addObject:uid];
274 [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
275 storeUIDs, removedUIDs];
277 /* if time did change, all participants have to re-decide ...
278 * ... exception from that rule: the organizer
282 ([props containsObject: @"startDate"] ||
283 [props containsObject: @"endDate"] ||
284 [props containsObject: @"duration"]))
289 ps = [newApt attendees];
291 for (i = 0; i < count; i++) {
294 p = [ps objectAtIndex:i];
295 if (![p hasSameEmailAddress:organizer])
296 [p setParticipationStatus:iCalPersonPartStatNeedsAction];
298 _iCal = [[newApt parent] versitString];
299 updateForcesReconsider = YES;
302 /* perform storing */
304 storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
305 delError = [self deleteInUIDs: removedUIDs];
307 // TODO: make compound
308 if (storeError != nil) return storeError;
309 if (delError != nil) return delError;
311 /* email notifications */
312 if ([self sendEMailNotifications]
313 && [self _aptIsStillRelevant: newApt])
316 = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
317 [attendees removePerson: organizer];
318 [self sendEMailUsingTemplateNamed: @"Invitation"
321 toAttendees: attendees];
323 if (updateForcesReconsider) {
324 attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
325 [attendees removeObjectsInArray:[changes insertedAttendees]];
326 [attendees removePerson:organizer];
327 [self sendEMailUsingTemplateNamed: @"Update"
330 toAttendees: attendees];
334 = [NSMutableArray arrayWithArray: [changes deletedAttendees]];
335 [attendees removePerson: organizer];
336 if ([attendees count])
338 iCalEvent *cancelledApt;
340 cancelledApt = [newApt copy];
341 [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
342 [self sendEMailUsingTemplateNamed: @"Removal"
344 andNewObject: cancelledApt
345 toAttendees: attendees];
346 [cancelledApt release];
351 [self primarySaveContentString: _iCal];
356 - (NSException *) deleteWithBaseSequence: (int)_v
359 Note: We need to delete in all participants folders and send iMIP messages
360 for all external accounts.
361 Delete is basically identical to save with all attendees and the
362 organizer being deleted.
365 - fetch stored content
367 - check if sequence matches (or if 0=ignore)
368 - extract old attendee list + organizer (make unique)
369 - delete in removed folders
370 - send iMIP mail for all folders not found
373 NSMutableArray *attendees, *removedUIDs;
376 if ([[context request] handledByDefaultHandler])
378 /* load existing content */
379 apt = (iCalEvent *) [self component: NO];
381 /* compare sequence if requested */
387 removedUIDs = [NSMutableArray arrayWithArray:
388 [self attendeeUIDsFromAppointment: apt]];
389 if (![removedUIDs containsObject: owner])
390 [removedUIDs addObject: owner];
392 if ([self sendEMailNotifications]
393 && [self _aptIsStillRelevant: apt])
395 /* send notification email to attendees excluding organizer */
396 attendees = [NSMutableArray arrayWithArray:[apt attendees]];
397 [attendees removePerson:[apt organizer]];
399 /* flag appointment as being cancelled */
400 [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
401 [apt increaseSequence];
403 /* remove all attendees to signal complete removal */
404 [apt removeAllAttendees];
406 /* send notification email */
407 [self sendEMailUsingTemplateNamed: @"Deletion"
410 toAttendees: attendees];
413 error = [self deleteInUIDs: removedUIDs];
416 error = [self primaryDelete];
421 - (NSException *) saveContentString: (NSString *) _iCalString
423 return [self saveContentString: _iCalString baseSequence: 0];
428 - (NSString *) outlookMessageClass
430 return @"IPM.Appointment";
433 @end /* SOGoAppointmentObject */