Add support for events with recurrence dates

pull/239/head
Francis Lachapelle 2017-12-15 16:17:51 -05:00
parent e61a55f30e
commit 7f99514744
15 changed files with 337 additions and 48 deletions

1
NEWS
View File

@ -10,6 +10,7 @@ New features
- [web] register SOGo as a handler for the mailto scheme (#1223) - [web] register SOGo as a handler for the mailto scheme (#1223)
- [web] new events list view where events are grouped by day - [web] new events list view where events are grouped by day
- [web] user setting to always show mail editor inside current window or in popup window - [web] user setting to always show mail editor inside current window or in popup window
- [web] add support for events with recurrence dates (RDATE)
Enhancements Enhancements
- [web] follow requested URL after user authentication - [web] follow requested URL after user authentication

View File

@ -46,6 +46,7 @@
firstInstanceCalendarDateRange: (NGCalendarDateRange *) _fir firstInstanceCalendarDateRange: (NGCalendarDateRange *) _fir
recurrenceRules: (NSArray *) _rRules recurrenceRules: (NSArray *) _rRules
exceptionRules: (NSArray *) _exRules exceptionRules: (NSArray *) _exRules
recurrenceDates: (NSArray *) _rDates
exceptionDates: (NSArray *) _exDates; exceptionDates: (NSArray *) _exDates;
+ (id) recurrenceCalculatorForRecurrenceRule: (iCalRecurrenceRule *) _rrule + (id) recurrenceCalculatorForRecurrenceRule: (iCalRecurrenceRule *) _rrule
@ -54,8 +55,7 @@
- (id) initWithRecurrenceRule: (iCalRecurrenceRule *) _rrule - (id) initWithRecurrenceRule: (iCalRecurrenceRule *) _rrule
firstInstanceCalendarDateRange: (NGCalendarDateRange *) _range; firstInstanceCalendarDateRange: (NGCalendarDateRange *) _range;
- (NSArray *) - (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *)_r;
recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *)_r;
- (BOOL) doesRecurrWithinCalendarDateRange: (NGCalendarDateRange *) _range; - (BOOL) doesRecurrWithinCalendarDateRange: (NGCalendarDateRange *) _range;
- (NGCalendarDateRange *) firstInstanceCalendarDateRange; - (NGCalendarDateRange *) firstInstanceCalendarDateRange;

View File

@ -134,6 +134,17 @@ static Class yearlyCalcClass = Nil;
} }
} }
+ (void) _fillRanges: (NSMutableArray *) ranges
fromDates: (NSArray *) rdates
withinRange: (NGCalendarDateRange *) limits
startingWithDate: (NGCalendarDateRange *) first
{
NSArray *dates;
dates = [self _ranges: rdates withinRange: limits startingWithDate: first];
[ranges addObjectsFromArray: dates];
}
+ (void) _removeExceptionsFromRanges: (NSMutableArray *) ranges + (void) _removeExceptionsFromRanges: (NSMutableArray *) ranges
withRules: (NSArray *) exrules withRules: (NSArray *) exrules
withinRange: (NGCalendarDateRange *) limits withinRange: (NGCalendarDateRange *) limits
@ -159,8 +170,30 @@ static Class yearlyCalcClass = Nil;
} }
+ (NSArray *) _dates: (NSArray *) dateList + (NSArray *) _dates: (NSArray *) dateList
withinRange: (NGCalendarDateRange *) limits withinRange: (NGCalendarDateRange *) limits
startingWithDate: (NGCalendarDateRange *) first startingWithDate: (NGCalendarDateRange *) first
{
return [self _dates: dateList
withinRange: limits
startingWithDate: first
ranges: NO];
}
+ (NSArray *) _ranges: (NSArray *) dateList
withinRange: (NGCalendarDateRange *) limits
startingWithDate: (NGCalendarDateRange *) first
{
return [self _dates: dateList
withinRange: limits
startingWithDate: first
ranges: YES];
}
+ (NSArray *) _dates: (NSArray *) dateList
withinRange: (NGCalendarDateRange *) limits
startingWithDate: (NGCalendarDateRange *) first
ranges: (BOOL) returnRanges
{ {
NSMutableArray *newDates; NSMutableArray *newDates;
NSEnumerator *dates; NSEnumerator *dates;
@ -178,7 +211,12 @@ static Class yearlyCalcClass = Nil;
currentRange = [NGCalendarDateRange calendarDateRangeWithStartDate: currentDate currentRange = [NGCalendarDateRange calendarDateRangeWithStartDate: currentDate
endDate: [currentDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: [first duration]]]; endDate: [currentDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: [first duration]]];
if ([limits doesIntersectWithDateRange: currentRange]) if ([limits doesIntersectWithDateRange: currentRange])
[newDates addObject: currentDate]; {
if (returnRanges)
[newDates addObject: currentRange];
else
[newDates addObject: currentDate];
}
} }
return newDates; return newDates;
@ -217,16 +255,19 @@ static Class yearlyCalcClass = Nil;
firstInstanceCalendarDateRange: (NGCalendarDateRange *) _fir firstInstanceCalendarDateRange: (NGCalendarDateRange *) _fir
recurrenceRules: (NSArray *) _rRules recurrenceRules: (NSArray *) _rRules
exceptionRules: (NSArray *) _exRules exceptionRules: (NSArray *) _exRules
recurrenceDates: (NSArray *) _rDates
exceptionDates: (NSArray *) _exDates exceptionDates: (NSArray *) _exDates
{ {
NSMutableArray *ranges; NSMutableArray *ranges;
ranges = [NSMutableArray arrayWithCapacity: 64]; ranges = [NSMutableArray arrayWithCapacity: 64];
if ([_rRules count] > 0) if ([_rRules count] > 0 || [_rDates count] > 0)
{ {
[self _fillRanges: ranges fromRules: _rRules [self _fillRanges: ranges fromRules: _rRules
withinRange: _r startingWithDate: _fir]; withinRange: _r startingWithDate: _fir];
[self _fillRanges: ranges fromDates: _rDates
withinRange: _r startingWithDate: _fir];
[self _removeExceptionsFromRanges: ranges withRules: _exRules [self _removeExceptionsFromRanges: ranges withRules: _exRules
withinRange: _r startingWithDate: _fir]; withinRange: _r startingWithDate: _fir];
[self _removeExceptionDatesFromRanges: ranges withDates: _exDates [self _removeExceptionDatesFromRanges: ranges withDates: _exDates

View File

@ -44,6 +44,12 @@
- (NSArray *)recurrenceRules; - (NSArray *)recurrenceRules;
- (NSArray *)recurrenceRulesWithTimeZone: (id) timezone; - (NSArray *)recurrenceRulesWithTimeZone: (id) timezone;
- (void) removeAllRecurrenceDates;
- (void) addToRecurrenceDates: (NSCalendarDate *) _rdate;
- (BOOL) hasRecurrenceDates;
- (NSArray *) recurrenceDates;
- (NSArray *) recurrenceDatesWithTimeZone: (id) theTimeZone;
- (void)removeAllExceptionRules; - (void)removeAllExceptionRules;
- (void)addToExceptionRules:(id)_rrule; - (void)addToExceptionRules:(id)_rrule;
- (BOOL)hasExceptionRules; - (BOOL)hasExceptionRules;

View File

@ -1,6 +1,6 @@
/* /*
Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2012 Inverse inc. Copyright (C) 2017 Inverse inc.
This file is part of SOPE. This file is part of SOPE.
@ -40,6 +40,8 @@
if ([classTag isEqualToString: @"RRULE"]) if ([classTag isEqualToString: @"RRULE"])
tagClass = [iCalRecurrenceRule class]; tagClass = [iCalRecurrenceRule class];
else if ([classTag isEqualToString: @"RDATE"])
tagClass = [iCalDateTime class];
else if ([classTag isEqualToString: @"EXDATE"]) else if ([classTag isEqualToString: @"EXDATE"])
tagClass = [iCalDateTime class]; tagClass = [iCalDateTime class];
else else
@ -84,6 +86,108 @@
return [self rules: rules withTimeZone: timezone]; return [self rules: rules withTimeZone: timezone];
} }
- (void) removeAllRecurrenceDates
{
[self removeChildren: [self childrenWithTag: @"rdate"]];
}
- (void) addToRecurrenceDates: (NSCalendarDate *) _rdate
{
iCalDateTime *dateTime;
dateTime = [iCalDateTime new];
[dateTime setTag: @"rdate"];
if ([self isKindOfClass: [iCalEvent class]] && [(iCalEvent *)self isAllDay])
[dateTime setDate: _rdate];
else
[dateTime setDateTime: _rdate];
[self addChild: dateTime];
[dateTime release];
}
- (BOOL) hasRecurrenceDates
{
return ([[self childrenWithTag: @"rdate"] count] > 0);
}
/**
* Returns the recurrence dates for the entity, but adjusted to the entity timezone.
* @param theTimeZone the timezone of the entity.
* @see [iCalTimeZone computedDatesForStrings:]
* @return the exception dates, adjusted to the timezone.
*/
- (NSArray *) recurrenceDatesWithTimeZone: (id) theTimeZone
{
NSArray *dates, *rDates;
NSEnumerator *dateList;
NSCalendarDate *rDate;
NSString *dateString;
int offset;
unsigned i;
if (theTimeZone)
{
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"rdate"] objectEnumerator];
while ((dateString = [dateList nextObject]))
{
rDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [rDates count]; i++)
{
rDate = [rDates objectAtIndex: i];
// Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000),
// and changes to 2012-05-24 04:00:00 +0000
if ([theTimeZone isKindOfClass: [iCalTimeZone class]])
{
rDate = [(iCalTimeZone *) theTimeZone computedDateForDate: rDate];
}
else
{
offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: rDate];
rDate = (NSCalendarDate *) [rDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
}
[(NSMutableArray *) dates addObject: rDate];
}
}
}
else
dates = [self recurrenceDates];
return dates;
}
/**
* Return the recurrence dates of the entity in GMT.
* @return an array of NSCalendarDate instances.
*/
- (NSArray *) recurrenceDates
{
NSArray *rDates;
NSMutableArray *dates;
NSEnumerator *dateList;
NSCalendarDate *rDate;
NSString *dateString;
unsigned i;
dates = [NSMutableArray array];
dateList = [[self childrenWithTag: @"rdate"] objectEnumerator];
while ((dateString = [dateList nextObject]))
{
rDates = [(iCalDateTime*) dateString dateTimes];
for (i = 0; i < [rDates count]; i++)
{
rDate = [rDates objectAtIndex: i];
[dates addObject: rDate];
}
}
return dates;
}
- (void) removeAllExceptionRules - (void) removeAllExceptionRules
{ {
[self removeChildren: [self exceptionRules]]; [self removeChildren: [self exceptionRules]];
@ -284,7 +388,7 @@
- (BOOL) isRecurrent - (BOOL) isRecurrent
{ {
return [self hasRecurrenceRules]; return [self hasRecurrenceRules] || [self hasRecurrenceDates];
} }
/* Matching */ /* Matching */
@ -303,10 +407,11 @@
firstInstanceCalendarDateRange: (NGCalendarDateRange *)_fir firstInstanceCalendarDateRange: (NGCalendarDateRange *)_fir
{ {
return [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: _r return [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: _r
firstInstanceCalendarDateRange: _fir firstInstanceCalendarDateRange: _fir
recurrenceRules: [self recurrenceRules] recurrenceRules: [self recurrenceRules]
exceptionRules: [self exceptionRules] exceptionRules: [self exceptionRules]
exceptionDates: [self exceptionDates]]; recurrenceDates: [self recurrenceDates]
exceptionDates: [self exceptionDates]];
} }
@ -315,27 +420,43 @@
lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: (NGCalendarDateRange *)_r lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: (NGCalendarDateRange *)_r
{ {
NSCalendarDate *date; NSCalendarDate *date;
NSEnumerator *rRules;
iCalRecurrenceRule *rule;
iCalRecurrenceCalculator *calc;
NSCalendarDate *rdate; NSCalendarDate *rdate;
date = nil; date = nil;
rRules = [[self recurrenceRules] objectEnumerator]; if ([self hasRecurrenceRules])
rule = [rRules nextObject];
while (rule && ![rule isInfinite] && !date)
{ {
calc = [iCalRecurrenceCalculator NSEnumerator *rRules;
recurrenceCalculatorForRecurrenceRule: rule iCalRecurrenceRule *rule;
withFirstInstanceCalendarDateRange: _r]; iCalRecurrenceCalculator *calc;
rdate = [[calc lastInstanceCalendarDateRange] startDate];
if (!rdate) rRules = [[self recurrenceRules] objectEnumerator];
date = [_r startDate]; rule = [rRules nextObject];
else if (!date || ([date compare: rdate] == NSOrderedAscending)) while (rule && ![rule isInfinite] && !date)
date = rdate; {
else calc = [iCalRecurrenceCalculator
rule = [rRules nextObject]; recurrenceCalculatorForRecurrenceRule: rule
withFirstInstanceCalendarDateRange: _r];
rdate = [[calc lastInstanceCalendarDateRange] startDate];
if (!rdate)
date = [_r startDate];
else if (!date || ([date compare: rdate] == NSOrderedAscending))
date = rdate;
else
rule = [rRules nextObject];
}
}
if ([self hasRecurrenceDates])
{
NSEnumerator *rDates;
rDates = [[self recurrenceDates] objectEnumerator];
while ((rdate = [rDates nextObject]))
{
if (!date || ([date compare: rdate] == NSOrderedAscending))
date = rdate;
}
} }
return date; return date;
@ -401,6 +522,7 @@ lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: (NGCalendarD
firstInstanceCalendarDateRange: firstInstanceRange firstInstanceCalendarDateRange: firstInstanceRange
recurrenceRules: rules recurrenceRules: rules
exceptionRules: nil exceptionRules: nil
recurrenceDates: nil
exceptionDates: nil]; exceptionDates: nil];
if ([recurrences count] > 0) if ([recurrences count] > 0)
firstOccurrenceStartDate = [[recurrences objectAtIndex: 0] firstOccurrenceStartDate = [[recurrences objectAtIndex: 0]

View File

@ -1216,10 +1216,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{ {
NSMutableDictionary *row, *fixedRow; NSMutableDictionary *row, *fixedRow;
NSMutableArray *records; NSMutableArray *records, *ranges;
NSDictionary *cycleinfo; NSDictionary *cycleinfo;
NGCalendarDateRange *firstRange, *recurrenceRange, *oneRange; NGCalendarDateRange *firstRange, *recurrenceRange, *oneRange;
NSArray *rules, *exRules, *exDates, *ranges; NSArray *rules, *exRules, *rDates, *exDates;
NSArray *components; NSArray *components;
NSString *content; NSString *content;
NSCalendarDate *checkStartDate, *checkEndDate, *firstStartDate, *firstEndDate; NSCalendarDate *checkStartDate, *checkEndDate, *firstStartDate, *firstEndDate;
@ -1268,6 +1268,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
} }
rules = [cycleinfo objectForKey: @"rules"]; rules = [cycleinfo objectForKey: @"rules"];
exRules = [cycleinfo objectForKey: @"exRules"]; exRules = [cycleinfo objectForKey: @"exRules"];
rDates = [cycleinfo objectForKey: @"rDates"];
exDates = [cycleinfo objectForKey: @"exDates"]; exDates = [cycleinfo objectForKey: @"exDates"];
eventTimeZone = nil; eventTimeZone = nil;
allDayTimeZone = nil; allDayTimeZone = nil;
@ -1333,8 +1334,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
tz = eventTimeZone ? eventTimeZone : allDayTimeZone; tz = eventTimeZone ? eventTimeZone : allDayTimeZone;
if (tz) if (tz)
{ {
// Adjust the exception dates // Adjust the recurrence and exception dates
exDates = [component exceptionDatesWithTimeZone: tz]; exDates = [component exceptionDatesWithTimeZone: tz];
rDates = [component recurrenceDatesWithTimeZone: tz];
// Adjust the recurrence rules "until" dates // Adjust the recurrence rules "until" dates
rules = [component recurrenceRulesWithTimeZone: tz]; rules = [component recurrenceRulesWithTimeZone: tz];
@ -1343,11 +1345,25 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
// Calculate the occurrences for the given range // Calculate the occurrences for the given range
records = [NSMutableArray array]; records = [NSMutableArray array];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange ranges =
firstInstanceCalendarDateRange: firstRange [NSMutableArray arrayWithArray:
recurrenceRules: rules [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange
exceptionRules: exRules firstInstanceCalendarDateRange: firstRange
exceptionDates: exDates]; recurrenceRules: rules
exceptionRules: exRules
recurrenceDates: rDates
exceptionDates: exDates]];
// Add the master occurrence when dealing with RDATES.
// However, the master event must not be flagged with X-MOZ-FAKED-MASTER.
if ([component hasRecurrenceDates] &&
![[[component uniqueChildWithTag: @"x-moz-faked-master"]
flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[recurrenceRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
max = [ranges count]; max = [ranges count];
for (count = 0; count < max; count++) for (count = 0; count < max; count++)
{ {

View File

@ -328,6 +328,7 @@
[newOccurence autorelease]; [newOccurence autorelease];
[newOccurence removeAllRecurrenceRules]; [newOccurence removeAllRecurrenceRules];
[newOccurence removeAllExceptionRules]; [newOccurence removeAllExceptionRules];
[newOccurence removeAllRecurrenceDates];
[newOccurence removeAllExceptionDates]; [newOccurence removeAllExceptionDates];
// It is important to set the organizer as some DAV clients (iCal // It is important to set the organizer as some DAV clients (iCal

View File

@ -49,7 +49,7 @@
/* master occurrence */ /* master occurrence */
component = [components objectAtIndex: 0]; component = [components objectAtIndex: 0];
if ([component hasRecurrenceRules] || [component recurrenceId]) if ([component isRecurrent] || [component recurrenceId])
{ {
// Skip the master event if required // Skip the master event if required
count = ([component recurrenceId] ? 0 : 1); count = ([component recurrenceId] ? 0 : 1);

View File

@ -1,6 +1,6 @@
/* iCalRepeatableEntityObject+SOGo.m - this file is part of SOGo /* iCalRepeatableEntityObject+SOGo.m - this file is part of SOGo
* *
* Copyright (C) 2007-2016 Inverse inc. * Copyright (C) 2007-2017 Inverse inc.
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -30,6 +30,7 @@
#import <NGExtensions/NGCalendarDateRange.h> #import <NGExtensions/NGCalendarDateRange.h>
#import <SoObjects/SOGo/CardElement+SOGo.h>
#import <SoObjects/SOGo/SOGoUser.h> #import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoUserDefaults.h> #import <SoObjects/SOGo/SOGoUserDefaults.h>
#import <SoObjects/SOGo/WOContext+SOGo.h> #import <SoObjects/SOGo/WOContext+SOGo.h>
@ -78,7 +79,7 @@
*/ */
- (NSDictionary *) attributesInContext: (WOContext *) context - (NSDictionary *) attributesInContext: (WOContext *) context
{ {
NSArray *allComponents, *rules; NSArray *allComponents, *rules, *dates;
NSCalendarDate *untilDate; NSCalendarDate *untilDate;
NSMutableDictionary *data, *repeat; NSMutableDictionary *data, *repeat;
NSString *frequency; NSString *frequency;
@ -86,6 +87,7 @@
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
iCalEvent *masterComponent; iCalEvent *masterComponent;
iCalRecurrenceRule *rule; iCalRecurrenceRule *rule;
NSInteger i, count;
data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]]; data = [NSMutableDictionary dictionaryWithDictionary: [super attributesInContext: context]];
@ -99,10 +101,12 @@
allComponents = [[self parent] todos]; allComponents = [[self parent] todos];
masterComponent = [allComponents objectAtIndex: 0]; masterComponent = [allComponents objectAtIndex: 0];
rules = [masterComponent recurrenceRules]; rules = [masterComponent recurrenceRules];
dates = [masterComponent recurrenceDates];
} }
else else
{ {
rules = [self recurrenceRules]; rules = [self recurrenceRules];
dates = [self recurrenceDates];
} }
if ([rules count] > 0) if ([rules count] > 0)
@ -129,7 +133,6 @@
{ {
NSMutableArray *byMonthDay = [NSMutableArray arrayWithArray: [rule byMonthDay]]; NSMutableArray *byMonthDay = [NSMutableArray arrayWithArray: [rule byMonthDay]];
NSMutableArray *byDay = [NSMutableArray array]; NSMutableArray *byDay = [NSMutableArray array];
NSInteger i, count;
count = [byMonthDay count]; count = [byMonthDay count];
for (i = count - 1; i >= 0; i--) for (i = count - 1; i >= 0; i--)
{ {
@ -154,6 +157,22 @@
[data setObject: repeat forKey: @"repeat"]; [data setObject: repeat forKey: @"repeat"];
} }
if ([dates count] > 0)
{
NSMutableArray *rDates = [NSMutableArray array];
NSCalendarDate *rDate;
ud = [[context activeUser] userDefaults];
timeZone = [ud timeZone];
count = [dates count];
for (i = 0; i < count; i++)
{
rDate = [dates objectAtIndex: i];
[rDate setTimeZone: timeZone];
[rDates addObject: [rDate iso8601DateString]];
}
[data setObject: [NSDictionary dictionaryWithObject: rDates forKey: @"dates"] forKey: @"repeat"];
}
return data; return data;
} }
@ -166,8 +185,11 @@
{ {
iCalRecurrenceRule *rule; iCalRecurrenceRule *rule;
iCalRecurrenceFrequency frequency; iCalRecurrenceFrequency frequency;
NSArray *rdates;
NSCalendarDate *date; NSCalendarDate *date;
SOGoUserDefaults *ud; SOGoUserDefaults *ud;
NSInteger i;
BOOL isAllDay;
id repeat, o; id repeat, o;
[super setAttributes: data inContext: context]; [super setAttributes: data inContext: context];
@ -177,6 +199,7 @@
return; return;
repeat = [data objectForKey: @"repeat"]; repeat = [data objectForKey: @"repeat"];
isAllDay = [[data objectForKey: @"isAllDay"] boolValue];
if ([repeat isKindOfClass: [NSDictionary class]]) if ([repeat isKindOfClass: [NSDictionary class]])
{ {
rule = [iCalRecurrenceRule new]; rule = [iCalRecurrenceRule new];
@ -199,6 +222,26 @@
frequency = iCalRecurrenceFrequenceDaily; frequency = iCalRecurrenceFrequenceDaily;
[rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]]; [rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
} }
else if ([o caseInsensitiveCompare: @"CUSTOM"] == NSOrderedSame)
{
[self removeAllRecurrenceDates];
o = [repeat objectForKey: @"dates"];
if ([o isKindOfClass: [NSArray class]])
{
rdates = o;
for (i = 0; i < [rdates count]; i++)
{
o = [rdates objectAtIndex: i];
if ([o isKindOfClass: [NSDictionary class]])
{
date = [self dateFromString: [o objectForKey: @"date"] inContext: context];
if (!isAllDay)
[self adjustDate: &date withTimeString: [o objectForKey: @"time"] inContext: context];
[self addToRecurrenceDates: date];
}
}
}
}
} }
else else
{ {
@ -254,6 +297,10 @@
{ {
[self removeAllRecurrenceRules]; [self removeAllRecurrenceRules];
} }
else if ([self hasRecurrenceDates])
{
[self removeAllRecurrenceDates];
}
} }
- (NSString *) cycleInfo - (NSString *) cycleInfo
@ -275,6 +322,12 @@
if (rules) if (rules)
[cycleInfo setObject: rules forKey: @"exRules"]; [cycleInfo setObject: rules forKey: @"exRules"];
/* recurrence dates */
rules = [self recurrenceDates];
if ([rules count])
[cycleInfo setObject: rules forKey: @"rDates"];
/* exception dates */
rules = [self exceptionDates]; rules = [self exceptionDates];
if ([rules count]) if ([rules count])
[cycleInfo setObject: rules forKey: @"exDates"]; [cycleInfo setObject: rules forKey: @"exDates"];
@ -328,7 +381,7 @@
*/ */
- (BOOL) doesOccurOnDate: (NSCalendarDate *) theOccurenceDate - (BOOL) doesOccurOnDate: (NSCalendarDate *) theOccurenceDate
{ {
NSArray *ranges; NSMutableArray *ranges;
NGCalendarDateRange *checkRange, *firstRange; NGCalendarDateRange *checkRange, *firstRange;
NSCalendarDate *startDate, *endDate; NSCalendarDate *startDate, *endDate;
id firstStartDate, timeZone; id firstStartDate, timeZone;
@ -352,11 +405,25 @@
endDate: endDate]; endDate: endDate];
// Calculate the occurrences for the given date // Calculate the occurrences for the given date
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: checkRange ranges =
firstInstanceCalendarDateRange: firstRange [NSMutableArray arrayWithArray:
recurrenceRules: [self recurrenceRulesWithTimeZone: timeZone] [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: checkRange
exceptionRules: [self exceptionRulesWithTimeZone: timeZone] firstInstanceCalendarDateRange: firstRange
exceptionDates: [self exceptionDatesWithTimeZone: timeZone]]; recurrenceRules: [self recurrenceRulesWithTimeZone: timeZone]
exceptionRules: [self exceptionRulesWithTimeZone: timeZone]
recurrenceDates: [self recurrenceDatesWithTimeZone: timeZone]
exceptionDates: [self exceptionDatesWithTimeZone: timeZone]]];
// Add the master occurrence when dealing with RDATES.
// However, the master event must not be flagged with X-MOZ-FAKED-MASTER.
if ([self hasRecurrenceDates] &&
![[[self uniqueChildWithTag: @"x-moz-faked-master"]
flattenedValuesForKey: @""] isEqualToString: @"1"] &&
[checkRange doesIntersectWithDateRange: firstRange])
{
[ranges insertObject: firstRange atIndex: 0];
}
doesOccur = [ranges dateRangeArrayContainsDate: startDate]; doesOccur = [ranges dateRangeArrayContainsDate: startDate];
} }

View File

@ -571,6 +571,7 @@ vtodo_class2 = "(Confidential task)";
"More options" = "More options"; "More options" = "More options";
"Delete This Occurrence" = "Delete This Occurrence"; "Delete This Occurrence" = "Delete This Occurrence";
"Delete All Occurrences" = "Delete All Occurrences"; "Delete All Occurrences" = "Delete All Occurrences";
"Add Recurrence Date" = "Add Recurrence Date";
"Add From" = "Add From"; "Add From" = "Add From";
"Add Due" = "Add Due"; "Add Due" = "Add Due";
"Import" = "Import"; "Import" = "Import";

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.m - this file is part of SOGo /* UIxAppointmentEditor.m - this file is part of SOGo
* *
* Copyright (C) 2007-2016 Inverse inc. * Copyright (C) 2007-2017 Inverse inc.
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -650,6 +650,7 @@
* @apiSuccess (Success 200) {Number} [repeat.days.occurence] Occurrence of a specific day within the monthly or yearly rule (values are -5 to 5) * @apiSuccess (Success 200) {Number} [repeat.days.occurence] Occurrence of a specific day within the monthly or yearly rule (values are -5 to 5)
* @apiSuccess (Success 200) {Number[]} [repeat.months] List of months of the year (values are 1 to 12) * @apiSuccess (Success 200) {Number[]} [repeat.months] List of months of the year (values are 1 to 12)
* @apiSuccess (Success 200) {Number[]} [repeat.monthdays] Days of the month (values are 1 to 31) * @apiSuccess (Success 200) {Number[]} [repeat.monthdays] Days of the month (values are 1 to 31)
* @apiSuccess (Success 200) {String[]} [repeat.dates] Recurrence dates (ISO8601)
*/ */
- (id <WOActionResults>) viewAction - (id <WOActionResults>) viewAction
{ {

View File

@ -1,6 +1,6 @@
/* UIxCalMainView.m - this file is part of SOGo /* UIxCalMainView.m - this file is part of SOGo
* *
* Copyright (C) 2006-2016 Inverse inc. * Copyright (C) 2006-2017 Inverse inc.
* *
* This file is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -476,6 +476,7 @@
@"weekly", @"weekly",
@"monthly", @"monthly",
@"yearly", @"yearly",
@"custom",
nil]; nil];
[repeatItems retain]; [repeatItems retain];
} }

View File

@ -179,7 +179,10 @@
<div layout="row" layout-align="start center"> <div layout="row" layout-align="start center">
<md-input-container class="md-block md-flex"> <md-input-container class="md-block md-flex">
<label><var:string label:value="Repeat"/></label> <label><var:string label:value="Repeat"/></label>
<md-select ng-model="editor.component.repeat.frequency" ng-disabled="editor.component.occurrenceId"> <md-select
ng-model="editor.component.repeat.frequency"
ng-change="editor.changeFrequency($event)"
ng-disabled="editor.component.occurrenceId">
<var:foreach list="repeatList" item="item"> <var:foreach list="repeatList" item="item">
<md-option var:value="item"><var:string var:value="itemRepeatText"/></md-option> <md-option var:value="item"><var:string var:value="itemRepeatText"/></md-option>
</var:foreach> </var:foreach>

View File

@ -144,4 +144,27 @@
</div> </div>
</div> </div>
<!-- custom (rdates) -->
<div ng-if="editor.component.repeat.frequency == 'custom'">
<label class="button-label"><var:string label:value="On"/></label>
<div layout="row" layout-align="start end" ng-repeat="rdate in editor.component.repeat.dates">
<md-button class="md-icon-button" type="button" ng-click="editor.component.$deleteRecurrenceDate($index)">
<md-icon>remove_circle</md-icon>
</md-button>
<md-datepicker
ng-model="editor.component.repeat.dates[$index]"
label:md-placeholder="On"><!-- date picker --></md-datepicker>
<sg-timepicker
ng-model="editor.component.repeat.dates[$index]"
ng-hide="editor.component.isAllDay"><!-- time picker --></sg-timepicker>
</div>
<div layout="row" layout-align="start center">
<md-button class="md-icon-button" type="button" ng-click="editor.component.$addRecurrenceDate()">
<md-icon>add_circle</md-icon>
</md-button>
<label class="button-label">
<var:string label:value="Add Recurrence Date"/>
</label>
</div>
</div>
</container> </container>

View File

@ -222,6 +222,7 @@
vm.showAttendeesEditor = vm.component.attendees && vm.component.attendees.length; vm.showAttendeesEditor = vm.component.attendees && vm.component.attendees.length;
vm.toggleAttendeesEditor = toggleAttendeesEditor; vm.toggleAttendeesEditor = toggleAttendeesEditor;
//vm.searchText = null; //vm.searchText = null;
vm.changeFrequency = changeFrequency;
vm.changeCalendar = changeCalendar; vm.changeCalendar = changeCalendar;
vm.cardFilter = cardFilter; vm.cardFilter = cardFilter;
vm.addAttendee = addAttendee; vm.addAttendee = addAttendee;
@ -272,6 +273,11 @@
vm.component.repeat.month.type == 'bymonthday'; vm.component.repeat.month.type == 'bymonthday';
} }
function changeFrequency() {
if (vm.component.repeat.frequency == 'custom')
vm.showRecurrenceEditor = true;
}
function changeCalendar() { function changeCalendar() {
var updateRequired = (vm.component.attendees && vm.component.attendees.length > 0); var updateRequired = (vm.component.attendees && vm.component.attendees.length > 0);
if (updateRequired) if (updateRequired)