From d7b010526b289f3029e52da2c8ae9280922d12a1 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Thu, 11 Feb 2016 10:54:07 -0500 Subject: [PATCH] (feat) warn when double-booking attendees and offer force save option --- NEWS | 1 + .../Appointments/SOGoAppointmentObject.h | 2 +- .../Appointments/SOGoAppointmentObject.m | 296 ++++++++++-------- .../Appointments/SOGoCalendarComponent.h | 3 +- .../Appointments/SOGoCalendarComponent.m | 6 + SoObjects/Appointments/SOGoTaskObject.h | 22 +- SoObjects/Appointments/SOGoTaskObject.m | 3 +- SoObjects/SOGo/SOGoDateFormatter.h | 23 +- SoObjects/SOGo/SOGoDateFormatter.m | 10 +- UI/Scheduler/UIxAppointmentActions.h | 4 +- UI/Scheduler/UIxAppointmentActions.m | 22 +- UI/Scheduler/UIxAppointmentEditor.h | 2 +- UI/Scheduler/UIxAppointmentEditor.m | 16 +- 13 files changed, 218 insertions(+), 192 deletions(-) diff --git a/NEWS b/NEWS index 3b895380a..7e95a375e 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ Enhancements - [web] now supports RFC6154 and NoInferiors IMAP flag - [web] improved confirm dialogs for deletions - [web] allow resources to prevent invitations (#3410) + - [web] warn when double-booking attendees and offer force save option Bug fixes - [web] handle birthday dates before 1970 diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 8ab78d0a0..3450189bd 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2014 Inverse inc. + Copyright (C) 2007-2016 Inverse inc. This file is part of SOGo diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 064674356..83be4431a 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1,6 +1,5 @@ /* - Copyright (C) 2007-2015 Inverse inc. - Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2007-2016 Inverse inc. This file is part of SOGo @@ -55,6 +54,7 @@ #import #import #import +#import #import #import #import @@ -415,8 +415,8 @@ } // This method scans the list of attendees. -- (NSException *) _handleAttendeeAvailability: (NSArray *) theAttendees - forEvent: (iCalEvent *) theEvent +- (NSException *) _handleAttendeesAvailability: (NSArray *) theAttendees + forEvent: (iCalEvent *) theEvent { iCalPerson *currentAttendee; SOGoUser *user; @@ -496,20 +496,20 @@ // // This methods scans the list of attendees. If they are // considered as resource, it checks for conflicting -// dates for the event. +// dates for the event and potentially auto-accept/decline +// the invitation. // +// For normal attendees, it'll return an exception with +// conflicting dates, unless we force the save.// // We check for between startDate + 1 second and // endDate - 1 second // -// -// It also CHANGES the participation status of resources -// depending on constraints defined on them. -// // Note that it doesn't matter if it changes the participation // status since in case of an error, nothing will get saved. // -- (NSException *) _handleResourcesConflicts: (NSArray *) theAttendees +- (NSException *) _handleAttendeesConflicts: (NSArray *) theAttendees forEvent: (iCalEvent *) theEvent + force: (BOOL) forceSave { iCalPerson *currentAttendee; NSMutableArray *attendees; @@ -540,110 +540,115 @@ enumerator = [attendees objectEnumerator]; while ((currentUID = [enumerator nextObject])) { + NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate; + SOGoAppointmentFolder *folder; + NGCalendarDateRange *range; + NSMutableArray *fbInfo; + NSArray *allOccurences; + + BOOL must_delete; + int i, j, delta; + user = [SOGoUser userWithLogin: currentUID]; - - if ([user isResource]) + + // We get the start/end date for our conflict range. If the event to be added is recurring, we + // check for at least a year to start with. + start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; + end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; + + folder = [user personalCalendarFolderInContext: context]; + + // Deny access to the resource if the ACLs don't allow the user + if (![folder aclSQLListingFilter]) { - NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate; - SOGoAppointmentFolder *folder; - NGCalendarDateRange *range; - NSMutableArray *fbInfo; - NSArray *allOccurences; - - BOOL must_delete; - int i, j, delta; - - // We get the start/end date for our conflict range. If the event to be added is recurring, we - // check for at least a year to start with. - start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; - end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; - - folder = [user personalCalendarFolderInContext: context]; - - // Deny access to the resource if the ACLs don't allow the user - if (![folder aclSQLListingFilter]) - { - NSDictionary *values; - NSString *reason; + NSDictionary *values; + NSString *reason; - values = [NSDictionary dictionaryWithObjectsAndKeys: - [user cn], @"Cn", - [user systemEmail], @"SystemEmail"]; - reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; - return [NSException exceptionWithHTTPStatus:403 reason: reason]; - } + values = [NSDictionary dictionaryWithObjectsAndKeys: + [user cn], @"Cn", + [user systemEmail], @"SystemEmail"]; + reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; + return [NSException exceptionWithHTTPStatus:403 reason: reason]; + } - fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start + fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start to: end]]; - // We first remove any occurences in the freebusy that corresponds to the - // current event. We do this to avoid raising a conflict if we move a 1 hour - // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. - // - // We must also check here for repetitive events that don't overlap our event. - // We remove all events that don't overlap. The events here are already - // decomposed. - // - if ([theEvent isRecurrent]) - allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start - endDate: end] - firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate] - endDate: [theEvent endDate]]]; - else - allOccurences = nil; - - for (i = [fbInfo count]-1; i >= 0; i--) + // We first remove any occurences in the freebusy that corresponds to the + // current event. We do this to avoid raising a conflict if we move a 1 hour + // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. + // + // We must also check here for repetitive events that don't overlap our event. + // We remove all events that don't overlap. The events here are already + // decomposed. + // + if ([theEvent isRecurrent]) + allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start + endDate: end] + firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate] + endDate: [theEvent endDate]]]; + else + allOccurences = nil; + + for (i = [fbInfo count]-1; i >= 0; i--) + { + // We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been + // modified in SOGoAppointmentFolder: -fixupCycleRecord: .... + rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"]; + delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT]; + rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta]; + + rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"]; + delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent endDate]] secondsOffsetFromGMT]; + rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta]; + + range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate + endDate: rangeEndDate]; + + // We remove the freebusy entries corresponding to the actual event being modified + if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) { - // We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been - // modified in SOGoAppointmentFolder: -fixupCycleRecord: .... - rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"]; - delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT]; - rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta]; - - rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"]; - delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent endDate]] secondsOffsetFromGMT]; - rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta]; - - range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate - endDate: rangeEndDate]; + [fbInfo removeObjectAtIndex: i]; + continue; + } - if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) + // No need to check if the event isn't recurrent here as it's handled correctly + // when we compute the "end" date. + if ([allOccurences count]) + { + must_delete = YES; + + for (j = 0; j < [allOccurences count]; j++) { - [fbInfo removeObjectAtIndex: i]; - continue; - } - - // No need to check if the event isn't recurrent here as it's handled correctly - // when we compute the "end" date. - if ([allOccurences count]) - { - must_delete = YES; - - for (j = 0; j < [allOccurences count]; j++) + if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]]) { - if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]]) - { - must_delete = NO; - break; - } + must_delete = NO; + break; } - - if (must_delete) - [fbInfo removeObjectAtIndex: i]; } + + if (must_delete) + [fbInfo removeObjectAtIndex: i]; } + } + + // Find the attendee associated to the current UID + for (i = 0; i < [theAttendees count]; i++) + { + currentAttendee = [theAttendees objectAtIndex: i]; + if ([[currentAttendee uidInContext: context] isEqualToString: currentUID]) + break; + else + currentAttendee = nil; + } + + if ([fbInfo count]) + { + SOGoDateFormatter *formatter; + + formatter = [[context activeUser] dateFormatterInContext: context]; - // Find the attendee associated to the current UID - for (i = 0; i < [theAttendees count]; i++) - { - currentAttendee = [theAttendees objectAtIndex: i]; - if ([[currentAttendee uidInContext: context] isEqualToString: currentUID]) - break; - else - currentAttendee = nil; - } - - if ([fbInfo count]) + if ([user isResource]) { // If we always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit // is imposed) or if numberOfSimultaneousBookings is greater than the number of @@ -663,35 +668,63 @@ NSDictionary *values; NSString *reason; iCalEvent *event; - + calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]]; event = [[calendar events] lastObject]; - + values = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", - [user cn], @"Cn", - [user systemEmail], @"SystemEmail", - ([event summary] ? [event summary] : @""), @"EventTitle", - [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate", - nil]; - + [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", + [user cn], @"Cn", + [user systemEmail], @"SystemEmail", + ([event summary] ? [event summary] : @""), @"EventTitle", + [formatter formattedDateAndTime: [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"]], @"StartDate", + nil]; + reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; - + return [NSException exceptionWithHTTPStatus: 403 reason: reason]; } } - else if (currentAttendee) + // + // We are dealing with a normal attendee. Lets check if we have conflicts, unless + // we are being asked to force the save anyway + // + else if (!forceSave) { - // No conflict, we auto-accept. We do this for resources automatically if no - // double-booking is observed. If it's not the desired behavior, just don't - // set the resource as one! - [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; - [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + NSMutableDictionary *info; + NSMutableArray *conflicts; + id o; + + info = [NSMutableDictionary dictionary]; + conflicts = [NSMutableArray array]; + + [info setObject: [currentAttendee cn] forKey: @"attendee_name"]; + [info setObject: [currentAttendee rfc822Email] forKey: @"attendee_email"]; + + for (i = 0; i < [fbInfo count]; i++) + { + o = [fbInfo objectAtIndex: i]; + [conflicts addObject: [NSDictionary dictionaryWithObjectsAndKeys: [formatter formattedDateAndTime: [o objectForKey: @"startDate"]], @"startDate", + [formatter formattedDateAndTime: [o objectForKey: @"endDate"]], @"endDate", nil]]; + } + + [info setObject: conflicts forKey: @"conflicts"]; + + return [NSException exceptionWithHTTPStatus: 403 + reason: [info jsonRepresentation]]; } + } // if ([fbInfo count]) ... + else if (currentAttendee && [user isResource]) + { + // No conflict, we auto-accept. We do this for resources automatically if no + // double-booking is observed. If it's not the desired behavior, just don't + // set the resource as one! + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; } - } - + } // if ([user isResource]) ... + return nil; } @@ -700,6 +733,7 @@ // - (NSException *) _handleAddedUsers: (NSArray *) attendees fromEvent: (iCalEvent *) newEvent + force: (BOOL) forceSave { iCalPerson *currentAttendee; NSEnumerator *enumerator; @@ -707,9 +741,9 @@ NSException *e; // We check for conflicts - if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent])) + if ((e = [self _handleAttendeesConflicts: attendees forEvent: newEvent force: forceSave])) return e; - if ((e = [self _handleAttendeeAvailability: attendees forEvent: newEvent])) + if ((e = [self _handleAttendeesAvailability: attendees forEvent: newEvent])) return e; enumerator = [attendees objectEnumerator]; @@ -765,6 +799,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // - (NSException *) _handleUpdatedEvent: (iCalEvent *) newEvent fromOldEvent: (iCalEvent *) oldEvent + force: (BOOL) forceSave { NSArray *addedAttendees, *deletedAttendees, *updatedAttendees; iCalEventChanges *changes; @@ -804,9 +839,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent withType: @"calendar:cancellation"]; } - if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent])) + if ((ex = [self _handleAttendeesConflicts: [newEvent attendees] forEvent: newEvent force: forceSave])) return ex; - if ((ex = [self _handleAttendeeAvailability: [newEvent attendees] forEvent: newEvent])) + if ((ex = [self _handleAttendeesAvailability: [newEvent attendees] forEvent: newEvent])) return ex; addedAttendees = [changes insertedAttendees]; @@ -855,7 +890,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if ([addedAttendees count]) { // Send an invitation to new attendees - if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent])) + if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent force: forceSave])) return ex; [self sendEMailUsingTemplateNamed: @"Invitation" @@ -900,6 +935,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // // - (NSException *) saveComponent: (iCalEvent *) newEvent +{ + return [self saveComponent: newEvent force: NO]; +} + +- (NSException *) saveComponent: (iCalEvent *) newEvent + force: (BOOL) forceSave { iCalEvent *oldEvent, *oldMasterEvent; NSCalendarDate *recurrenceId; @@ -924,7 +965,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // We catch conflicts and abort the save process immediately // in case of one with resources - if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) + if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent force: forceSave])) return ex; if ([attendees count]) @@ -967,7 +1008,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser]) // The owner is the organizer of the event; handle the modifications. We aslo // catch conflicts just like when the events are created - if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) + if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent force: forceSave])) return ex; } @@ -1604,7 +1645,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent currentUser = [context activeUser]; attendees = [occurence attendeesWithoutUser: currentUser]; -#warning Make sure this is correct .. if (![attendees count] && event != occurence) attendees = [event attendeesWithoutUser: currentUser]; @@ -2007,7 +2047,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent attendees = [event attendeesWithoutUser: ownerUser]; if ([attendees count]) { - if ((ex = [self _handleAddedUsers: attendees fromEvent: event])) + if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES])) return ex; else { @@ -2159,7 +2199,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent if (!newEvent && oldEvent) [self prepareDeleteOccurence: oldEvent]; // The master event was changed, A RECCURENCE-ID was added or modified - else if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent])) + else if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent force: YES])) return ex; } // diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index e1e2bc7bd..1595a1c75 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -61,7 +61,8 @@ - (void) updateComponent: (iCalRepeatableEntityObject *) newObject; - (NSException *) saveCalendar: (iCalCalendar *) newCalendar; - (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject; - +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newEvent + force: (BOOL) forceSave; /* mail notifications */ - (void) sendEMailUsingTemplateNamed: (NSString *) pageName forObject: (iCalRepeatableEntityObject *) object diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 762139774..ef158d268 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -685,6 +685,12 @@ return [self saveCalendar: [newObject parent]]; } +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newEvent + force: (BOOL) forceSave +{ + return [self saveComponent: newEvent]; +} + /* raw saving */ /* EMail Notifications */ diff --git a/SoObjects/Appointments/SOGoTaskObject.h b/SoObjects/Appointments/SOGoTaskObject.h index 5d15c856a..15512a21b 100644 --- a/SoObjects/Appointments/SOGoTaskObject.h +++ b/SoObjects/Appointments/SOGoTaskObject.h @@ -1,7 +1,5 @@ /* - - Copyright (C) 2006-2014 Inverse inc. - Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2006-2014-2016 Inverse inc. This file is part of SOGo. @@ -26,24 +24,6 @@ #import "SOGoCalendarComponent.h" -/* - SOGoTaskObject - - Represents a single task. This SOPE controller object manages all the - attendee storages (that is, it might store into multiple folders for meeting - tasks!). - - Note: SOGoTaskObject do not need to exist yet. They can also be "new" - tasks with an externally generated unique key. -*/ - -@class NSArray; -@class NSException; -@class NSString; - -@class iCalToDo; -@class iCalCalendar; - @interface SOGoTaskObject : SOGoCalendarComponent @end diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index 3e1343726..d936c28bf 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -1,6 +1,5 @@ /* - Copyright (C) 2006-2014 Inverse inc. - Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2006-2014-2016 Inverse inc. This file is part of SOGo. diff --git a/SoObjects/SOGo/SOGoDateFormatter.h b/SoObjects/SOGo/SOGoDateFormatter.h index 3889bff51..6eeec8c97 100644 --- a/SoObjects/SOGo/SOGoDateFormatter.h +++ b/SoObjects/SOGo/SOGoDateFormatter.h @@ -1,14 +1,14 @@ /* - Copyright (C) 2004 SKYRIX Software AG + Copyright (C) 2005-2016 Inverse inc. - This file is part of OpenGroupware.org. + This file is part of SOGo. - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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 Lesser General Public License for more details. @@ -48,21 +48,6 @@ - (NSString *) stringForObjectValue: (id) date; -// - (void) setFullWeekdayNameAndDetails; - -// - (NSString *) date: (NSCalendarDate *) date -// withFormat: (unsigned int) format; -// - (NSString *) date: (NSCalendarDate *) date -// withNSFormat: (NSNumber *) format; - - -// - (NSString *) shortDayOfWeek: (int)_day; -// - (NSString *) fullDayOfWeek: (int)_day; -// - (NSString *) shortMonthOfYear: (int)_month; -// - (NSString *) fullMonthOfYear: (int)_month; - -// - (NSString *) fullWeekdayNameAndDetailsForDate: (NSCalendarDate *)_date; - @end #endif /* __SOGoDateFormatter_H_ */ diff --git a/SoObjects/SOGo/SOGoDateFormatter.m b/SoObjects/SOGo/SOGoDateFormatter.m index a31af79ee..b16420acd 100644 --- a/SoObjects/SOGo/SOGoDateFormatter.m +++ b/SoObjects/SOGo/SOGoDateFormatter.m @@ -1,14 +1,14 @@ /* - Copyright (C) 2004 SKYRIX Software AG + Copyright (C) 2005-2016 Inverse inc. - This file is part of OpenGroupware.org. + This file is part of SOGo. - OGo is free software; you can redistribute it and/or modify it under + SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - OGo is distributed in the hope that it will be useful, but WITHOUT ANY + SOGo 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 Lesser General Public License for more details. @@ -21,7 +21,7 @@ #import #import -#import /* for NSXXXFormatString, ... */ +#import #import "SOGoDateFormatter.h" diff --git a/UI/Scheduler/UIxAppointmentActions.h b/UI/Scheduler/UIxAppointmentActions.h index 58657e3d1..087e3285f 100644 --- a/UI/Scheduler/UIxAppointmentActions.h +++ b/UI/Scheduler/UIxAppointmentActions.h @@ -1,8 +1,6 @@ /* UIxAppointmentActions.h - this file is part of SOGo * - * Copyright (C) 2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-2016 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 diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index 9d65f5182..29deb584f 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -1,8 +1,6 @@ /* UIxAppointmentActions.m - this file is part of SOGo * - * Copyright (C) 2011-2014 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-2016 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 @@ -69,6 +67,7 @@ NSException *ex; SOGoAppointmentFolder *targetCalendar, *sourceCalendar; SOGoAppointmentFolders *folders; + BOOL forceSave; rq = [context request]; params = [[rq contentAsString] objectFromJSONString]; @@ -77,6 +76,7 @@ startDelta = [params objectForKey: @"start"]; durationDelta = [params objectForKey: @"duration"]; destionationCalendar = [params objectForKey: @"destination"]; + forceSave = NO; if (daysDelta || startDelta || durationDelta) { @@ -120,7 +120,8 @@ [event updateRecurrenceRulesUntilDate: end]; [event setLastModified: [NSCalendarDate calendarDate]]; - ex = [co saveComponent: event]; + ex = [co saveComponent: event force: forceSave]; + // This condition will be executed only if the event is moved from a calendar to another. If destionationCalendar == 0; there is no calendar change if ([destionationCalendar length] > 0) { @@ -139,12 +140,21 @@ ex = [co moveToFolder: targetCalendar]; } } + if (ex) { + unsigned int httpStatus; + + httpStatus = 500; + + if ([ex respondsToSelect: @selector(httpStatus)]) + httpStatus = [ex httpStatus]; + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: - [ex reason], @"message", + [ex reason], @"message", nil]; - response = [self responseWithStatus: 403 + + response = [self responseWithStatus: httpStatus andJSONRepresentation: jsonResponse]; } else diff --git a/UI/Scheduler/UIxAppointmentEditor.h b/UI/Scheduler/UIxAppointmentEditor.h index ece2c9c6d..6c1a28b50 100644 --- a/UI/Scheduler/UIxAppointmentEditor.h +++ b/UI/Scheduler/UIxAppointmentEditor.h @@ -1,6 +1,6 @@ /* UIxAppointmentEditor.h - this file is part of SOGo * - * Copyright (C) 2007-2015 Inverse inc. + * Copyright (C) 2007-2016 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 diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index be5aa1ddc..f8942e43f 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -1,6 +1,6 @@ /* UIxAppointmentEditor.m - this file is part of SOGo * - * Copyright (C) 2007-2015 Inverse inc. + * Copyright (C) 2007-2016 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 @@ -467,7 +467,9 @@ SOGoAppointmentObject *co; SoSecurityManager *sm; WORequest *request; + unsigned int httpStatus; + BOOL forceSave; event = [self event]; co = [self clientObject]; @@ -488,6 +490,7 @@ else { [self setAttributes: params]; + forceSave = NO; if ([event hasRecurrenceRules]) [self _adjustRecurrentRules]; @@ -511,12 +514,12 @@ } // Save the event. - ex = [co saveComponent: event]; + ex = [co saveComponent: event force: forceSave]; } else { // The event was modified -- save it. - ex = [co saveComponent: event]; + ex = [co saveComponent: event force: forceSave]; if (componentCalendar && ![[componentCalendar ocsPath] @@ -539,9 +542,12 @@ if (ex) { httpStatus = 500; + + if ([ex respondsToSelect: @selector(httpStatus)]) + httpStatus = [ex httpStatus]; + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: - @"failure", @"status", - [ex reason], @"message", + [ex reason], @"message", nil]; } else