From ff9a299ed8e878cfb328d748dea489e2cc130d68 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Sat, 22 Nov 2008 07:20:22 +0000 Subject: [PATCH] Monotone-Parent: 9004babc502ac0232d7659ba8ada6726ad286662 Monotone-Revision: fe15a52bbb65b7147dfd596fd4f0791b716d1b26 Monotone-Author: flachapelle@inverse.ca Monotone-Date: 2008-11-22T07:20:22 Monotone-Branch: ca.inverse.sogo --- .../Appointments/SOGoAppointmentObject.h | 2 + .../Appointments/SOGoAppointmentObject.m | 122 +++++++++++++----- .../Appointments/SOGoCalendarComponent.h | 1 + .../Appointments/SOGoCalendarComponent.m | 2 + .../Appointments/SOGoComponentOccurence.h | 2 +- .../Appointments/SOGoComponentOccurence.m | 17 ++- .../iCalRepeatableEntityObject+SOGo.m | 10 +- UI/Scheduler/UIxOccurenceDialog.m | 13 ++ UI/Scheduler/product.plist | 7 +- UI/WebServerResources/SchedulerUI.js | 42 +++--- 10 files changed, 159 insertions(+), 59 deletions(-) diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index e6be8fd0c..c15621498 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -47,6 +47,8 @@ @interface SOGoAppointmentObject : SOGoCalendarComponent - (NSException *) changeParticipationStatus: (NSString *) _status; +- (NSException *) changeParticipationStatus: (NSString *) _status + forRecurrenceId: (NSCalendarDate *) _recurrenceId; - (void) takeAttendeeStatus: (iCalPerson *) attendee from: (NSString *) originator; diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 8b4098e1f..0b0bf24af 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -50,6 +50,7 @@ #import "NSArray+Appointments.h" #import "SOGoAppointmentFolder.h" #import "SOGoAppointmentOccurence.h" +#import "SOGoCalendarComponent.h" #import "SOGoAppointmentObject.h" @@ -162,6 +163,7 @@ NSString *possibleName; folder = [container lookupCalendarFolderForUID: uid]; + // should call lookupCalendarFoldersForUIDs to search among all folders object = [folder lookupName: nameInContainer inContext: context acquire: NO]; if ([object isKindOfClass: [NSException class]]) @@ -324,7 +326,7 @@ if ([attendees count]) { [self _handleAddedUsers: attendees fromEvent: newEvent]; - [self sendEMailUsingTemplateNamed: @"Invitation" + [self sendEMailUsingTemplateNamed: @"Update" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: oldEvent toAttendees: attendees]; @@ -379,14 +381,18 @@ - (NSException *) _updateAttendee: (iCalPerson *) attendee ownerUser: (SOGoUser *) theOwnerUser forEventUID: (NSString *) eventUID + withRecurrenceId: (NSCalendarDate *) recurrenceId withSequence: (NSNumber *) sequence forUID: (NSString *) uid shouldAddSentBy: (BOOL) b { SOGoAppointmentObject *eventObject; - iCalEvent *event; + //iCalEvent *event; + iCalCalendar *calendar; + iCalEntityObject *event; iCalPerson *otherAttendee; - NSString *iCalString; + NSArray *events; + NSString *iCalString, *recurrenceTime; NSException *error; error = nil; @@ -394,32 +400,55 @@ eventObject = [self _lookupEvent: eventUID forUID: uid]; if (![eventObject isNew]) { - event = [eventObject component: NO secure: NO]; + if (recurrenceId == nil) + { + // We must update main event and all its occurences (if any). + calendar = [eventObject calendar: NO secure: NO]; + event = [calendar firstChildWithTag: [self componentTag]]; + events = [calendar allObjects]; + } + else + { + // If recurrenceId is defined, find the specified occurence + // within the repeating vEvent. + recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; + event = [eventObject lookupOccurence: recurrenceTime]; + + if (event == nil) + event = [eventObject newOccurenceWithID: recurrenceTime]; + events = [NSArray arrayWithObject: event]; + } + if ([[event sequence] compare: sequence] == NSOrderedSame) { SOGoUser *currentUser; - - //otherAttendee = [event findParticipant: [context activeUser]]; - otherAttendee = [event findParticipant: theOwnerUser]; - [otherAttendee setPartStat: [attendee partStat]]; - - // If one has accepted / declined an invitation on behalf of - // the attendee, we add the user to the SENT-BY attribute. - currentUser = [context activeUser]; - if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]]) + int i; + + for (i = 0; i < [events count]; i++) { - NSString *currentEmail; - currentEmail = [[currentUser allEmails] objectAtIndex: 0]; - [otherAttendee addAttribute: @"SENT-BY" - value: [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail]]; - } - else - { - // We must REMOVE any SENT-BY here. This is important since if A accepted - // the event for B and then, B changes by himself his participation status, - // we don't want to keep the previous SENT-BY attribute there. - [(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"]; + event = [events objectAtIndex: i]; + + otherAttendee = [event findParticipant: theOwnerUser]; + [otherAttendee setPartStat: [attendee partStat]]; + + // If one has accepted / declined an invitation on behalf of + // the attendee, we add the user to the SENT-BY attribute. + currentUser = [context activeUser]; + if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]]) + { + NSString *currentEmail; + currentEmail = [[currentUser allEmails] objectAtIndex: 0]; + [otherAttendee addAttribute: @"SENT-BY" + value: [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail]]; + } + else + { + // We must REMOVE any SENT-BY here. This is important since if A accepted + // the event for B and then, B changes by himself his participation status, + // we don't want to keep the previous SENT-BY attribute there. + [(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"]; + } } iCalString = [[event parent] versitString]; @@ -489,10 +518,16 @@ from: ownerUser]; organizerUID = [[event organizer] uid]; + + if (!organizerUID) + // event is an recurrence; retrieve organizer from master event + organizerUID = [[(iCalEntityObject*)[[event parent] firstChildWithTag: [self componentTag]] organizer] uid]; + if (organizerUID) ex = [self _updateAttendee: attendee ownerUser: theOwnerUser forEventUID: [event uid] + withRecurrenceId: [event recurrenceId] withSequence: [event sequence] forUID: organizerUID shouldAddSentBy: YES]; @@ -523,6 +558,7 @@ [self _updateAttendee: attendee ownerUser: theOwnerUser forEventUID: [event uid] + withRecurrenceId: [event recurrenceId] withSequence: [event sequence] forUID: uid shouldAddSentBy: YES]; @@ -687,6 +723,7 @@ [self _updateAttendee: attendee ownerUser: ownerUser forEventUID: [event uid] + withRecurrenceId: [event recurrenceId] withSequence: [event sequence] forUID: uid shouldAddSentBy: NO]; @@ -713,6 +750,7 @@ [self _updateAttendee: attendee ownerUser: ownerUser forEventUID: [event uid] + withRecurrenceId: [event recurrenceId] withSequence: [event sequence] forUID: uid shouldAddSentBy: NO]; @@ -778,14 +816,38 @@ // - (NSException *) changeParticipationStatus: (NSString *) _status { + return [self changeParticipationStatus: _status forRecurrenceId: nil]; +} + +- (NSException *) changeParticipationStatus: (NSString *) _status forRecurrenceId: (NSCalendarDate *) _recurrenceId +{ + iCalCalendar *calendar; iCalEvent *event; iCalPerson *attendee; NSException *ex; SOGoUser *ownerUser; + NSString *recurrenceTime; + event = nil; ex = nil; - event = [self component: NO secure: NO]; + calendar = [self calendar: NO secure: NO]; + if (calendar) + { + if (_recurrenceId) + { + // If _recurrenceId is defined, find the specified occurence + // within the repeating vEvent. + recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]]; + event = [self lookupOccurence: recurrenceTime]; + // If no occurence found, create one + event = [self newOccurenceWithID: recurrenceTime]; + } + else + // No specific occurence specified; return the first vEvent of + // the vCalendar. + event = [calendar firstChildWithTag: [self componentTag]]; + } if (event) { // owerUser will actually be the owner of the calendar @@ -794,7 +856,7 @@ // course be on the attendee that is the owner of the // calendar where the participation change has occured. ownerUser = [SOGoUser userWithLogin: owner roles: nil]; - + attendee = [event findParticipant: ownerUser]; if (attendee) ex = [self _handleAttendee: attendee @@ -802,14 +864,14 @@ statusChange: _status inEvent: event]; else - ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */ + ex = [NSException exceptionWithHTTPStatus: 404 // Not Found reason: @"user does not participate in this " @"calendar event"]; } else - ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + ex = [NSException exceptionWithHTTPStatus: 500 // Server Error reason: @"unable to parse event record"]; - + return ex; } @@ -827,7 +889,7 @@ occurence = event; if ([event userIsOrganizer: ownerUser]) { - currentUser = [context activeUser]; + currentUser = [context activeUser]; // is this correct? attendees = [occurence attendeesWithoutUser: currentUser]; if (![attendees count] && event != occurence) attendees = [event attendeesWithoutUser: currentUser]; diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index d2be267e9..79d42d76f 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -79,6 +79,7 @@ - (NSArray *) getUIDsForICalPersons: (NSArray *) iCalPersons; /* recurrences */ +- (iCalRepeatableEntityObject *) lookupOccurence: (NSString *) recID; - (SOGoComponentOccurence *) occurence: (iCalRepeatableEntityObject *) component; - (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) recID; diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index eabb610ae..9a42fde5d 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -532,6 +532,8 @@ _occurenceHasID (iCalRepeatableEntityObject *occurence, NSString *recID) subject = [p getSubject]; text = [p getBody]; + NSLog(@"Template: %@", pageName); + NSLog(@"Email: %@ => %@ (%@)", senderEmail, recipient, subject); /* construct message */ headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; diff --git a/SoObjects/Appointments/SOGoComponentOccurence.h b/SoObjects/Appointments/SOGoComponentOccurence.h index 19df1c25b..57c88876b 100644 --- a/SoObjects/Appointments/SOGoComponentOccurence.h +++ b/SoObjects/Appointments/SOGoComponentOccurence.h @@ -34,7 +34,7 @@ - (iCalRepeatableEntityObject *) occurence; - (BOOL) isNew; -- (NSException *) delete; +- (NSException *) prepareDelete; @end diff --git a/SoObjects/Appointments/SOGoComponentOccurence.m b/SoObjects/Appointments/SOGoComponentOccurence.m index 4a21f8624..ba750d3f2 100644 --- a/SoObjects/Appointments/SOGoComponentOccurence.m +++ b/SoObjects/Appointments/SOGoComponentOccurence.m @@ -27,9 +27,11 @@ #import #import +#import "SOGoAppointmentObject.h" +#import "SOGoCalendarComponent.h" + #import "SOGoComponentOccurence.h" -#import "SOGoCalendarComponent.h" @interface SOGoCalendarComponent (OccurenceExtensions) - (void) prepareDeleteOccurence: (iCalRepeatableEntityObject *) component; @@ -135,7 +137,7 @@ return component; } -- (NSException *) delete; +- (NSException *) prepareDelete; { NSException *error; iCalCalendar *parent; @@ -149,7 +151,10 @@ [master addToExceptionDates: [component startDate]]; parent = [component parent]; [[parent children] removeObject: component]; + + // changes participant status & send invitation email - as if it was a new event! :( [container saveComponent: master]; + error = nil; } @@ -163,9 +168,13 @@ #warning most of SOGoCalendarComponent and SOGoComponentOccurence share the same external interface... \ they should be siblings or SOGoComponentOccurence the parent class of SOGoCalendarComponent... -- (NSException *) changeParticipationStatus: (NSString *) newPartStat +- (NSException *) changeParticipationStatus: (NSString *) newStatus { - return [container changeParticipationStatus: newPartStat]; + NSCalendarDate *date; + + date = [component recurrenceId]; + + return [container changeParticipationStatus: newStatus forRecurrenceId: date]; } @end diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m index 4cd59a0b0..8411d9d90 100644 --- a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m @@ -116,11 +116,11 @@ doesOccur = [self isRecurrent]; if (doesOccur) { - tz = [occurenceDate timeZone]; - if ([tz isDaylightSavingTimeForDate: occurenceDate] != [tz isDaylightSavingTimeForDate: [self startDate]]) { - daylightOffset = [tz secondsFromGMTForDate: occurenceDate] - [tz secondsFromGMTForDate: [self startDate]]; - occurenceDate = [occurenceDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: daylightOffset]; - } +// tz = [occurenceDate timeZone]; +// if ([tz isDaylightSavingTimeForDate: occurenceDate] != [tz isDaylightSavingTimeForDate: [self startDate]]) { +// daylightOffset = [tz secondsFromGMTForDate: occurenceDate] - [tz secondsFromGMTForDate: [self startDate]]; +// occurenceDate = [occurenceDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: daylightOffset]; +// } endDate = [occurenceDate addTimeInterval: [self occurenceInterval]]; checkRange = [NGCalendarDateRange calendarDateRangeWithStartDate: occurenceDate diff --git a/UI/Scheduler/UIxOccurenceDialog.m b/UI/Scheduler/UIxOccurenceDialog.m index c6b2e7a2d..626cde32b 100644 --- a/UI/Scheduler/UIxOccurenceDialog.m +++ b/UI/Scheduler/UIxOccurenceDialog.m @@ -87,4 +87,17 @@ return self; } +- (WOResponse *) deleteAction +{ + SOGoCalendarComponent *component; + WOResponse *response; + + component = [self clientObject]; + response = (WOResponse *) [component prepareDelete]; + if (!response) + response = [self responseWithStatus: 204]; + + return response; +} + @end diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index c58f5a70c..9f883e69b 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -251,7 +251,7 @@ confirmDeletion = { protectedBy = "ModifyComponent"; pageName = "UIxOccurenceDialog"; - actionName = "confirmDeletion"; + actionName = "confirmDeletion"; }; }; }; @@ -273,6 +273,11 @@ pageName = "UIxAppointmentEditor"; actionName = "save"; }; + delete = { + protectedBy = "Delete Objects"; + pageName = "UIxOccurenceDialog"; + actionName = "delete"; + }; }; }; diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index ba6e85eb1..04a895605 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -396,7 +396,7 @@ function onViewEventCallback(http) { } } else { - log("onViewEventCallback ajax error:" + http.url); + log("onViewEventCallback ajax error: " + http.url); } } @@ -414,23 +414,29 @@ function performEventEdition(folder, event, recurrence) { } function performEventDeletion(folder, event, recurrence) { - if (calendarEvents) { - var urlstr = ApplicationBaseURL + folder + "/" + event; - var occurenceTime; + if (calendarEvents) { if (recurrence) { - urlstr += "/" + recurrence; - occurenceTime = recurrence.substring(9); + // Only one recurrence + var occurenceTime = recurrence.substring(9); + var nodes = _eventBlocksMatching(folder, event, occurenceTime); + var urlstr = ApplicationBaseURL + folder + "/" + event + "/" + recurrence + "/delete"; + + if (nodes) + document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr, + performDeleteEventCallback, + { nodes: nodes, + occurence: occurenceTime }); } - else - occurenceTime = null; - - urlstr += "/delete"; - var nodes = _eventBlocksMatching(folder, event, occurenceTime); - if (nodes) - document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr, - performDeleteEventCallback, - { nodes: nodes, - occurence: occurenceTime }); + else { + // All recurrences + if (document.deleteEventAjaxRequest) { + document.deleteEventAjaxRequest.aborted = true; + document.deleteEventAjaxRequest.abort(); + } + eventsToDelete.push([event]); + calendarsOfEventsToDelete.push(folder); + _batchDeleteEvents(); + } } } @@ -443,7 +449,6 @@ function performDeleteEventCallback(http) { var calendar = nodes[0].calendar; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; - // log("node: " + node); node.parentNode.removeChild(node); } var basename = calendar + "-" + cname; @@ -453,11 +458,12 @@ function performDeleteEventCallback(http) { if (row) row.parentNode.removeChild(row); + // Update calendar events cache var occurences = calendarEvents[calendar][cname]; var newOccurences = []; for (var i = 0; i < occurences.length; i++) { var occurence = occurences[i]; - if (occurence[13] != recurrenceTime) + if (occurence[13] != occurenceTime) newOccurences.push(occurence); } calendarEvents[calendar][cname] = newOccurences;