diff --git a/NEWS b/NEWS
index 0369dbd2c..17702560b 100644
--- a/NEWS
+++ b/NEWS
@@ -5,7 +5,7 @@ New features
-
Enhancements
- -
+ - [web] don't allow a recurrence rule to end before the first occurrence
Bug fixes
- [eas] properly generate the BusyStatus for normal events
diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings
index 67800b4b1..0dd662120 100644
--- a/UI/Scheduler/English.lproj/Localizable.strings
+++ b/UI/Scheduler/English.lproj/Localizable.strings
@@ -379,6 +379,8 @@ validate_notitle = "No title is set, continue?";
validate_invalid_startdate = "Incorrect startdate field!";
validate_invalid_enddate = "Incorrect enddate field!";
validate_endbeforestart = "The end date that you entered occurs before the start date.";
+validate_untilbeforeend = "The recurrence must end after the first occurrence.";
+
"Events" = "Events";
"Tasks" = "Tasks";
"Show completed tasks" = "Show completed tasks";
diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m
index c8fc7bcd2..e05d55fd1 100644
--- a/UI/Scheduler/UIxAppointmentEditor.m
+++ b/UI/Scheduler/UIxAppointmentEditor.m
@@ -187,17 +187,19 @@
}
*/
-- (void) _adjustRecurrentRules
+- (NSException *) _adjustRecurrentRules
{
iCalEvent *event;
iCalRecurrenceRule *rule;
NSEnumerator *rules;
+ NSException *ex;
NSCalendarDate *untilDate;
SOGoUserDefaults *ud;
NSTimeZone *timeZone;
event = [self event];
rules = [[event recurrenceRules] objectEnumerator];
+ ex = nil;
ud = [[context activeUser] userDefaults];
timeZone = [ud timeZone];
@@ -206,22 +208,33 @@
untilDate = [rule untilDate];
if (untilDate)
{
- // The until date must match the time of the end date
- NSCalendarDate *date;
+ if ([untilDate compare: [event endDate]] == NSOrderedAscending)
+ {
+ ex = [NSException exceptionWithHTTPStatus: 500
+ reason: [self labelForKey: @"validate_untilbeforeend"]];
+ break;
+ }
+ else
+ {
+ // The until date must match the time of the end date
+ NSCalendarDate *date;
- date = [[event endDate] copy];
- [date setTimeZone: timeZone];
- [untilDate setTimeZone: timeZone];
- untilDate = [untilDate dateByAddingYears:0
- months:0
- days:0
- hours:[date hourOfDay]
- minutes:[date minuteOfHour]
- seconds:0];
- [rule setUntilDate: untilDate];
- [date release];
+ date = [[event endDate] copy];
+ [date setTimeZone: timeZone];
+ [untilDate setTimeZone: timeZone];
+ untilDate = [untilDate dateByAddingYears:0
+ months:0
+ days:0
+ hours:[date hourOfDay]
+ minutes:[date minuteOfHour]
+ seconds:0];
+ [rule setUntilDate: untilDate];
+ [date release];
+ }
}
}
+
+ return ex;
}
/**
@@ -481,47 +494,50 @@
forceSave = [[params objectForKey: @"ignoreConflicts"] boolValue];
if ([event hasRecurrenceRules])
- [self _adjustRecurrentRules];
+ ex = [self _adjustRecurrentRules];
- if ([co isNew])
+ if (!ex)
{
- if (componentCalendar
- && ![[componentCalendar ocsPath]
- isEqualToString: [previousCalendar ocsPath]])
+ if ([co isNew])
{
- // New event in a different calendar -- make sure the user can
- // write to the selected calendar since the rights were verified
- // on the calendar specified in the URL, not on the selected
- // calendar of the popup menu.
- if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
- onObject: componentCalendar
- inContext: context])
- co = [componentCalendar lookupName: [co nameInContainer]
- inContext: context
- acquire: NO];
- }
-
- // Save the event.
- ex = [co saveComponent: event force: forceSave];
- }
- else
- {
- // The event was modified -- save it.
- ex = [co saveComponent: event force: forceSave];
-
- if (componentCalendar
- && ![[componentCalendar ocsPath]
- isEqualToString: [previousCalendar ocsPath]])
- {
- // The event was moved to a different calendar.
- if (![sm validatePermission: SoPerm_DeleteObjects
- onObject: previousCalendar
- inContext: context])
+ if (componentCalendar
+ && ![[componentCalendar ocsPath]
+ isEqualToString: [previousCalendar ocsPath]])
{
+ // New event in a different calendar -- make sure the user can
+ // write to the selected calendar since the rights were verified
+ // on the calendar specified in the URL, not on the selected
+ // calendar of the popup menu.
if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
onObject: componentCalendar
inContext: context])
- ex = [co moveToFolder: componentCalendar];
+ co = [componentCalendar lookupName: [co nameInContainer]
+ inContext: context
+ acquire: NO];
+ }
+
+ // Save the event.
+ ex = [co saveComponent: event force: forceSave];
+ }
+ else
+ {
+ // The event was modified -- save it.
+ ex = [co saveComponent: event force: forceSave];
+
+ if (componentCalendar
+ && ![[componentCalendar ocsPath]
+ isEqualToString: [previousCalendar ocsPath]])
+ {
+ // The event was moved to a different calendar.
+ if (![sm validatePermission: SoPerm_DeleteObjects
+ onObject: previousCalendar
+ inContext: context])
+ {
+ if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles
+ onObject: componentCalendar
+ inContext: context])
+ ex = [co moveToFolder: componentCalendar];
+ }
}
}
}
diff --git a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
index 9376bfff1..55669bdc4 100644
--- a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
+++ b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox
@@ -214,9 +214,15 @@
-
+
+
+