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 <NGExtensions/NSNull+misc.h>
26 #import <NGExtensions/NSObject+Logs.h>
27 #import <NGCards/iCalCalendar.h>
28 #import <NGCards/iCalEvent.h>
29 #import <NGCards/iCalEventChanges.h>
30 #import <NGCards/iCalPerson.h>
32 #import <SoObjects/SOGo/LDAPUserManager.h>
33 #import <SoObjects/SOGo/SOGoObject.h>
34 #import <SoObjects/SOGo/SOGoPermissions.h>
36 #import "NSArray+Appointments.h"
37 #import "SOGoAppointmentObject.h"
39 @implementation SOGoAppointmentObject
41 - (NSString *) componentTag
47 - (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
53 NSString *email, *uid;
55 if (![_apt isNotNull])
58 if ((attendees = [_apt attendees]) == nil)
60 count = [attendees count];
61 uids = [NSMutableArray arrayWithCapacity:count + 1];
63 um = [LDAPUserManager sharedUserManager];
67 email = [[_apt organizer] rfc822Email];
68 if ([email isNotNull]) {
69 uid = [um getUIDForEmail: email];
70 if ([uid isNotNull]) {
74 [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
79 for (i = 0; i < count; i++)
83 person = [attendees objectAtIndex:i];
84 email = [person rfc822Email];
85 if (![email isNotNull]) continue;
87 uid = [um getUIDForEmail:email];
88 if (![uid isNotNull]) {
89 [self logWithFormat:@"Note: got no uid for email: '%@'", email];
92 if (![uids containsObject:uid])
99 /* folder management */
101 - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
102 // TODO: what does this do? lookup the home of the organizer?
103 return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
105 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
106 return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
109 /* store in all the other folders */
111 - (NSException *) saveContentString: (NSString *) _iCal
112 inUIDs: (NSArray *) _uids
116 NSException *allErrors = nil;
118 e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
120 while ((folder = [e nextObject]) != nil) {
122 SOGoAppointmentObject *apt;
124 if (![folder isNotNull]) /* no folder was found for given UID */
127 apt = [folder lookupName: [self nameInContainer] inContext: context
129 if ([apt isKindOfClass: [NSException class]])
131 [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
132 [self nameInContainer], folder];
133 [self logWithFormat:@"the exception reason was: %@",
134 [(NSException *) apt reason]];
138 if (![apt isNotNull]) {
139 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
140 [self nameInContainer], folder];
143 if ([apt isKindOfClass: [NSException class]]) {
144 [self logWithFormat:@"Exception: %@", [(NSException *) apt reason]];
148 if ((error = [apt primarySaveContentString:_iCal]) != nil) {
149 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
150 // TODO: make compound
158 - (NSException *)deleteInUIDs:(NSArray *)_uids {
161 NSException *allErrors = nil;
163 e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
165 while ((folder = [e nextObject])) {
167 SOGoAppointmentObject *apt;
169 apt = [folder lookupName:[self nameInContainer] inContext: context
171 if ([apt isKindOfClass: [NSException class]]) {
172 [self logWithFormat: @"%@", [(NSException *) apt reason]];
176 if ((error = [apt primaryDelete]) != nil) {
177 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
178 // TODO: make compound
185 /* "iCal multifolder saves" */
186 - (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
190 now = [NSCalendarDate calendarDate];
192 return ([[appointment endDate] earlierDate: now] == now);
195 - (NSException *) saveContentString: (NSString *) _iCal
196 baseSequence: (int) _v
199 Note: we need to delete in all participants folders and send iMIP messages
200 for all external accounts.
203 - fetch stored content
205 - check if sequence matches (or if 0=ignore)
206 - extract old attendee list + organizer (make unique)
207 - parse new content (ensure that sequence is increased!)
208 - extract new attendee list + organizer (make unique)
209 - make a diff => new, same, removed
211 - delete in removed folders
212 - send iMIP mail for all folders not found
215 iCalEvent *oldApt, *newApt;
216 iCalEventChanges *changes;
217 iCalPerson *organizer;
218 NSString *oldContent, *uid;
219 NSArray *uids, *props;
220 NSMutableArray *attendees, *storeUIDs, *removedUIDs;
221 NSException *storeError, *delError;
222 BOOL updateForcesReconsider;
224 updateForcesReconsider = NO;
226 if ([_iCal length] == 0)
227 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
228 reason: @"got no iCalendar content to store!"];
230 um = [LDAPUserManager sharedUserManager];
232 /* handle old content */
234 oldContent = [self contentAsString]; /* if nil, this is a new appointment */
235 if ([oldContent length] == 0)
237 /* new appointment */
238 [self debugWithFormat:@"saving new appointment: %@", _iCal];
242 oldApt = (iCalEvent *) [self component: NO];
244 /* compare sequence if requested */
250 /* handle new content */
252 newApt = (iCalEvent *) [self component: NO];
254 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
255 reason: @"could not parse iCalendar content!"];
259 changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
260 uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
261 removedUIDs = [NSMutableArray arrayWithArray: uids];
263 uids = [self getUIDsForICalPersons: [newApt attendees]];
264 storeUIDs = [NSMutableArray arrayWithArray: uids];
265 props = [changes updatedProperties];
267 /* detect whether sequence has to be increased */
268 if ([changes hasChanges])
269 [newApt increaseSequence];
271 /* preserve organizer */
273 organizer = [newApt organizer];
274 uid = [self getUIDForICalPerson: organizer];
276 uid = [self ownerInContext: nil];
278 if (![storeUIDs containsObject:uid])
279 [storeUIDs addObject:uid];
280 [removedUIDs removeObject:uid];
283 /* organizer might have changed completely */
285 if (oldApt && ([props containsObject: @"organizer"])) {
286 uid = [self getUIDForICalPerson:[oldApt organizer]];
288 if (![storeUIDs containsObject:uid]) {
289 if (![removedUIDs containsObject:uid]) {
290 [removedUIDs addObject:uid];
296 [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
297 storeUIDs, removedUIDs];
299 /* if time did change, all participants have to re-decide ...
300 * ... exception from that rule: the organizer
304 ([props containsObject: @"startDate"] ||
305 [props containsObject: @"endDate"] ||
306 [props containsObject: @"duration"]))
311 ps = [newApt attendees];
313 for (i = 0; i < count; i++) {
316 p = [ps objectAtIndex:i];
317 if (![p hasSameEmailAddress:organizer])
318 [p setParticipationStatus:iCalPersonPartStatNeedsAction];
320 _iCal = [[newApt parent] versitString];
321 updateForcesReconsider = YES;
324 /* perform storing */
326 storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
327 delError = [self deleteInUIDs: removedUIDs];
329 // TODO: make compound
330 if (storeError != nil) return storeError;
331 if (delError != nil) return delError;
333 /* email notifications */
334 if ([self sendEMailNotifications]
335 && [self _aptIsStillRelevant: newApt])
338 = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
339 [attendees removePerson: organizer];
340 [self sendEMailUsingTemplateNamed: @"Invitation"
343 toAttendees: attendees];
345 if (updateForcesReconsider) {
346 attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
347 [attendees removeObjectsInArray:[changes insertedAttendees]];
348 [attendees removePerson:organizer];
349 [self sendEMailUsingTemplateNamed: @"Update"
352 toAttendees: attendees];
356 = [NSMutableArray arrayWithArray: [changes deletedAttendees]];
357 [attendees removePerson: organizer];
358 if ([attendees count])
360 iCalEvent *cancelledApt;
362 cancelledApt = [newApt copy];
363 [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
364 [self sendEMailUsingTemplateNamed: @"Removal"
366 andNewObject: cancelledApt
367 toAttendees: attendees];
368 [cancelledApt release];
375 - (NSException *)deleteWithBaseSequence:(int)_v {
377 Note: We need to delete in all participants folders and send iMIP messages
378 for all external accounts.
379 Delete is basically identical to save with all attendees and the
380 organizer being deleted.
383 - fetch stored content
385 - check if sequence matches (or if 0=ignore)
386 - extract old attendee list + organizer (make unique)
387 - delete in removed folders
388 - send iMIP mail for all folders not found
391 NSMutableArray *attendees, *removedUIDs;
393 /* load existing content */
395 apt = (iCalEvent *) [self component: NO];
397 /* compare sequence if requested */
403 removedUIDs = [NSMutableArray arrayWithArray:
404 [self attendeeUIDsFromAppointment: apt]];
405 if (![removedUIDs containsObject: owner])
406 [removedUIDs addObject: owner];
408 if ([self sendEMailNotifications])
410 /* send notification email to attendees excluding organizer */
411 attendees = [NSMutableArray arrayWithArray:[apt attendees]];
412 [attendees removePerson:[apt organizer]];
414 /* flag appointment as being cancelled */
415 [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
416 [apt increaseSequence];
418 /* remove all attendees to signal complete removal */
419 [apt removeAllAttendees];
421 /* send notification email */
422 [self sendEMailUsingTemplateNamed: @"Deletion"
425 toAttendees: attendees];
430 return [self deleteInUIDs: removedUIDs];
433 - (NSException *) saveContentString: (NSString *) _iCalString
435 return [self saveContentString: _iCalString baseSequence: 0];
440 - (NSString *) outlookMessageClass
442 return @"IPM.Appointment";
445 @end /* SOGoAppointmentObject */