02111-1307, USA.
*/
-#import "SOGoAppointmentObject.h"
+#import <Foundation/NSCalendarDate.h>
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGObjWeb/WOContext.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalEventChanges.h>
#import <NGCards/iCalPerson.h>
-#import <SOGo/AgenorUserManager.h>
-#import <SOGo/SOGoObject.h>
-
-#import "iCalEntityObject+Agenor.h"
-
-#import "common.h"
+#import <SoObjects/SOGo/LDAPUserManager.h>
+#import <SoObjects/SOGo/NSArray+Utilities.h>
+#import <SoObjects/SOGo/SOGoObject.h>
+#import <SoObjects/SOGo/SOGoPermissions.h>
+#import <SoObjects/SOGo/WORequest+SOGo.h>
#import "NSArray+Appointments.h"
+#import "SOGoAppointmentFolder.h"
-@implementation SOGoAppointmentObject
+#import "SOGoAppointmentObject.h"
-/* accessors */
+@implementation SOGoAppointmentObject
-- (iCalEvent *) event
+- (NSString *) componentTag
{
- return [self firstEventFromCalendar: [self calendar]];
+ return @"vevent";
}
/* iCal handling */
- (NSArray *) attendeeUIDsFromAppointment: (iCalEvent *) _apt
{
- AgenorUserManager *um;
+ LDAPUserManager *um;
NSMutableArray *uids;
NSArray *attendees;
unsigned i, count;
count = [attendees count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
- um = [AgenorUserManager sharedUserManager];
+ um = [LDAPUserManager sharedUserManager];
/* add organizer */
email = [[_apt organizer] rfc822Email];
if ([email isNotNull]) {
- uid = [um getUIDForEmail:email];
+ uid = [um getUIDForEmail: email];
if ([uid isNotNull]) {
[uids addObject:uid];
}
/* add attendees */
- for (i = 0; i < count; i++) {
- iCalPerson *person;
+ for (i = 0; i < count; i++)
+ {
+ iCalPerson *person;
- person = [attendees objectAtIndex:i];
- email = [person rfc822Email];
- if (![email isNotNull]) continue;
+ person = [attendees objectAtIndex:i];
+ email = [person rfc822Email];
+ if (![email isNotNull]) continue;
- uid = [um getUIDForEmail:email];
- if (![uid isNotNull]) {
- [self logWithFormat:@"Note: got no uid for email: '%@'", email];
- continue;
+ uid = [um getUIDForEmail:email];
+ if (![uid isNotNull]) {
+ [self logWithFormat:@"Note: got no uid for email: '%@'", email];
+ continue;
+ }
+ if (![uids containsObject:uid])
+ [uids addObject:uid];
}
- if (![uids containsObject:uid])
- [uids addObject:uid];
- }
-
- return uids;
-}
-
-/* folder management */
-- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
- // TODO: what does this do? lookup the home of the organizer?
- return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
-}
-- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
- return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
+ return uids;
}
/* store in all the other folders */
-- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
+- (NSException *) saveContentString: (NSString *) _iCal
+ inUIDs: (NSArray *) _uids
+{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
- id ctx;
-
- ctx = [[WOApplication application] context];
-
- e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
- objectEnumerator];
- while ((folder = [e nextObject]) != nil) {
- NSException *error;
- SOGoAppointmentObject *apt;
-
- if (![folder isNotNull]) /* no folder was found for given UID */
- continue;
-
- apt = [folder lookupName: [self nameInContainer] inContext:ctx
- acquire: NO];
- if ([apt isKindOfClass: [NSException class]])
- {
- [self logWithFormat:@"Note: an exception occured finding '%@' in folder: %@",
- [self nameInContainer], folder];
- [self logWithFormat:@"the exception reason was: %@",
- [(NSException *) apt reason]];
- continue;
- }
+ NSException *error;
+ SOGoAppointmentObject *apt;
- if (![apt isNotNull]) {
- [self logWithFormat:@"Note: did not find '%@' in folder: %@",
- [self nameInContainer], folder];
- continue;
- }
- if ([apt isKindOfClass: [NSException class]]) {
- [self logWithFormat:@"Exception: %@", [(NSException *) apt reason]];
- continue;
- }
-
- if ((error = [apt primarySaveContentString:_iCal]) != nil) {
- [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
- // TODO: make compound
- allErrors = error;
+ e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
+ objectEnumerator];
+ while ((folder = [e nextObject]))
+ {
+ apt = [SOGoAppointmentObject objectWithName: nameInContainer
+ inContainer: folder];
+ error = [apt primarySaveContentString:_iCal];
+ if (error)
+ {
+ [self logWithFormat:@"Note: failed to save iCal in folder: %@", folder];
+ // TODO: make compound
+ allErrors = error;
+ }
}
- }
return allErrors;
}
-- (NSException *)deleteInUIDs:(NSArray *)_uids {
+- (NSException *) deleteInUIDs: (NSArray *) _uids
+{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
- id ctx;
+ NSException *error;
+ SOGoAppointmentObject *apt;
- ctx = [[WOApplication application] context];
-
- e = [[self lookupCalendarFoldersForUIDs:_uids inContext:ctx]
- objectEnumerator];
- while ((folder = [e nextObject])) {
- NSException *error;
- SOGoAppointmentObject *apt;
-
- apt = [folder lookupName:[self nameInContainer] inContext:ctx
- acquire:NO];
- if ([apt isKindOfClass: [NSException class]]) {
- [self logWithFormat: @"%@", [(NSException *) apt reason]];
- continue;
- }
+ e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
+ objectEnumerator];
+ while ((folder = [e nextObject]))
+ {
+ apt = [folder lookupName: [self nameInContainer]
+ inContext: context
+ acquire:NO];
+ if ([apt isKindOfClass: [NSException class]]) {
+ [self logWithFormat: @"%@", [(NSException *) apt reason]];
+ continue;
+ }
- if ((error = [apt primaryDelete]) != nil) {
- [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
- // TODO: make compound
- allErrors = error;
+ if ((error = [apt primaryDelete]) != nil) {
+ [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
+ // TODO: make compound
+ allErrors = error;
+ }
}
- }
+
return allErrors;
}
-- (iCalEvent *) firstEventFromCalendar: (iCalCalendar *) aCalendar
+/* "iCal multifolder saves" */
+- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
{
- iCalEvent *event;
- NSArray *events;
+ NSCalendarDate *now;
- events = [aCalendar childrenWithTag: @"vevent"];
- if ([events count])
- event = (iCalEvent *) [[events objectAtIndex: 0]
- groupWithClass: [iCalEvent class]];
- else
- event = nil;
+ now = [NSCalendarDate calendarDate];
- return event;
+ return ([[appointment endDate] earlierDate: now] == now);
}
-/* "iCal multifolder saves" */
-
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v
{
- delete in removed folders
- send iMIP mail for all folders not found
*/
- AgenorUserManager *um;
- iCalCalendar *newCalendar;
+ LDAPUserManager *um;
iCalEvent *oldApt, *newApt;
iCalEventChanges *changes;
iCalPerson *organizer;
NSException *storeError, *delError;
BOOL updateForcesReconsider;
- updateForcesReconsider = NO;
-
- if ([_iCal length] == 0) {
- return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
- reason:@"got no iCalendar content to store!"];
- }
+ if ([[context request] handledByDefaultHandler])
+ {
+ updateForcesReconsider = NO;
- um = [AgenorUserManager sharedUserManager];
+ if ([_iCal length] == 0)
+ return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
+ reason: @"got no iCalendar content to store!"];
- /* handle old content */
-
- oldContent = [self contentAsString]; /* if nil, this is a new appointment */
- if ([oldContent length] == 0)
- {
- /* new appointment */
- [self debugWithFormat:@"saving new appointment: %@", _iCal];
- oldApt = nil;
- }
- else
- oldApt = [self firstEventFromCalendar: [self calendar]];
-
- /* compare sequence if requested */
+ um = [LDAPUserManager sharedUserManager];
- if (_v != 0) {
- // TODO
- }
-
+ /* handle old content */
- /* handle new content */
+ oldContent = [self contentAsString]; /* if nil, this is a new appointment */
+ if ([oldContent length] == 0)
+ {
+ /* new appointment */
+ [self debugWithFormat:@"saving new appointment: %@", _iCal];
+ oldApt = nil;
+ }
+ else
+ oldApt = (iCalEvent *) [self component: NO];
- newCalendar = [iCalCalendar parseSingleFromSource: _iCal];
- newApt = [self firstEventFromCalendar: newCalendar];
- if (newApt == nil) {
- return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
- reason:@"could not parse iCalendar content!"];
- }
+ /* compare sequence if requested */
+ if (_v != 0) {
+ // TODO
+ }
- /* diff */
+ /* handle new content */
- changes = [iCalEventChanges changesFromEvent: oldApt
- toEvent: newApt];
-
- uids = [um getUIDsForICalPersons:[changes deletedAttendees]
- applyStrictMapping:NO];
- removedUIDs = [NSMutableArray arrayWithArray:uids];
-
- uids = [um getUIDsForICalPersons:[newApt attendees]
- applyStrictMapping:NO];
- storeUIDs = [NSMutableArray arrayWithArray:uids];
- props = [changes updatedProperties];
-
- /* detect whether sequence has to be increased */
- if ([changes hasChanges])
- [newApt increaseSequence];
-
- /* preserve organizer */
-
- organizer = [newApt organizer];
- uid = [um getUIDForICalPerson:organizer];
- if (uid) {
- if (![storeUIDs containsObject:uid])
- [storeUIDs addObject:uid];
- [removedUIDs removeObject:uid];
- }
-
- /* organizer might have changed completely */
-
- if (oldApt && ([props containsObject: @"organizer"])) {
- uid = [um getUIDForICalPerson:[oldApt organizer]];
- if (uid) {
- if (![storeUIDs containsObject:uid]) {
- if (![removedUIDs containsObject:uid]) {
- [removedUIDs addObject:uid];
- }
- }
- }
- }
-
- [self debugWithFormat:@"UID ops:\n store: %@\n remove: %@",
- storeUIDs, removedUIDs];
-
- /* if time did change, all participants have to re-decide ...
- * ... exception from that rule: the organizer
- */
+ newApt = (iCalEvent *) [self component: NO];
+ if (!newApt)
+ return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
+ reason: @"could not parse iCalendar content!"];
- if (oldApt != nil &&
- ([props containsObject:@"startDate"] ||
- [props containsObject:@"endDate"] ||
- [props containsObject:@"duration"]))
- {
- NSArray *ps;
- unsigned i, count;
+ /* diff */
+
+ changes = [iCalEventChanges changesFromEvent: oldApt toEvent: newApt];
+ uids = [self getUIDsForICalPersons: [changes deletedAttendees]];
+ removedUIDs = [NSMutableArray arrayWithArray: uids];
+
+ uids = [self getUIDsForICalPersons: [newApt attendees]];
+ storeUIDs = [NSMutableArray arrayWithArray: uids];
+ props = [changes updatedProperties];
+
+ /* detect whether sequence has to be increased */
+ if ([changes hasChanges])
+ [newApt increaseSequence];
+
+ /* preserve organizer */
+
+ organizer = [newApt organizer];
+ uid = [self getUIDForICalPerson: organizer];
+ if (!uid)
+ uid = [self ownerInContext: nil];
+ if (uid)
+ {
+ [storeUIDs addObjectUniquely: uid];
+ [removedUIDs removeObject: uid];
+ }
+
+ /* organizer might have changed completely */
+
+ if (oldApt && ([props containsObject: @"organizer"]))
+ {
+ uid = [self getUIDForICalPerson: [oldApt organizer]];
+ if (uid && ![storeUIDs containsObject: uid])
+ [removedUIDs addObjectUniquely: uid];
+ }
+
+ [self debugWithFormat: @"UID ops:\n store: %@\n remove: %@",
+ storeUIDs, removedUIDs];
+
+ /* if time did change, all participants have to re-decide ...
+ * ... exception from that rule: the organizer
+ */
+
+ if (oldApt
+ && ([props containsObject: @"startDate"]
+ || [props containsObject: @"endDate"]
+ || [props containsObject: @"duration"]))
+ {
+ NSArray *ps;
+ unsigned i, count;
- ps = [newApt attendees];
- count = [ps count];
- for (i = 0; i < count; i++) {
- iCalPerson *p;
+ ps = [newApt attendees];
+ count = [ps count];
+ for (i = 0; i < count; i++) {
+ iCalPerson *p;
- p = [ps objectAtIndex:i];
- if (![p hasSameEmailAddress:organizer])
- [p setParticipationStatus:iCalPersonPartStatNeedsAction];
- }
- _iCal = [[newApt parent] versitString];
- updateForcesReconsider = YES;
- }
-
- /* perform storing */
+ p = [ps objectAtIndex:i];
+ if (![p hasSameEmailAddress:organizer])
+ [p setParticipationStatus:iCalPersonPartStatNeedsAction];
+ }
+ _iCal = [[newApt parent] versitString];
+ updateForcesReconsider = YES;
+ }
- storeError = [self saveContentString:_iCal inUIDs:storeUIDs];
- delError = [self deleteInUIDs:removedUIDs];
+ /* perform storing */
- // TODO: make compound
- if (storeError != nil) return storeError;
- if (delError != nil) return delError;
-
- /* email notifications */
- if ([self sendEMailNotifications])
- {
- attendees = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
- [attendees removePerson: organizer];
- [self sendEMailUsingTemplateNamed: @"Invitation"
- forOldObject: nil
- andNewObject: newApt
- toAttendees: attendees];
-
- if (updateForcesReconsider) {
- attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
- [attendees removeObjectsInArray:[changes insertedAttendees]];
- [attendees removePerson:organizer];
- [self sendEMailUsingTemplateNamed: @"Update"
- forOldObject: oldApt
- andNewObject: newApt
- toAttendees: attendees];
- }
+ storeError = [self saveContentString: _iCal inUIDs: storeUIDs];
+ delError = [self deleteInUIDs: removedUIDs];
- attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
- [attendees removePerson: organizer];
- if ([attendees count])
- {
- iCalEvent *canceledApt;
+ // TODO: make compound
+ if (storeError != nil) return storeError;
+ if (delError != nil) return delError;
+
+ /* email notifications */
+ if ([self sendEMailNotifications]
+ && [self _aptIsStillRelevant: newApt])
+ {
+ iCalEvent *requestApt;
+
+ requestApt = [newApt copy];
+ [(iCalCalendar *) [requestApt parent] setMethod: @"request"];
+ attendees
+ = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
+ [attendees removePerson: organizer];
+ [self sendEMailUsingTemplateNamed: @"Invitation"
+ forOldObject: nil
+ andNewObject: requestApt
+ toAttendees: attendees];
+ [requestApt release];
+
+ if (updateForcesReconsider)
+ {
+ iCalEvent *updatedApt;
+
+ updatedApt = [newApt copy];
+ [(iCalCalendar *) [updatedApt parent] setMethod: @"request"];
+ attendees = [NSMutableArray arrayWithArray:[newApt attendees]];
+ [attendees removeObjectsInArray:[changes insertedAttendees]];
+ [attendees removePerson:organizer];
+ [self sendEMailUsingTemplateNamed: @"Update"
+ forOldObject: oldApt
+ andNewObject: updatedApt
+ toAttendees: attendees];
+ [updatedApt release];
+ }
+
+ attendees
+ = [NSMutableArray arrayWithArray: [changes deletedAttendees]];
+ [attendees removePerson: organizer];
+ if ([attendees count])
+ {
+ iCalEvent *cancelledApt;
- canceledApt = [newApt copy];
- [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
- [self sendEMailUsingTemplateNamed: @"Removal"
- forOldObject: nil
- andNewObject: canceledApt
- toAttendees: attendees];
- [canceledApt release];
- }
+ cancelledApt = [newApt copy];
+ [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
+ [self sendEMailUsingTemplateNamed: @"Removal"
+ forOldObject: nil
+ andNewObject: cancelledApt
+ toAttendees: attendees];
+ [cancelledApt release];
+ }
+ }
}
+ else
+ [self primarySaveContentString: _iCal];
return nil;
}
-- (NSException *)deleteWithBaseSequence:(int)_v {
+- (NSException *) deleteWithBaseSequence: (int)_v
+{
/*
Note: We need to delete in all participants folders and send iMIP messages
for all external accounts.
- send iMIP mail for all folders not found
*/
iCalEvent *apt;
- NSArray *removedUIDs;
- NSMutableArray *attendees;
+ NSMutableArray *attendees, *removedUIDs;
+ NSException *error;
+ if ([[context request] handledByDefaultHandler])
+ {
/* load existing content */
-
- apt = [self event];
+ apt = (iCalEvent *) [self component: NO];
/* compare sequence if requested */
// // TODO
// }
- removedUIDs = [self attendeeUIDsFromAppointment:apt];
-
- if ([self sendEMailNotifications])
- {
- /* send notification email to attendees excluding organizer */
- attendees = [NSMutableArray arrayWithArray:[apt attendees]];
- [attendees removePerson:[apt organizer]];
+ removedUIDs = [NSMutableArray arrayWithArray:
+ [self attendeeUIDsFromAppointment: apt]];
+ if (![removedUIDs containsObject: owner])
+ [removedUIDs addObject: owner];
+
+ if ([self sendEMailNotifications]
+ && [self _aptIsStillRelevant: apt])
+ {
+ /* send notification email to attendees excluding organizer */
+ attendees = [NSMutableArray arrayWithArray: [apt attendees]];
+ [attendees removePerson: [apt organizer]];
- /* flag appointment as being canceled */
- [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
- [apt increaseSequence];
-
- /* remove all attendees to signal complete removal */
- [apt removeAllAttendees];
-
- /* send notification email */
- [self sendEMailUsingTemplateNamed: @"Deletion"
- forOldObject: nil
- andNewObject: apt
- toAttendees: attendees];
- }
-
- /* perform */
-
- return [self deleteInUIDs:removedUIDs];
-}
-
-- (NSException *) saveContentString: (NSString *) _iCalString
-{
- return [self saveContentString: _iCalString baseSequence: 0];
-}
+ /* flag appointment as being cancelled */
+ [(iCalCalendar *) [apt parent] setMethod: @"cancel"];
+ [apt increaseSequence];
-- (NSException *) changeParticipationStatus: (NSString *) _status
- inContext: (id) _ctx
-{
- iCalEvent *apt;
- iCalPerson *p;
- NSString *newContent;
- NSException *ex;
- NSString *myEMail;
-
- ex = nil;
+ /* remove all attendees to signal complete removal */
+ [apt removeAllAttendees];
- // TODO: do we need to use SOGoAppointment? (prefer iCalEvent?)
- apt = [self event];
+ /* send notification email */
+ [self sendEMailUsingTemplateNamed: @"Deletion"
+ forOldObject: nil
+ andNewObject: apt
+ toAttendees: attendees];
+ }
- if (apt)
- {
- myEMail = [[_ctx activeUser] email];
- p = [apt findParticipantWithEmail: myEMail];
- if (p)
- {
- // TODO: send iMIP reply mails?
-
- [p setPartStat:_status];
- newContent = [[apt parent] versitString];
- if (newContent)
- {
- ex = [self saveContentString:newContent];
- if (ex)
- // TODO: why is the exception wrapped?
- /* Server Error */
- ex = [NSException exceptionWithHTTPStatus: 500
- reason: [ex reason]];
- }
- else
- ex
- = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
- reason: @"Could not generate iCalendar data ..."];
- }
- else
- ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
- reason: @"user does not participate in this "
- @"appointment"];
+ error = [self deleteInUIDs: removedUIDs];
}
else
- ex = [NSException exceptionWithHTTPStatus:500 /* Server Error */
- reason:@"unable to parse appointment record"];
+ error = [self primaryDelete];
- return ex;
+ return error;
}
+- (NSException *) saveContentString: (NSString *) _iCalString
+{
+ return [self saveContentString: _iCalString baseSequence: 0];
+}
/* message type */
return @"IPM.Appointment";
}
-- (NSException *) saveContentString: (NSString *) contentString
- baseVersion: (unsigned int) baseVersion
-{
- NSString *newContentString, *oldContentString;
- iCalCalendar *eventCalendar;
- iCalEvent *event;
- NSArray *organizers;
-
- oldContentString = [self contentAsString];
- if (oldContentString)
- newContentString = contentString;
- else
- {
- eventCalendar = [iCalCalendar parseSingleFromSource: contentString];
- event = [self firstEventFromCalendar: eventCalendar];
- organizers = [event childrenWithTag: @"organizer"];
- if ([organizers count])
- newContentString = contentString;
- else
- {
- [event setOrganizerWithUid: [[self container] ownerInContext: nil]];
- newContentString = [eventCalendar versitString];
- }
- }
-
- return [super saveContentString: newContentString
- baseVersion: baseVersion];
-}
-
-- (NSString *) roleOfUser: (NSString *) login
- inContext: (WOContext *) context
-{
- AgenorUserManager *um;
- iCalEvent *event;
- NSString *role, *email;
-
- um = [AgenorUserManager sharedUserManager];
- email = [um getEmailForUID: login];
-
- event = [self event];
- if ([event isOrganizer: email])
- role = @"Organizer";
- else if ([event isParticipant: email])
- role = @"Participant";
- else
- role = nil;
-
- return role;
-}
-
@end /* SOGoAppointmentObject */
-
-
-