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 <NGObjWeb/NSException+HTTP.h>
23 #import <NGExtensions/NSNull+misc.h>
24 #import <NGExtensions/NSObject+Logs.h>
25 #import <NGCards/iCalCalendar.h>
26 #import <NGCards/iCalEvent.h>
27 #import <NGCards/iCalEventChanges.h>
28 #import <NGCards/iCalPerson.h>
30 #import <SoObjects/SOGo/LDAPUserManager.h>
31 #import <SoObjects/SOGo/SOGoObject.h>
32 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import "NSArray+Appointments.h"
35 #import "SOGoAppointmentObject.h"
37 @implementation SOGoAppointmentObject
39 - (NSString *) componentTag
45 - (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
51 NSString *email, *uid;
53 if (![_apt isNotNull])
56 if ((attendees = [_apt attendees]) == nil)
58 count = [attendees count];
59 uids = [NSMutableArray arrayWithCapacity:count + 1];
61 um = [LDAPUserManager sharedUserManager];
65 email = [[_apt organizer] rfc822Email];
66 if ([email isNotNull]) {
67 uid = [um getUIDForEmail: email];
68 if ([uid isNotNull]) {
72 [self logWithFormat:@"Note: got no uid for organizer: '%@'", email];
77 for (i = 0; i < count; i++)
81 person = [attendees objectAtIndex:i];
82 email = [person rfc822Email];
83 if (![email isNotNull]) continue;
85 uid = [um getUIDForEmail:email];
86 if (![uid isNotNull]) {
87 [self logWithFormat:@"Note: got no uid for email: '%@'", email];
90 if (![uids containsObject:uid])
97 /* folder management */
99 - (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
100 // TODO: what does this do? lookup the home of the organizer?
101 return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
103 - (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
104 return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
107 /* store in all the other folders */
109 - (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
112 NSException *allErrors = nil;
114 e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
116 while ((folder = [e nextObject]) != nil) {
118 SOGoAppointmentObject *apt;
120 if (![folder isNotNull]) /* no folder was found for given UID */
123 apt = [folder lookupName: [self nameInContainer] inContext: context
125 if ([apt isKindOfClass: [NSException class]])
127 [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
128 [self nameInContainer], folder];
129 [self logWithFormat:@"the exception reason was: %@",
130 [(NSException *) apt reason]];
134 if (![apt isNotNull]) {
135 [self logWithFormat:@"Note: did not find '%@' in folder: %@",
136 [self nameInContainer], folder];
139 if ([apt isKindOfClass: [NSException class]]) {
140 [self logWithFormat:@"Exception: %@", [(NSException *) apt reason]];
144 if ((error = [apt primarySaveContentString:_iCal]) != nil) {
145 [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
146 // TODO: make compound
154 - (NSException *)deleteInUIDs:(NSArray *)_uids {
157 NSException *allErrors = nil;
159 e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
161 while ((folder = [e nextObject])) {
163 SOGoAppointmentObject *apt;
165 apt = [folder lookupName:[self nameInContainer] inContext: context
167 if ([apt isKindOfClass: [NSException class]]) {
168 [self logWithFormat: @"%@", [(NSException *) apt reason]];
172 if ((error = [apt primaryDelete]) != nil) {
173 [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
174 // TODO: make compound
181 /* "iCal multifolder saves" */
183 - (NSException *) saveContentString: (NSString *) _iCal
184 baseSequence: (int) _v
187 Note: we need to delete in all participants folders and send iMIP messages
188 for all external accounts.
191 - fetch stored content
193 - check if sequence matches (or if 0=ignore)
194 - extract old attendee list + organizer (make unique)
195 - parse new content (ensure that sequence is increased!)
196 - extract new attendee list + organizer (make unique)
197 - make a diff => new, same, removed
199 - delete in removed folders
200 - send iMIP mail for all folders not found
203 iCalEvent *oldApt, *newApt;
204 iCalEventChanges *changes;
205 iCalPerson *organizer;
206 NSString *oldContent, *uid;
207 NSArray *uids, *props;
208 NSMutableArray *attendees, *storeUIDs, *removedUIDs;
209 NSException *storeError, *delError;
210 BOOL updateForcesReconsider;
212 updateForcesReconsider = NO;
214 if ([_iCal length] == 0)
215 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
216 reason: @"got no iCalendar content to store!"];
218 um = [LDAPUserManager sharedUserManager];
220 /* handle old content */
222 oldContent = [self contentAsString]; /* if nil, this is a new appointment */
223 if ([oldContent length] == 0)
225 /* new appointment */
226 [self debugWithFormat:@"saving new appointment: %@", _iCal];
230 oldApt = (iCalEvent *) [self component: NO];
232 /* compare sequence if requested */
238 /* handle new content */
240 newApt = (iCalEvent *) [self component: NO];
242 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
243 reason: @"could not parse iCalendar content!"];
247 changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
248 uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
249 removedUIDs = [NSMutableArray arrayWithArray: uids];
251 uids = [self getUIDsForICalPersons: [newApt attendees]];
252 storeUIDs = [NSMutableArray arrayWithArray: uids];
253 props = [changes updatedProperties];
255 /* detect whether sequence has to be increased */
256 if ([changes hasChanges])
257 [newApt increaseSequence];
259 /* preserve organizer */
261 organizer = [newApt organizer];
262 uid = [self getUIDForICalPerson: organizer];
264 uid = [self ownerInContext: nil];
266 if (![storeUIDs containsObject:uid])
267 [storeUIDs addObject:uid];
268 [removedUIDs removeObject:uid];
271 /* organizer might have changed completely */
273 if (oldApt && ([props containsObject: @"organizer"])) {
274 uid = [self getUIDForICalPerson:[oldApt organizer]];
276 if (![storeUIDs containsObject:uid]) {
277 if (![removedUIDs containsObject:uid]) {
278 [removedUIDs addObject:uid];
284 [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
285 storeUIDs, removedUIDs];
287 /* if time did change, all participants have to re-decide ...
288 * ... exception from that rule: the organizer
292 ([props containsObject: @"startDate"] ||
293 [props containsObject: @"endDate"] ||
294 [props containsObject: @"duration"]))
299 ps = [newApt attendees];
301 for (i = 0; i < count; i++) {
304 p = [ps objectAtIndex:i];
305 if (![p hasSameEmailAddress:organizer])
306 [p setParticipationStatus:iCalPersonPartStatNeedsAction];
308 _iCal = [[newApt parent] versitString];
309 updateForcesReconsider = YES;
312 /* perform storing */
314 storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
315 delError = [self deleteInUIDs: removedUIDs];
317 // TODO: make compound
318 if (storeError != nil) return storeError;
319 if (delError != nil) return delError;
321 /* email notifications */
322 if ([self sendEMailNotifications])
324 attendees = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
325 [attendees removePerson: organizer];
326 [self sendEMailUsingTemplateNamed: @"Invitation"
329 toAttendees: attendees];
331 if (updateForcesReconsider) {
332 attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
333 [attendees removeObjectsInArray:[changes insertedAttendees]];
334 [attendees removePerson:organizer];
335 [self sendEMailUsingTemplateNamed: @"Update"
338 toAttendees: attendees];
341 attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
342 [attendees removePerson: organizer];
343 if ([attendees count])
345 iCalEvent *canceledApt;
347 canceledApt = [newApt copy];
348 [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
349 [self sendEMailUsingTemplateNamed: @"Removal"
351 andNewObject: canceledApt
352 toAttendees: attendees];
353 [canceledApt release];
360 - (NSException *)deleteWithBaseSequence:(int)_v {
362 Note: We need to delete in all participants folders and send iMIP messages
363 for all external accounts.
364 Delete is basically identical to save with all attendees and the
365 organizer being deleted.
368 - fetch stored content
370 - check if sequence matches (or if 0=ignore)
371 - extract old attendee list + organizer (make unique)
372 - delete in removed folders
373 - send iMIP mail for all folders not found
376 NSMutableArray *attendees, *removedUIDs;
378 /* load existing content */
380 apt = (iCalEvent *) [self component: NO];
382 /* compare sequence if requested */
388 removedUIDs = [NSMutableArray arrayWithArray:
389 [self attendeeUIDsFromAppointment: apt]];
390 if (![removedUIDs containsObject: owner])
391 [removedUIDs addObject: owner];
393 if ([self sendEMailNotifications])
395 /* send notification email to attendees excluding organizer */
396 attendees = [NSMutableArray arrayWithArray:[apt attendees]];
397 [attendees removePerson:[apt organizer]];
399 /* flag appointment as being canceled */
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];
415 return [self deleteInUIDs: removedUIDs];
418 - (NSException *) saveContentString: (NSString *) _iCalString
420 return [self saveContentString: _iCalString baseSequence: 0];
425 - (NSString *) outlookMessageClass
427 return @"IPM.Appointment";
430 @end /* SOGoAppointmentObject */