diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index e463220eb..4318c9a05 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -1391,7 +1391,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. participationStatus = @"DECLINED"; [appointmentObject changeParticipationStatus: participationStatus - withDelegate: nil]; + withDelegate: nil + alarm: nil]; [s appendString: @""]; [s appendString: @""]; diff --git a/NEWS b/NEWS index c74a127f3..f4527acc1 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ New features - Allow including or not freebusy info from subscribed calendars - Now possible to set an autosave timer for draft messages + - Now possible to set alarms on event invitations (#76) Enhancements - updated CKEditor to version 4.4.6 and added the 'Source Area' plugin diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index 5d0a40152..55c66a1bd 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -9,6 +9,7 @@ Appointments_PRINCIPAL_CLASS = SOGoAppointmentsProduct Appointments_OBJC_FILES = \ Product.m \ NSArray+Appointments.m \ + iCalAlarm+SOGo.m \ iCalCalendar+SOGo.m \ iCalEntityObject+SOGo.m \ iCalRepeatableEntityObject+SOGo.m \ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 6abfb6c0d..8ab78d0a0 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -30,6 +30,7 @@ @class WORequest; +@class iCalAlarm; @class iCalEvent; @class iCalCalendar; @@ -38,9 +39,12 @@ @interface SOGoAppointmentObject : SOGoCalendarComponent - (NSException *) changeParticipationStatus: (NSString *) status - withDelegate: (iCalPerson *) delegate; + withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm; + - (NSException *) changeParticipationStatus: (NSString *) status withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm forRecurrenceId: (NSCalendarDate *) _recurrenceId; // diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index b10ce0c59..f4ece14de 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1309,6 +1309,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent return ex; } +// +// +// - (NSDictionary *) _caldavSuccessCodeWithRecipient: (NSString *) recipient { NSMutableArray *element; @@ -1391,9 +1394,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // - (NSException *) changeParticipationStatus: (NSString *) status withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm { return [self changeParticipationStatus: status withDelegate: delegate + alarm: alarm forRecurrenceId: nil]; } @@ -1402,6 +1407,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // - (NSException *) changeParticipationStatus: (NSString *) _status withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm forRecurrenceId: (NSCalendarDate *) _recurrenceId { iCalCalendar *calendar; @@ -1479,7 +1485,18 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // Over DAV, it'll be handled directly in PUTAction: if (![context request] || [[context request] handledByDefaultHandler] || [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) - ex = [self saveCalendar: [event parent]]; + { + // If an alarm was specified, let's use it. This would happen if an attendee accepts/declines/etc. an + // event invitation and also sets an alarm along the way. This would happen ONLY from the web interface. + [event removeAllAlarms]; + + if (alarm) + { + [event addToAlarms: alarm]; + } + + ex = [self saveCalendar: [event parent]]; + } } } else @@ -1512,17 +1529,17 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent [v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame) b = NO; } - - // - // If we have to deal with Thunderbird/Lightning, we always send invitation - // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT - // to NONE/CLIENT when responding to an external invitation received by - // SOGo - so no invitation responses are ever sent by Lightning. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and - // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 - // - userAgents = [[context request] headersForKey: @"User-Agent"]; - + + // + // If we have to deal with Thunderbird/Lightning, we always send invitation + // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT + // to NONE/CLIENT when responding to an external invitation received by + // SOGo - so no invitation responses are ever sent by Lightning. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 + // + userAgents = [[context request] headersForKey: @"User-Agent"]; + for (i = 0; i < [userAgents count]; i++) { if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound && @@ -1533,7 +1550,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent } } - return b; } @@ -1594,7 +1610,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { // The current user deletes the occurence; let the organizer know that // the user has declined this occurence. - [self changeParticipationStatus: @"DECLINED" withDelegate: nil + [self changeParticipationStatus: @"DECLINED" + withDelegate: nil + alarm: nil forRecurrenceId: recurrenceId]; send_receipt = NO; } @@ -2161,12 +2179,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { [self changeParticipationStatus: @"DECLINED" withDelegate: nil // FIXME (specify delegate?) + alarm: nil forRecurrenceId: [self _addedExDate: oldEvent newEvent: newEvent]]; } else if (attendee) { [self changeParticipationStatus: [attendee partStat] withDelegate: delegate + alarm: nil forRecurrenceId: recurrenceId]; } // All attendees and the organizer field were removed. Apple iCal does diff --git a/SoObjects/Appointments/SOGoComponentOccurence.h b/SoObjects/Appointments/SOGoComponentOccurence.h index 68b1d5369..ed77dd96c 100644 --- a/SoObjects/Appointments/SOGoComponentOccurence.h +++ b/SoObjects/Appointments/SOGoComponentOccurence.h @@ -25,6 +25,7 @@ @class NSException; +@class iCalAlarm; @class iCalCalendar; @class iCalPerson; @class iCalRepeatableEntityObject; @@ -37,7 +38,8 @@ - (BOOL) isNew; - (NSException *) changeParticipationStatus: (NSString *) newPartStat - withDelegate: (iCalPerson *) delegate; + withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm; @end diff --git a/SoObjects/Appointments/SOGoComponentOccurence.m b/SoObjects/Appointments/SOGoComponentOccurence.m index 3b829d15e..f81a73f5a 100644 --- a/SoObjects/Appointments/SOGoComponentOccurence.m +++ b/SoObjects/Appointments/SOGoComponentOccurence.m @@ -215,6 +215,7 @@ they should be siblings or SOGoComponentOccurence the parent class of SOGoCalendarComponent... - (NSException *) changeParticipationStatus: (NSString *) newStatus withDelegate: (iCalPerson *) delegate + alarm: (iCalAlarm *) alarm { NSCalendarDate *date; @@ -222,6 +223,7 @@ return [container changeParticipationStatus: newStatus withDelegate: delegate + alarm: alarm forRecurrenceId: date]; } diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.h b/SoObjects/Appointments/iCalAlarm+SOGo.h new file mode 100644 index 000000000..2980f7bc6 --- /dev/null +++ b/SoObjects/Appointments/iCalAlarm+SOGo.h @@ -0,0 +1,37 @@ +/* iCalAlarm+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2014 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +@class iCalRepeatableEntityObject; + +@interface iCalAlarm (SOGoExtensions) + ++ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity + owner: (NSString *) theOwner + action: (NSString *) reminderAction + unit: (NSString *) reminderUnit + quantity: (NSString *) reminderQuantity + reference: (NSString *) reminderReference + reminderRelation: (NSString *) reminderRelation + emailAttendees: (BOOL) reminderEmailAttendees + emailOrganizer: (BOOL) reminderEmailOrganizer; + +@end diff --git a/SoObjects/Appointments/iCalAlarm+SOGo.m b/SoObjects/Appointments/iCalAlarm+SOGo.m new file mode 100644 index 000000000..c98c316db --- /dev/null +++ b/SoObjects/Appointments/iCalAlarm+SOGo.m @@ -0,0 +1,127 @@ +/* iCalAlarm+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2014 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import "iCalAlarm+SOGo.h" + +#import +#import +#import + +#import + +#import +#import + +@implementation iCalAlarm (SOGoExtensions) + +- (void) _appendAttendees: (NSArray *) attendees + toEmailAlarm: (iCalAlarm *) alarm +{ + NSMutableArray *aAttendees; + int count, max; + iCalPerson *currentAttendee, *aAttendee; + + max = [attendees count]; + aAttendees = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + currentAttendee = [attendees objectAtIndex: count]; + aAttendee = [iCalPerson elementWithTag: @"attendee"]; + [aAttendee setCn: [currentAttendee cn]]; + [aAttendee setEmail: [currentAttendee rfc822Email]]; + [aAttendees addObject: aAttendee]; + } + [alarm setAttendees: aAttendees]; +} + +- (void) _appendOrganizerToEmailAlarm: (iCalAlarm *) alarm + owner: (NSString *) uid +{ + NSDictionary *ownerIdentity; + iCalPerson *aAttendee; + + ownerIdentity = [[SOGoUser userWithLogin: uid roles: nil] + defaultIdentity]; + aAttendee = [iCalPerson elementWithTag: @"attendee"]; + [aAttendee setCn: [ownerIdentity objectForKey: @"fullName"]]; + [aAttendee setEmail: [ownerIdentity objectForKey: @"email"]]; + [alarm addChild: aAttendee]; +} + ++ (id) alarmForEvent: (iCalRepeatableEntityObject *) theEntity + owner: (NSString *) theOwner + action: (NSString *) reminderAction + unit: (NSString *) reminderUnit + quantity: (NSString *) reminderQuantity + reference: (NSString *) reminderReference + reminderRelation: (NSString *) reminderRelation + emailAttendees: (BOOL) reminderEmailAttendees + emailOrganizer: (BOOL) reminderEmailOrganizer +{ + iCalTrigger *aTrigger; + iCalAlarm *anAlarm; + NSString *aValue; + + anAlarm = [[self alloc] init]; + + aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"]; + [aTrigger setValueType: @"DURATION"]; + [anAlarm setTrigger: aTrigger]; + + if ([reminderAction length] > 0 && [reminderUnit length] > 0) + { + [anAlarm setAction: [reminderAction uppercaseString]]; + if ([reminderAction isEqualToString: @"email"]) + { + [anAlarm removeAllAttendees]; + if (reminderEmailAttendees) + [anAlarm _appendAttendees: [theEntity attendees] + toEmailAlarm: anAlarm]; + if (reminderEmailOrganizer) + [anAlarm _appendOrganizerToEmailAlarm: anAlarm owner: theOwner]; + [anAlarm setSummary: [theEntity summary]]; + [anAlarm setComment: [theEntity comment]]; + } + + if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame) + aValue = [NSString stringWithString: @"-P"]; + else + aValue = [NSString stringWithString: @"P"]; + + if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame || + [reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame) + aValue = [aValue stringByAppendingString: @"T"]; + + aValue = [aValue stringByAppendingFormat: @"%i%@", + [reminderQuantity intValue], + [reminderUnit substringToIndex: 1]]; + [aTrigger setSingleValue: aValue forKey: @""]; + [aTrigger setRelationType: reminderRelation]; + } + else + { + [anAlarm release]; + anAlarm = nil; + } + + return AUTORELEASE(anAlarm); +} + +@end diff --git a/UI/MailPartViewers/UIxMailPartICalActions.m b/UI/MailPartViewers/UIxMailPartICalActions.m index be8c47e73..e068ebbe5 100644 --- a/UI/MailPartViewers/UIxMailPartICalActions.m +++ b/UI/MailPartViewers/UIxMailPartICalActions.m @@ -248,6 +248,7 @@ { response = (WOResponse*)[eventObject changeParticipationStatus: newStatus withDelegate: delegate + alarm: nil forRecurrenceId: [chosenEvent recurrenceId]]; // if (ex) // response = ex; //[self responseWithStatus: 500]; diff --git a/UI/Scheduler/GNUmakefile b/UI/Scheduler/GNUmakefile index c9c8a38b6..4c0765244 100644 --- a/UI/Scheduler/GNUmakefile +++ b/UI/Scheduler/GNUmakefile @@ -52,9 +52,6 @@ SchedulerUI_RESOURCE_FILES += \ SchedulerUI_RESOURCE_FILES += \ Toolbars/SOGoAppointmentFolders.toolbar \ Toolbars/SOGoAppointmentObject.toolbar \ - Toolbars/SOGoAppointmentObjectAccept.toolbar \ - Toolbars/SOGoAppointmentObjectDecline.toolbar \ - Toolbars/SOGoAppointmentObjectAcceptOrDecline.toolbar \ Toolbars/SOGoTaskObject.toolbar \ Toolbars/SOGoComponentClose.toolbar \ Toolbars/SOGoEmpty.toolbar diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentObjectAccept.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentObjectAccept.toolbar deleted file mode 100644 index e3913afc1..000000000 --- a/UI/Scheduler/Toolbars/SOGoAppointmentObjectAccept.toolbar +++ /dev/null @@ -1,7 +0,0 @@ -( /* the toolbar groups -*-cperl-*- */ - ( { link = "#"; - isSafe = NO; - label = "accept"; - onclick = "return modifyEvent(this, 'accept');"; - image = "tb-ab-properties-flat-24x24.png"; } ) -) diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentObjectAcceptOrDecline.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentObjectAcceptOrDecline.toolbar deleted file mode 100644 index bb967d8ea..000000000 --- a/UI/Scheduler/Toolbars/SOGoAppointmentObjectAcceptOrDecline.toolbar +++ /dev/null @@ -1,12 +0,0 @@ -( /* the toolbar groups -*-cperl-*- */ - ( { link = "#"; - isSafe = NO; - label = "accept"; - onclick = "return modifyEvent(this, 'accept');"; - image = "tb-ab-properties-flat-24x24.png"; }, - { link = "#"; - isSafe = NO; - label = "decline"; - onclick = "return modifyEvent(this, 'decline');"; - image = "tb-mail-stop-flat-24x24.png"; } ) -) diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentObjectDecline.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentObjectDecline.toolbar deleted file mode 100644 index 347b8f8cf..000000000 --- a/UI/Scheduler/Toolbars/SOGoAppointmentObjectDecline.toolbar +++ /dev/null @@ -1,7 +0,0 @@ -( /* the toolbar groups -*-cperl-*- */ - ( { link = "#"; - isSafe = NO; - label = "decline"; - onclick = "return modifyEvent(this, 'decline');"; - image = "tb-mail-stop-flat-24x24.png"; } ) -) diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 86c26153b..653226628 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -51,6 +51,7 @@ #import #import #import +#import #import #import #import @@ -80,7 +81,7 @@ isTransparent = NO; sendAppointmentNotifications = YES; componentCalendar = nil; - + user = [[self context] activeUser]; ASSIGN (dateFormatter, [user dateFormatterInContext: context]); } @@ -111,6 +112,11 @@ return event; } +- (NSString *) rsvpURL +{ + return [NSString stringWithFormat: @"%@/rsvpAppointment", + [[self clientObject] baseURL]]; +} - (NSString *) saveURL { return [NSString stringWithFormat: @"%@/saveAsAppointment", @@ -390,6 +396,137 @@ } } +// +// +// +- (id ) rsvpAction +{ + iCalPerson *delegatedAttendee; + NSDictionary *message; + WOResponse *response; + WORequest *request; + iCalAlarm *anAlarm; + NSString *status; + + int replyList, reminderList; + + request = [context request]; + message = [[request contentAsString] objectFromJSONString]; + + delegatedAttendee = nil; + anAlarm = nil; + status = nil; + + replyList = [[message objectForKey: @"replyList"] intValue]; + + switch (replyList) + { + case 0: + status = @"ACCEPTED"; + break; + + case 1: + status = @"DECLINED"; + break; + + case 2: + status = @"NEEDS-ACTION"; + break; + + case 3: + status = @"TENTATIVE"; + break; + + case 4: + default: + { + NSString *delegatedEmail, *delegatedUid; + SOGoUser *user; + + status = @"DELEGATED"; + delegatedEmail = [[message objectForKey: @"delegatedTo"] stringByTrimmingSpaces]; + + if ([delegatedEmail length]) + { + user = [context activeUser]; + delegatedAttendee = [iCalPerson new]; + [delegatedAttendee autorelease]; + [delegatedAttendee setEmail: delegatedEmail]; + delegatedUid = [delegatedAttendee uid]; + if (delegatedUid) + { + SOGoUser *delegatedUser; + delegatedUser = [SOGoUser userWithLogin: delegatedUid]; + [delegatedAttendee setCn: [delegatedUser cn]]; + } + + [delegatedAttendee setRole: @"REQ-PARTICIPANT"]; + [delegatedAttendee setRsvp: @"TRUE"]; + [delegatedAttendee setParticipationStatus: iCalPersonPartStatNeedsAction]; + [delegatedAttendee setDelegatedFrom: + [NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]]; + } + else + return [NSException exceptionWithHTTPStatus: 400 + reason: @"missing 'to' parameter"]; + } + break; + } + + // Extract the user alarm, if any + reminderList = [[message objectForKey: @"reminderList"] intValue]; + + if ([[message objectForKey: @"reminderList"] isEqualToString: @"WONoSelectionString"] || reminderList == 5 || reminderList == 10 || reminderList == 14) + { + // No selection, wipe alarm which will be done in changeParticipationStatus... + } + else if (reminderList == 15) + { + // Custom + anAlarm = [iCalAlarm alarmForEvent: [self event] + owner: [[self clientObject] ownerInContext: context] + action: [message objectForKey: @"reminderAction"] + unit: [message objectForKey: @"reminderUnit"] + quantity: [message objectForKey: @"reminderQuantity"] + reference: [message objectForKey: @"reminderReference"] + reminderRelation: [message objectForKey: @"reminderRelation"] + emailAttendees: [[message objectForKey: @"reminderEmailAttendees"] boolValue] + emailOrganizer: [[message objectForKey: @"reminderEmailOrganizer"] boolValue]]; + } + else + { + // Standard + NSString *aValue; + + aValue = [[UIxComponentEditor reminderValues] objectAtIndex: reminderList]; + + // Predefined alarm + if ([aValue length]) + { + iCalTrigger *aTrigger; + + anAlarm = [[[iCalAlarm alloc] init] autorelease]; + aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"]; + [aTrigger setValueType: @"DURATION"]; + [anAlarm setTrigger: aTrigger]; + [anAlarm setAction: @"DISPLAY"]; + [aTrigger setSingleValue: aValue forKey: @""]; + } + } + + response = (WOResponse *)[[self clientObject] changeParticipationStatus: status + withDelegate: delegatedAttendee + alarm: anAlarm]; + + if (!response) + response = [self responseWith204]; + + return response; +} + +// +// +// - (id ) saveAction { SOGoAppointmentFolder *previousCalendar; @@ -560,7 +697,7 @@ actionName = [[request requestHandlerPath] lastPathComponent]; return ([[self clientObject] conformsToProtocol: @protocol (SOGoComponentOccurence)] - && [actionName hasPrefix: @"save"]); + && ([actionName hasPrefix: @"save"] || [actionName hasPrefix: @"rsvp"])); } - (void) takeValuesFromRequest: (WORequest *) _rq @@ -637,81 +774,4 @@ } -- (id) _statusChangeAction: (NSString *) newStatus -{ - [[self clientObject] changeParticipationStatus: newStatus - withDelegate: nil]; - - return [self responseWith204]; -} - -- (id) acceptAction -{ - return [self _statusChangeAction: @"ACCEPTED"]; -} - -- (id) declineAction -{ - return [self _statusChangeAction: @"DECLINED"]; -} - -- (id) needsActionAction -{ - return [self _statusChangeAction: @"NEEDS-ACTION"]; -} - -- (id) tentativeAction -{ - return [self _statusChangeAction: @"TENTATIVE"]; -} - -- (id) delegateAction -{ -// BOOL receiveUpdates; - NSString *delegatedEmail, *delegatedUid; - iCalPerson *delegatedAttendee; - SOGoUser *user; - WORequest *request; - WOResponse *response; - - response = nil; - request = [context request]; - delegatedEmail = [request formValueForKey: @"to"]; - if ([delegatedEmail length]) - { - user = [context activeUser]; - delegatedAttendee = [iCalPerson new]; - [delegatedAttendee autorelease]; - [delegatedAttendee setEmail: delegatedEmail]; - delegatedUid = [delegatedAttendee uid]; - if (delegatedUid) - { - SOGoUser *delegatedUser; - delegatedUser = [SOGoUser userWithLogin: delegatedUid]; - [delegatedAttendee setCn: [delegatedUser cn]]; - } - - [delegatedAttendee setRole: @"REQ-PARTICIPANT"]; - [delegatedAttendee setRsvp: @"TRUE"]; - [delegatedAttendee setParticipationStatus: iCalPersonPartStatNeedsAction]; - [delegatedAttendee setDelegatedFrom: - [NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]]; - -// receiveUpdates = [[request formValueForKey: @"receiveUpdates"] boolValue]; -// if (receiveUpdates) -// [delegatedAttendee setRole: @"NON-PARTICIPANT"]; - - response = (WOResponse*)[[self clientObject] changeParticipationStatus: @"DELEGATED" - withDelegate: delegatedAttendee]; - } - else - response = [NSException exceptionWithHTTPStatus: 400 - reason: @"missing 'to' parameter"]; - - if (!response) - response = [self responseWith204]; - - return response; -} - @end diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index 6120901d8..201065c11 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -38,6 +38,7 @@ id item; id attendee; + NSString *rsvpURL; NSString *saveURL; NSMutableArray *calendarList; NSDictionary *organizerProfile; @@ -62,7 +63,7 @@ NSString *dateFormat; NSMutableDictionary *jsonAttendees; - + NSString *reminder; NSString *reminderQuantity; NSString *reminderUnit; @@ -184,6 +185,8 @@ - (BOOL) isWriteableClientObject; - (NSException *) validateObjectForStatusChange; ++ (NSArray *) reminderValues; + @end #endif /* UIXCOMPONENTEDITOR_H */ diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 77a4d032a..60ee34c12 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -51,6 +51,7 @@ #import #import +#import #import #import #import @@ -225,7 +226,7 @@ iRANGE(2); [attendee release]; [jsonAttendees release]; [calendarList release]; - + [reminder release]; [reminderQuantity release]; [reminderUnit release]; @@ -683,12 +684,16 @@ iRANGE(2); ASSIGN (ownerAsAttendee, [component findAttendeeWithEmail: (id)ownerEmail]); } } -// /* cycles */ -// if ([component isRecurrent]) -// { -// rrule = [[component recurrenceRules] objectAtIndex: 0]; -// [self adjustCycleControlsForRRule: rrule]; -// } +} + +- (void) setRSVPURL: (NSString *) theURL +{ + rsvpURL = theURL; +} + +- (NSString *) rsvpURL +{ + return rsvpURL; } - (void) setSaveURL: (NSString *) newSaveURL @@ -2168,40 +2173,7 @@ RANGE(2); } } -- (void) _appendAttendees: (NSArray *) attendees - toEmailAlarm: (iCalAlarm *) alarm -{ - NSMutableArray *aAttendees; - int count, max; - iCalPerson *currentAttendee, *aAttendee; - max = [attendees count]; - aAttendees = [NSMutableArray arrayWithCapacity: max]; - for (count = 0; count < max; count++) - { - currentAttendee = [attendees objectAtIndex: count]; - aAttendee = [iCalPerson elementWithTag: @"attendee"]; - [aAttendee setCn: [currentAttendee cn]]; - [aAttendee setEmail: [currentAttendee rfc822Email]]; - [aAttendees addObject: aAttendee]; - } - [alarm setAttendees: aAttendees]; -} - -- (void) _appendOrganizerToEmailAlarm: (iCalAlarm *) alarm -{ - NSString *uid; - NSDictionary *ownerIdentity; - iCalPerson *aAttendee; - - uid = [[self clientObject] ownerInContext: context]; - ownerIdentity = [[SOGoUser userWithLogin: uid roles: nil] - defaultIdentity]; - aAttendee = [iCalPerson elementWithTag: @"attendee"]; - [aAttendee setCn: [ownerIdentity objectForKey: @"fullName"]]; - [aAttendee setEmail: [ownerIdentity objectForKey: @"email"]]; - [alarm addChild: aAttendee]; -} - (void) takeValuesFromRequest: (WORequest *) _rq inContext: (WOContext *) _ctx @@ -2236,68 +2208,43 @@ RANGE(2); [component removeAllAlarms]; else { - iCalTrigger *aTrigger; iCalAlarm *anAlarm; NSString *aValue; NSUInteger index; - anAlarm = [iCalAlarm new]; - index = [reminderItems indexOfObject: reminder]; - - aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"]; - [aTrigger setValueType: @"DURATION"]; - [anAlarm setTrigger: aTrigger]; - aValue = [reminderValues objectAtIndex: index]; - if ([aValue length]) { - // Predefined alarm - [anAlarm setAction: @"DISPLAY"]; - [aTrigger setSingleValue: aValue forKey: @""]; - } - else { - // Custom alarm - if ([reminderAction length] > 0 && [reminderUnit length] > 0) - { - [anAlarm setAction: [reminderAction uppercaseString]]; - if ([reminderAction isEqualToString: @"email"]) - { - [anAlarm removeAllAttendees]; - if (reminderEmailAttendees) - [self _appendAttendees: [component attendees] - toEmailAlarm: anAlarm]; - if (reminderEmailOrganizer) - [self _appendOrganizerToEmailAlarm: anAlarm]; - [anAlarm setSummary: [component summary]]; - [anAlarm setComment: [component comment]]; - } - - if ([reminderReference caseInsensitiveCompare: @"BEFORE"] == NSOrderedSame) - aValue = [NSString stringWithString: @"-P"]; - else - aValue = [NSString stringWithString: @"P"]; - - if ([reminderUnit caseInsensitiveCompare: @"MINUTES"] == NSOrderedSame || - [reminderUnit caseInsensitiveCompare: @"HOURS"] == NSOrderedSame) - aValue = [aValue stringByAppendingString: @"T"]; - - aValue = [aValue stringByAppendingFormat: @"%i%@", - [reminderQuantity intValue], - [reminderUnit substringToIndex: 1]]; - [aTrigger setSingleValue: aValue forKey: @""]; - [aTrigger setRelationType: reminderRelation]; - } - else - { - [anAlarm release]; - anAlarm = nil; - } - } + + // Predefined alarm + if ([aValue length]) + { + iCalTrigger *aTrigger; + + anAlarm = [[[iCalAlarm alloc] init] autorelease]; + aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"]; + [aTrigger setValueType: @"DURATION"]; + [anAlarm setTrigger: aTrigger]; + [anAlarm setAction: @"DISPLAY"]; + [aTrigger setSingleValue: aValue forKey: @""]; + } + else + { + // Custom alarm + anAlarm = [iCalAlarm alarmForEvent: component + owner: [[self clientObject] ownerInContext: context] + action: reminderAction + unit: reminderUnit + quantity: reminderQuantity + reference: reminderReference + reminderRelation: reminderRelation + emailAttendees: reminderEmailAttendees + emailOrganizer: reminderEmailOrganizer]; + } + if (anAlarm) { [component removeAllAlarms]; [component addToAlarms: anAlarm]; - [anAlarm release]; } } @@ -2597,4 +2544,9 @@ RANGE(2); return response; } ++ (NSArray *) reminderValues +{ + return reminderValues; +} + @end diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index c13980130..da074912e 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -241,6 +241,11 @@ pageName = "UIxAppointmentEditor"; actionName = "save"; }; + rsvpAppointment = { + protectedBy = "RespondToComponent"; + pageName = "UIxAppointmentEditor"; + actionName = "rsvp"; + }; saveAsAppointment = { protectedBy = "ModifyComponent"; pageName = "UIxAppointmentEditor"; @@ -256,31 +261,6 @@ actionClass = "UIxComponentEditor"; actionName = "raw"; }; - accept = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "accept"; - }; - decline = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "decline"; - }; - delegate = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "delegate"; - }; - tentative = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "tentative"; - }; - needsaction = { - protectedBy = "RespondToComponent"; - pageName = "UIxAppointmentEditor"; - actionName = "needsAction"; - }; adjust = { protectedBy = "ModifyComponent"; actionClass = "UIxAppointmentActions"; diff --git a/UI/Templates/SchedulerUI/UIxAppointmentEditor.wox b/UI/Templates/SchedulerUI/UIxAppointmentEditor.wox index 607b32a38..016175578 100644 --- a/UI/Templates/SchedulerUI/UIxAppointmentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxAppointmentEditor.wox @@ -11,6 +11,7 @@ componentCalendar="componentCalendar" eventIsReadOnly="eventIsReadOnly" var:component="event" + var:rsvpURL="rsvpURL" var:saveURL="saveURL"> diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 41c972fce..4fde87648 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -182,6 +182,7 @@ +
    @@ -234,7 +235,10 @@ > + +
    + - + + + + + + + + + + +
    +
    diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index b9c4207d0..5ee6cb528 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -529,20 +529,6 @@ function onMenuRawEvent(event) { openGenericWindow.delay(0.1, url); } -function modifyEvent(sender, modification, parameters) { - var currentLocation = '' + window.location; - var arr = currentLocation.split("/"); - arr[arr.length-1] = modification; - - document.modifyEventAjaxRequest = triggerAjaxRequest(arr.join("/"), - modifyEventCallback, - modification, - parameters, - { "Content-type": "application/x-www-form-urlencoded" }); - - return false; -} - function closeInvitationWindow() { var closeDiv = document.createElement("div"); document.body.appendChild(closeDiv); diff --git a/UI/WebServerResources/UIxAppointmentEditor.js b/UI/WebServerResources/UIxAppointmentEditor.js index 24c2c92b1..72b0b84a4 100644 --- a/UI/WebServerResources/UIxAppointmentEditor.js +++ b/UI/WebServerResources/UIxAppointmentEditor.js @@ -1,10 +1,8 @@ /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* - Copyright (C) 2005 SKYRIX Software AG - Copyright (C) 2006-2013 Inverse inc. - - + Copyright (C) 2006-2014 Inverse inc. + This file is part of SOGo. SOGo is free software; you can redistribute it and/or modify it under diff --git a/UI/WebServerResources/UIxComponentEditor.js b/UI/WebServerResources/UIxComponentEditor.js index 87a20bd61..42d1cad0b 100644 --- a/UI/WebServerResources/UIxComponentEditor.js +++ b/UI/WebServerResources/UIxComponentEditor.js @@ -386,26 +386,22 @@ function onPopupReminderWindow(event) { } function onOkButtonClick (e) { - var item = $("replyList"); - var value = parseInt(item.options[item.selectedIndex].value); - var action = ""; - var parameters = ""; - - if (value == 0) - action = 'accept'; - else if (value == 1) - action = 'decline'; - else if (value == 2) - action = 'needsaction'; - else if (value == 3) - action = 'tentative'; - else if (value == 4) { - var url = ApplicationBaseURL + "/" + activeCalendar + "/" + activeComponent; - delegateInvitation(url, modifyEventCallback); - } + + var jsonData = Form.serialize(document.forms['rsvpform'], true); - if (action != "") - modifyEvent (item, action, parameters); + var input = $("delegatedTo"); + if (input && input.readAttribute("uid") != null) { + jsonData['delegatedTo'] = input.readAttribute("uid"); + } + + triggerAjaxRequest(document.forms['rsvpform'].readAttribute("action"), + modifyEventCallback, + null, + Object.toJSON(jsonData), + { "content-type": "application/json"} + ); + + return false; } function onCancelButtonClick (e) {