Support for repetitive alarms and tasks

This commit is contained in:
Ludovic Marcotte
2014-09-12 08:34:15 -04:00
parent 8cdaac6300
commit 12a4fd05a2
44 changed files with 921 additions and 552 deletions
+12 -6
View File
@@ -1,6 +1,6 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2007-2012 Inverse inc.
Copyright (C) 2007-2014 Inverse inc.
This file is part of SOGo.
@@ -50,6 +50,7 @@
@class GCSFolder;
@class iCalCalendar;
@class iCalTimeZone;
@class NGCalendarDateRange;
@class SOGoWebDAVValue;
typedef enum {
@@ -118,6 +119,11 @@ typedef enum {
- (NSArray *) fetchAlarmInfosFrom: (NSNumber *) _startUTCDate
to: (NSNumber *) _endUTCDate;
- (void) flattenCycleRecord: (NSDictionary *) theRecord
forRange: (NGCalendarDateRange *) theRange
intoArray: (NSMutableArray *) theRecords
withCalendar: (iCalCalendar *) calendar;
/* URL generation */
- (NSString *) baseURLForAptWithUID: (NSString *) _uid
@@ -138,11 +144,6 @@ typedef enum {
- (NSArray *) lookupCalendarFoldersForICalPerson: (NSArray *) _persons
inContext: (id) _ctx;
// - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
// inContext: (id) _ctx;
// - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
// inContext: (id) _ctx;
/* bulk fetches */
- (NSString *) aclSQLListingFilter;
@@ -186,6 +187,11 @@ typedef enum {
- (NSArray *) aclUsersWithProxyWriteAccess: (BOOL) write;
- (void) findEntityForClosestAlarm: (id *) theEntity
timezone: (NSTimeZone *) theTimeZone
startDate: (NSCalendarDate **) theStartDate
endDate: (NSCalendarDate **) theEndDate;
@end
#endif /* __Appointments_SOGoAppointmentFolder_H__ */
+345 -152
View File
@@ -40,6 +40,7 @@
#import <DOM/DOMProtocols.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalEvent.h>
#import <NGCards/iCalFreeBusy.h>
@@ -49,6 +50,7 @@
#import <NGCards/iCalRecurrenceRule.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalTimeZonePeriod.h>
#import <NGCards/iCalToDo.h>
#import <NGCards/NSString+NGCards.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <NGExtensions/NSNull+misc.h>
@@ -74,6 +76,7 @@
#import <SOGo/WORequest+SOGo.h>
#import <SOGo/WOResponse+SOGo.h>
#import "iCalCalendar+SOGo.h"
#import "iCalRepeatableEntityObject+SOGo.h"
#import "iCalEvent+SOGo.h"
#import "iCalPerson+SOGo.h"
@@ -99,7 +102,7 @@
@implementation SOGoAppointmentFolder
static NSNumber *sharedYes = nil;
static iCalEvent *iCalEventK = nil;
static Class iCalEventK = nil;
+ (void) initialize
{
@@ -769,7 +772,7 @@ static iCalEvent *iCalEventK = nil;
* @param theRecord a dictionnary with the attributes of the event.
* @return a copy of theRecord with adjusted dates.
*/
- (NSMutableDictionary *) fixupRecord: (NSDictionary *) theRecord
- (NSMutableDictionary *) _fixupRecord: (NSDictionary *) theRecord
{
NSMutableDictionary *record;
static NSString *fields[] = { @"c_startdate", @"startDate",
@@ -813,12 +816,12 @@ static iCalEvent *iCalEventK = nil;
//
//
//
- (NSArray *) fixupRecords: (NSArray *) theRecords
- (NSArray *) _fixupRecords: (NSArray *) theRecords
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
unsigned count, max;
id row; // TODO: what is the type of the record?
id row;
if (theRecords)
{
@@ -826,7 +829,7 @@ static iCalEvent *iCalEventK = nil;
ma = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
row = [self fixupRecord: [theRecords objectAtIndex: count]];
row = [self _fixupRecord: [theRecords objectAtIndex: count]];
if (row)
[ma addObject: row];
}
@@ -900,6 +903,9 @@ static iCalEvent *iCalEventK = nil;
return record;
}
//
//
//
- (int) _indexOfRecordMatchingDate: (NSCalendarDate *) matchDate
inArray: (NSArray *) recordArray
{
@@ -922,6 +928,9 @@ static iCalEvent *iCalEventK = nil;
return recordIndex;
}
//
//
//
- (void) _fixExceptionRecord: (NSMutableDictionary *) recRecord
fromRow: (NSDictionary *) row
{
@@ -938,12 +947,57 @@ static iCalEvent *iCalEventK = nil;
[recRecord setObjects: objects forKeys: fields];
}
//
//
//
- (void) _computeAlarmForRow: (NSMutableDictionary *) row
master: (iCalEntityObject *) master
{
iCalEntityObject *component;
iCalAlarm *alarm;
if (![master recurrenceId])
{
component = [master copy];
[component setStartDate: [NSCalendarDate dateWithTimeIntervalSince1970: [[row objectForKey: @"c_startdate"] intValue]]];
if ([component isKindOfClass: [iCalEvent class]])
{
[(iCalEvent *)component setEndDate: [NSCalendarDate dateWithTimeIntervalSince1970: [[row objectForKey: @"c_enddate"] intValue]]];
}
else
{
[(iCalToDo *)component setDue: [NSCalendarDate dateWithTimeIntervalSince1970: [[row objectForKey: @"c_enddate"] intValue]]];
}
}
else
{
component = master;
RETAIN(component);
}
// Check if we have any alarm, that could happen for recurrence exceptions with no
// alarm defined.
if ([[component alarms] count])
{
alarm = [[component alarms] objectAtIndex: 0];
[row setObject: [NSNumber numberWithInt: [[alarm nextAlarmDate] timeIntervalSince1970]]
forKey: @"c_nextalarm"];
}
RELEASE(component);
}
//
//
//
- (void) _appendCycleException: (iCalRepeatableEntityObject *) component
firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
fromRow: (NSDictionary *) row
forRange: (NGCalendarDateRange *) dateRange
withTimeZone: (NSTimeZone *) tz
toArray: (NSMutableArray *) ma
withTimeZone: (NSTimeZone *) tz
toArray: (NSMutableArray *) ma
{
NSCalendarDate *recurrenceId;
NSMutableDictionary *newRecord;
@@ -983,7 +1037,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
{
if ([dateRange containsDate: [component startDate]])
{
newRecord = [self fixupRecord: [component quickRecordForContainer: self]];
// We must pass nill to :container here in order to avoid re-entrancy issues.
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
}
else
@@ -998,8 +1053,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
else
{
// The recurrence id of the exception is outside the date range;
// simply add the exception to the records array
newRecord = [self fixupRecord: [component quickRecordForContainer: self]];
// simply add the exception to the records array.
// We must pass nill to :container here in order to avoid re-entrancy issues.
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
newRecordRange = [NGCalendarDateRange
calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"]
endDate: [newRecord objectForKey: @"endDate"]];
@@ -1024,6 +1080,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
[self _fixExceptionRecord: newRecord fromRow: row];
}
// We finally adjust the c_nextalarm
[self _computeAlarmForRow: (id)row
master: component];
}
//
@@ -1033,30 +1093,32 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
forRange: (NGCalendarDateRange *) dateRange
withTimeZone: (NSTimeZone *) tz
withCalendar: (iCalCalendar *) calendar
toArray: (NSMutableArray *) ma
{
NSArray *elements, *components;
unsigned int count, max;
NSArray *components;
NSString *content;
unsigned int count, max;
content = [row objectForKey: @"c_content"];
if ([content length])
if (!calendar && [content isNotNull])
{
// TODO : c_content could have already been parsed.
// @see _flattenCycleRecord:forRange:intoArray:
elements = [iCalCalendar parseFromSource: content];
if ([elements count])
{
components = [[elements objectAtIndex: 0] allObjects];
max = [components count];
for (count = 1; count < max; count++) // skip master event
[self _appendCycleException: [components objectAtIndex: count]
firstInstanceCalendarDateRange: fir
fromRow: row
forRange: dateRange
withTimeZone: tz
toArray: ma];
}
calendar = [iCalCalendar parseSingleFromSource: content];
}
if (calendar)
{
components = [calendar allObjects];
max = [components count];
for (count = 1; count < max; count++) // skip master event
[self _appendCycleException: [components objectAtIndex: count]
firstInstanceCalendarDateRange: fir
fromRow: row
forRange: dateRange
withTimeZone: tz
toArray: ma];
}
}
@@ -1068,21 +1130,23 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
* @param theRecords the array into which are copied the resulting occurrences.
* @see [iCalRepeatableEntityObject+SOGo doesOccurOnDate:]
*/
- (void) _flattenCycleRecord: (NSDictionary *) theRecord
forRange: (NGCalendarDateRange *) theRange
intoArray: (NSMutableArray *) theRecords
- (void) flattenCycleRecord: (NSDictionary *) theRecord
forRange: (NGCalendarDateRange *) theRange
intoArray: (NSMutableArray *) theRecords
withCalendar: (iCalCalendar *) calendar
{
NSMutableDictionary *row, *fixedRow;
NSMutableArray *records;
NSDictionary *cycleinfo;
NGCalendarDateRange *firstRange, *recurrenceRange, *oneRange;
NSArray *rules, *exRules, *exDates, *ranges;
NSArray *elements, *components;
NSArray *components;
NSString *content;
NSCalendarDate *checkStartDate, *checkEndDate, *firstStartDate, *firstEndDate;
NSTimeZone *allDayTimeZone;
iCalDateTime *dtstart;
iCalEvent *component;
iCalRepeatableEntityObject *component;
iCalTimeZone *eventTimeZone;
unsigned count, max, offset;
id tz;
@@ -1109,104 +1173,122 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
allDayTimeZone = nil;
tz = nil;
row = [self fixupRecord: theRecord];
row = [self _fixupRecord: theRecord];
[row removeObjectForKey: @"c_cycleinfo"];
[row setObject: sharedYes forKey: @"isRecurrentEvent"];
content = [theRecord objectForKey: @"c_content"];
if ([content isNotNull])
if (!calendar && [content isNotNull])
{
elements = [iCalCalendar parseFromSource: content];
if ([elements count])
{
components = [[elements objectAtIndex: 0] events];
if ([components count])
{
// Retrieve the range of the first/master event
component = [components objectAtIndex: 0];
dtstart = (iCalDateTime *) [component uniqueChildWithTag: @"dtstart"];
firstRange = [component firstOccurenceRange]; // ignores timezone
calendar = [iCalCalendar parseSingleFromSource: content];
}
eventTimeZone = [dtstart timeZone];
if (eventTimeZone)
{
// Adjust the range to check with respect to the event timezone (extracted from the start date)
checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]];
checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]];
recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate
endDate: checkEndDate];
}
else
{
recurrenceRange = theRange;
if ([[theRecord objectForKey: @"c_isallday"] boolValue])
{
// The event lasts all-day and has no timezone (floating); we convert the range of the first event
// to the user's timezone
allDayTimeZone = timeZone;
offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]];
firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
[firstStartDate setTimeZone: allDayTimeZone];
[firstEndDate setTimeZone: allDayTimeZone];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate
endDate: firstEndDate];
}
}
#warning this code is ugly: we should not mix objects with different types as\
it reduces readability
tz = eventTimeZone ? eventTimeZone : allDayTimeZone;
if (tz)
{
// Adjust the exception dates
exDates = [component exceptionDatesWithTimeZone: tz];
// Adjust the recurrence rules "until" dates
rules = [component recurrenceRulesWithTimeZone: tz];
exRules = [component exceptionRulesWithTimeZone: tz];
}
// Calculate the occurrences for the given range
records = [NSMutableArray array];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange
firstInstanceCalendarDateRange: firstRange
recurrenceRules: rules
exceptionRules: exRules
exceptionDates: exDates];
max = [ranges count];
for (count = 0; count < max; count++)
{
oneRange = [ranges objectAtIndex: count];
fixedRow = [self fixupCycleRecord: row
cycleRange: oneRange
firstInstanceCalendarDateRange: firstRange
withEventTimeZone: eventTimeZone];
if (fixedRow)
[records addObject: fixedRow];
}
if (calendar)
{
if ([[theRecord objectForKey: @"c_component"] isEqualToString: @"vtodo"])
components = [calendar todos];
else
components = [calendar events];
if ([components count])
{
// Retrieve the range of the first/master event
component = [components objectAtIndex: 0];
dtstart = (iCalDateTime *) [component uniqueChildWithTag: @"dtstart"];
firstRange = [component firstOccurenceRange]; // ignores timezone
eventTimeZone = [dtstart timeZone];
if (eventTimeZone)
{
// Adjust the range to check with respect to the event timezone (extracted from the start date)
checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]];
checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]];
recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate
endDate: checkEndDate];
[self _appendCycleExceptionsFromRow: row
firstInstanceCalendarDateRange: firstRange
forRange: theRange
withTimeZone: allDayTimeZone
toArray: records];
[theRecords addObjectsFromArray: records];
}
}
else
{
recurrenceRange = theRange;
if ([[theRecord objectForKey: @"c_isallday"] boolValue])
{
// The event lasts all-day and has no timezone (floating); we convert the range of the first event
// to the user's timezone
allDayTimeZone = timeZone;
offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]];
firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0
seconds:-offset];
[firstStartDate setTimeZone: allDayTimeZone];
[firstEndDate setTimeZone: allDayTimeZone];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate
endDate: firstEndDate];
}
}
#warning this code is ugly: we should not mix objects with different types as \
it reduces readability
tz = eventTimeZone ? eventTimeZone : allDayTimeZone;
if (tz)
{
// Adjust the exception dates
exDates = [component exceptionDatesWithTimeZone: tz];
// Adjust the recurrence rules "until" dates
rules = [component recurrenceRulesWithTimeZone: tz];
exRules = [component exceptionRulesWithTimeZone: tz];
}
// Calculate the occurrences for the given range
records = [NSMutableArray array];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange
firstInstanceCalendarDateRange: firstRange
recurrenceRules: rules
exceptionRules: exRules
exceptionDates: exDates];
max = [ranges count];
for (count = 0; count < max; count++)
{
oneRange = [ranges objectAtIndex: count];
fixedRow = [self fixupCycleRecord: row
cycleRange: oneRange
firstInstanceCalendarDateRange: firstRange
withEventTimeZone: eventTimeZone];
// We now adjust the c_nextalarm based on each occurences. For each of them, we use the master event
// alarm information since exceptions to recurrence rules might have their own, while that is not the
// case for standard occurences.
if ([component hasAlarms])
{
[self _computeAlarmForRow: fixedRow
master: component];
}
[records addObject: fixedRow];
}
[self _appendCycleExceptionsFromRow: row
firstInstanceCalendarDateRange: firstRange
forRange: theRange
withTimeZone: allDayTimeZone
withCalendar: calendar
toArray: records];
[theRecords addObjectsFromArray: records];
} // if ([components count]) ...
}
else
[self errorWithFormat:@"cyclic record doesn't have content -> %@", theRecord];
}
//
// TODO: is the result supposed to be sorted by date?
//
- (NSArray *) _flattenCycleRecords: (NSArray *) _records
fetchRange: (NGCalendarDateRange *) _r
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
NSDictionary *row;
NSCalendarDate *rangeEndDate;
@@ -1224,12 +1306,15 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
for (count = 0; count < max; count++)
{
row = [_records objectAtIndex: count];
[self _flattenCycleRecord: row forRange: _r intoArray: ma];
[self flattenCycleRecord: row forRange: _r intoArray: ma withCalendar: nil];
}
return ma;
}
//
//
//
- (void) _buildStripFieldsFromFields: (NSArray *) fields
{
stripFields = [[NSMutableArray alloc] initWithCapacity: [fields count]];
@@ -1246,6 +1331,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
@"c_component", nil]];
}
//
//
//
- (void) _fixupProtectedInformation: (NSEnumerator *) ma
inFields: (NSArray *) fields
forUser: (NSString *) uid
@@ -1299,7 +1387,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
BOOL rememberRecords, canCycle;
rememberRecords = [self _checkIfWeCanRememberRecords: _fields];
canCycle = [_component isEqualToString: @"vevent"];
canCycle = [_component isEqualToString: @"vevent"] || [_component isEqualToString: @"vtodo"];
// if (rememberRecords)
// NSLog (@"we will remember those records!");
@@ -1341,28 +1429,31 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
}
privacySQLString = [self aclSQLListingFilter];
if (privacySQLString)
{
if ([privacySQLString length])
[baseWhere addObject: privacySQLString];
if ([title length])
if ([filters length])
{
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
{
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
}
if ([filters length])
{
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
{
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
}
}
else
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
}
else
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
/* prepare mandatory fields */
fields = [NSMutableArray arrayWithArray: _fields];
[fields addObjectUniquely: @"c_name"];
[fields addObjectUniquely: @"c_uid"];
@@ -1386,7 +1477,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
if (records)
{
if (r)
records = [self fixupRecords: records];
records = [self _fixupRecords: records];
ma = [NSMutableArray arrayWithArray: records];
}
else
@@ -1971,26 +2062,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
return [name isEqualToString: @"OPTIONS"];
}
/*
- (id) lookupComponentByUID: (NSString *) uid
{
NSString *filename;
id component;
filename = [self resourceNameForEventUID: uid];
if (filename)
{
component = [self lookupName: filename inContext: context acquire: NO];
if ([component isKindOfClass: [NSException class]])
component = nil;
}
else
component = nil;
return nil;
}
*/
- (id) lookupName: (NSString *)_key
inContext: (id)_ctx
acquire: (BOOL)_flag
@@ -3356,5 +3427,127 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
return activeTasks;
}
- (void) findEntityForClosestAlarm: (id *) theEntity
timezone: (NSTimeZone *) theTimeZone
startDate: (NSCalendarDate **) theStartDate
endDate: (NSCalendarDate **) theEndDate
{
// If the event is recurring, we MUST find the right occurence.
if ([*theEntity hasRecurrenceRules])
{
NSCalendarDate *startDate, *endDate;
NSMutableDictionary *quickRecord;
NSCalendarDate *start, *end;
NGCalendarDateRange *range;
NSMutableArray *alarms;
iCalDateTime *date;
iCalTimeZone *tz;
BOOL b, isEvent;
isEvent = [*theEntity isKindOfClass: [iCalEvent class]];
b = NO;
if (isEvent)
b = [*theEntity isAllDay];
// We build a fake "quick record". Our record must include some mandatory info, like @"c_startdate" and @"c_enddate"
quickRecord = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: b], @"c_isallday",
[NSNumber numberWithBool: [*theEntity isRecurrent]], @"c_iscycle",
nil];
startDate = [*theEntity startDate];
endDate = (isEvent ? [*theEntity endDate] : [*theEntity due]);
if ([startDate isNotNull])
{
if (b)
{
// An all-day event usually doesn't have a timezone associated to its
// start date; however, if it does, we convert it to GMT.
date = (iCalDateTime*) [*theEntity uniqueChildWithTag: @"dtstart"];
tz = [(iCalDateTime*) date timeZone];
if (tz)
startDate = [tz computedDateForDate: startDate];
}
[quickRecord setObject: [*theEntity quickRecordDateAsNumber: startDate
withOffset: 0
forAllDay: b]
forKey: @"c_startdate"];
}
if ([endDate isNotNull])
{
if (b)
{
// An all-day event usually doesn't have a timezone associated to its
// end date; however, if it does, we convert it to GMT.
date = (isEvent ? (iCalDateTime*) [*theEntity uniqueChildWithTag: @"dtend"] : (iCalDateTime*) [*theEntity uniqueChildWithTag: @"due"]);
tz = [(iCalDateTime*) date timeZone];
if (tz)
endDate = [tz computedDateForDate: endDate];
}
[quickRecord setObject: [*theEntity quickRecordDateAsNumber: endDate
withOffset: ((b) ? -1 : 0)
forAllDay: b]
forKey: @"c_enddate"];
}
if ([*theEntity isRecurrent])
{
NSCalendarDate *date;
date = [*theEntity lastPossibleRecurrenceStartDate];
if (!date)
{
/* this could also be *nil*, but in the end it makes the fetchspecs
more complex - thus we set it to a "reasonable" distant future */
date = iCalDistantFuture;
}
[quickRecord setObject: [*theEntity quickRecordDateAsNumber: date
withOffset: 0 forAllDay: NO]
forKey: @"c_cycleenddate"];
[quickRecord setObject: [*theEntity cycleInfo] forKey: @"c_cycleinfo"];
}
alarms = [NSMutableArray array];
start = [NSCalendarDate date];
end = [start addYear:1 month:0 day:0 hour:0 minute:0 second:0];
range = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
[self flattenCycleRecord: quickRecord
forRange: range
intoArray: alarms
withCalendar: [*theEntity parent]];
if ([alarms count])
{
NSDictionary *anAlarm;
id o;
// Take the first alarm since it's the 'closest' one
anAlarm = [alarms objectAtIndex: 0];
// We grab the last one and we use that info. The logic is simple.
// 1. grab the RECURRENCE-ID, if found in our master event, use that
// 2. if not found, use the master's event info but adjust the start/end date
if ((o = [[*theEntity parent] eventWithRecurrenceID: [anAlarm objectForKey: @"c_recurrence_id"]]))
{
*theEntity = o;
*theStartDate = [*theEntity startDate];
*theEndDate = [*theEntity endDate];
}
else
{
*theStartDate = [NSCalendarDate dateWithTimeIntervalSince1970: [[anAlarm objectForKey: @"c_startdate"] intValue]];
*theEndDate = [NSCalendarDate dateWithTimeIntervalSince1970: [[anAlarm objectForKey: @"c_enddate"] intValue]];
}
[*theStartDate setTimeZone: theTimeZone];
[*theEndDate setTimeZone: theTimeZone];
}
} // if ([event hasRecurrenceRules]) ...
}
@end /* SOGoAppointmentFolder */
@@ -905,7 +905,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
updatedAttendees: nil
operation: EventCreated];
}
else
else
{
BOOL hasOrganizer;
@@ -1,8 +1,6 @@
/* SOGoAppointmentOccurence.h - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-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
@@ -1,8 +1,6 @@
/* SOGoAppointmentOccurence.m - this file is part of SOGo
*
* Copyright (C) 2008-2012 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-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
@@ -1,8 +1,6 @@
/* SOGoComponentOccurence.h - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-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
@@ -56,6 +54,7 @@
inContainer: (SOGoCalendarComponent *) newContainer;
- (void) setComponent: (iCalRepeatableEntityObject *) newComponent;
- (iCalRepeatableEntityObject *) masterComponent;
- (void) setMasterComponent: (iCalRepeatableEntityObject *) newMaster;
@end
@@ -127,6 +127,12 @@
ASSIGN (parentCalendar, [component parent]);
}
- (iCalRepeatableEntityObject *) masterComponent
{
return master;
}
- (void) setMasterComponent: (iCalRepeatableEntityObject *) newMaster
{
master = newMaster;
@@ -194,7 +200,7 @@
[master increaseSequence];
// We save the updated iCalendar in the database.
error = [container saveComponent: calendar];
error = [container saveCalendar: calendar];
}
return error;
@@ -1,8 +1,6 @@
/* SOGoEMailAlarmsManager.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-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
@@ -1,8 +1,6 @@
/* SOGoEMailAlarmsManager.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-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
+33
View File
@@ -20,6 +20,8 @@
02111-1307, USA.
*/
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSException.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -81,4 +83,35 @@
return ex;
}
- (iCalRepeatableEntityObject *) newOccurenceWithID: (NSString *) theRecurrenceID
{
iCalToDo *newOccurence, *master;
NSCalendarDate *date, *firstDate;
NSTimeInterval interval;
newOccurence = (iCalToDo *) [super newOccurenceWithID: theRecurrenceID];
date = [newOccurence recurrenceId];
master = [self component: NO secure: NO];
firstDate = [master startDate];
interval = [[master due]
timeIntervalSinceDate: (NSDate *)firstDate];
[newOccurence setStartDate: date];
[newOccurence setDue: [date addYear: 0
month: 0
day: 0
hour: 0
minute: 0
second: interval]];
return newOccurence;
}
- (void) prepareDeleteOccurence: (iCalToDo *) occurence
{
}
@end /* SOGoTaskObject */
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoTaskOccurence.h - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-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
+1 -3
View File
@@ -1,8 +1,6 @@
/* SOGoTaskOccurence.m - this file is part of SOGo
*
* Copyright (C) 2008 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2008-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
+5 -3
View File
@@ -25,6 +25,7 @@
#import <NGCards/iCalRepeatableEntityObject.h>
#import "iCalCalendar+SOGo.h"
#import "iCalEntityObject+SOGo.h"
@implementation iCalCalendar (SOGoExtensions)
@@ -78,7 +79,8 @@
return [self _occurrence: recID inArray: [self todos]];
}
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
CardGroup *element;
NSArray *elements;
@@ -91,11 +93,11 @@
element = [elements objectAtIndex: 0];
else
{
[self logWithFormat: @"ERROR: given calendar contains no elements: %@", self];
NSLog(@"ERROR: given calendar contains no elements: %@", self);
element = nil;
}
return [element quickRecordForContainer: theContainer];
return [(id)element quickRecordFromContent: theContent container: theContainer];
}
@end
@@ -23,6 +23,7 @@
#import <NGCards/iCalEntityObject.h>
@class NSMutableDictionary;
@class SOGoUser;
extern NSCalendarDate *iCalDistantFuture;
@@ -43,7 +44,6 @@ extern NSNumber *iCalDistantFutureNumber;
- (id) itipEntryWithMethod: (NSString *) method;
- (NSArray *) attendeesWithoutUser: (SOGoUser *) user;
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer;
- (int) priorityNumber;
- (NSString *) createdBy;
@@ -52,6 +52,12 @@ extern NSNumber *iCalDistantFutureNumber;
withOffset: (int) offset
forAllDay: (BOOL) allDay;
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer;
- (void) updateNextAlarmDateInRow: (NSMutableDictionary *) row
forContainer: (id) theContainer;
@end
#endif /* ICALENTITYOBJECT_SOGO_H */
+145 -1
View File
@@ -25,9 +25,16 @@
#import <Foundation/NSValue.h>
#import <Foundation/NSTimeZone.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalRepeatableEntityObject.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <NGExtensions/NSNull+misc.h>
#import "SOGoAppointmentFolder.h"
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext+SoObjects.h>
@@ -38,6 +45,7 @@
#import "iCalPerson+SOGo.h"
#import "iCalCalendar+SOGo.h"
#import "iCalEntityObject+SOGo.h"
NSCalendarDate *iCalDistantFuture = nil;
@@ -213,7 +221,8 @@ NSNumber *iCalDistantFutureNumber = nil;
return dateNumber;
}
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
[self subclassResponsibility: _cmd];
@@ -255,4 +264,139 @@ NSNumber *iCalDistantFutureNumber = nil;
return created_by;
}
- (void) updateNextAlarmDateInRow: (NSMutableDictionary *) row
forContainer: (id) theContainer
{
NSCalendarDate *nextAlarmDate;
nextAlarmDate = nil;
if ([self hasAlarms])
{
// We currently have the following limitations for alarms:
// - only the first alarm is considered;
// - the alarm's action must be of type DISPLAY;
//
// Morever, we don't update the quick table if the property X-WebStatus
// of the trigger is set to "triggered".
iCalAlarm *anAlarm;
NSString *webstatus;
if (![(id)self isRecurrent])
{
anAlarm = [[self alarms] objectAtIndex: 0];
if ([[anAlarm action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
{
webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"];
if (!webstatus
|| ([webstatus caseInsensitiveCompare: @"TRIGGERED"]
!= NSOrderedSame))
nextAlarmDate = [anAlarm nextAlarmDate];
}
else
{
// TODO: handle email alarms here
}
}
// Recurring event/task
else
{
NSCalendarDate *start, *end;
NGCalendarDateRange *range;
NSMutableArray *alarms;
alarms = [NSMutableArray array];
start = [NSCalendarDate date];
end = [start addYear:1 month:0 day:0 hour:0 minute:0 second:0];
range = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
// Always check if container is defined. If not, that means this method
// call was reentrant.
if (theContainer)
{
NSTimeInterval now;
int i, v, delta, c_startdate, c_nextalarm;
//
// Here is the logic:
//
// We flatten the structure. When flattening it (or after), we compute the alarm based on the trigger for every single
// event part of the recurrence rule. Exceptions can have their own triggers. Then, we start from NOW and move forward,
// and we look at the closest one. When found one, we pick it and store it in c_nextalarm.
//
// When popping up the alarm, we must find for which occurence it is - so we can show the proper start/end time, or even
// infos from the exception. It gets tricky because the user could have snoozed the alarm. So here is the logic:
//
// We flatten the structure and compute the alarms based on triggers. If c_nextalarm is a match, we have have a winner.
// If we don't have a match, we pick the event for which its trigger is the closest to the c_nextalarm.
//
nextAlarmDate = nil;
[theContainer flattenCycleRecord: (id)row
forRange: range
intoArray: alarms
withCalendar: [self parent]];
// We pickup the closest one from now. We remove the actual reminder (ie., 15 mins before - plus 1 minute for roundups)
// so we don't pickup the same alarm over and over. This could happen if our alarm popups and the user clicks on "Cancel".
// In case of a repetitive event, we want to pickup the next one (next day for example), and not the one that has just
// popped up.
now = [start timeIntervalSince1970];
v = 0;
for (i = 0; i < [alarms count]; i++)
{
c_startdate = [[[alarms objectAtIndex: i] objectForKey: @"c_startdate"] intValue];
c_nextalarm = [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue];
delta = (c_startdate - now - (c_startdate - c_nextalarm)) - 60;
// If value is not initialized, we grab it right away
if (!v && delta > 0)
v = delta;
// If we found a smaller delta than before, use it.
if (v > 0 && delta > 0 && delta <= v)
{
id o;
// Find the relevant component
if ([self respondsToSelector: @selector(endDate)])
o = [[self parent] eventWithRecurrenceID: [[alarms objectAtIndex: i] objectForKey: @"c_recurrence_id"]];
else
o = [[self parent] todoWithRecurrenceID: [[alarms objectAtIndex: i] objectForKey: @"c_recurrence_id"]];
if (!o)
o = self;
if ([[o alarms] count])
{
anAlarm = [[o alarms] objectAtIndex: 0];
if ([[anAlarm action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
{
webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"];
if (!webstatus
|| ([webstatus caseInsensitiveCompare: @"TRIGGERED"]
!= NSOrderedSame))
v = delta;
nextAlarmDate = [NSDate dateWithTimeIntervalSince1970: [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue]];
}
else
{
// TODO: handle email alarms here
}
}
}
} // for ( ... )
} // if (theContainer)
}
}
if ([nextAlarmDate isNotNull])
[row setObject: [NSNumber numberWithInt: [nextAlarmDate timeIntervalSince1970]]
forKey: @"c_nextalarm"];
else
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
}
@end
-1
View File
@@ -29,7 +29,6 @@
- (BOOL) isStillRelevant;
- (unsigned int) occurenceInterval;
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer;
- (void) updateRecurrenceRulesUntilDate: (NSCalendarDate *) previousEndDate;
@end
+11 -60
View File
@@ -28,7 +28,6 @@
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalAlarm.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalTimeZone.h>
#import <NGCards/iCalEvent.h>
@@ -37,6 +36,7 @@
#import <NGCards/iCalTrigger.h>
#import <NGCards/NSString+NGCards.h>
#import "SOGoAppointmentFolder.h"
#import "iCalRepeatableEntityObject+SOGo.h"
#import "iCalEvent+SOGo.h"
@@ -64,10 +64,11 @@
//
//
//
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
NSMutableDictionary *row;
NSCalendarDate *startDate, *endDate, *nextAlarmDate;
NSCalendarDate *startDate, *endDate;
NSArray *attendees, *categories;
NSString *uid, *title, *location, *status;
NSNumber *sequence;
@@ -167,6 +168,7 @@
forAllDay: isAllDay]
forKey: @"c_enddate"];
}
if ([self isRecurrent])
{
NSCalendarDate *date;
@@ -202,11 +204,11 @@
else
{
/* confirmed by default */
[row setObject: [NSNumber numberWithInt:1] forKey: @"c_status"];
[row setObject: [NSNumber numberWithInt: 1] forKey: @"c_status"];
}
[row setObject: [NSNumber numberWithUnsignedInt: accessClass]
forKey: @"c_classification"];
forKey: @"c_classification"];
organizer = [self organizer];
if (organizer)
@@ -235,35 +237,10 @@
[row setObject:partstates forKey: @"c_partstates"];
[partstates release];
nextAlarmDate = nil;
if (![self isRecurrent] && [self hasAlarms])
{
// We currently have the following limitations for alarms:
// - the component must not be recurrent;
// - only the first alarm is considered;
// - the alarm's action must be of type DISPLAY;
//
// Morever, we don't update the quick table if the property X-WebStatus
// of the trigger is set to "triggered".
iCalAlarm *anAlarm;
NSString *webstatus;
anAlarm = [[self alarms] objectAtIndex: 0];
if ([[anAlarm action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
{
webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"];
if (!webstatus
|| ([webstatus caseInsensitiveCompare: @"TRIGGERED"]
!= NSOrderedSame))
nextAlarmDate = [anAlarm nextAlarmDate];
}
}
if ([nextAlarmDate isNotNull])
[row setObject: [NSNumber numberWithInt: [nextAlarmDate timeIntervalSince1970]]
forKey: @"c_nextalarm"];
else
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
/* handle alarms */
[self updateNextAlarmDateInRow: row forContainer: theContainer];
/* handle categories */
categories = [self categories];
if ([categories count] > 0)
[row setObject: [categories componentsJoinedByString: @","]
@@ -274,33 +251,7 @@
return row;
}
/**
* Extract the start and end dates from the event, from which all recurrence
* calculations will be based on.
* @return the range of the first occurrence.
*/
- (NGCalendarDateRange *) firstOccurenceRange
{
NSCalendarDate *start, *end;
NGCalendarDateRange *firstRange;
NSArray *dates;
firstRange = nil;
dates = [[[self uniqueChildWithTag: @"dtstart"] valuesForKey: @""] lastObject];
if ([dates count] > 0)
{
start = [[dates lastObject] asCalendarDate]; // ignores timezone
end = [start addTimeInterval: [self occurenceInterval]];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
}
return firstRange;
}
- (unsigned int) occurenceInterval
- (NSTimeInterval) occurenceInterval
{
return [[self endDate] timeIntervalSinceDate: [self startDate]];
}
@@ -28,6 +28,7 @@
- (NSString *) cycleInfo;
- (NGCalendarDateRange *) firstOccurenceRange;
- (NSTimeInterval) occurenceInterval;
- (BOOL) doesOccurOnDate: (NSCalendarDate *) occurenceDate;
@end
@@ -23,6 +23,7 @@
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalEvent.h>
@@ -31,6 +32,7 @@
#import <NGCards/iCalTimeZone.h>
#import <NGCards/NSString+NGCards.h>
#import <NGCards/NSDictionary+NGCards.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import "iCalRepeatableEntityObject+SOGo.h"
@@ -98,14 +100,33 @@
return value;
}
/**
* Extract the start and end dates from the event, from which all recurrence
* calculations will be based on.
* @return the range of the first occurrence.
*/
- (NGCalendarDateRange *) firstOccurenceRange
{
[self subclassResponsibility: _cmd];
NSCalendarDate *start, *end;
NGCalendarDateRange *firstRange;
NSArray *dates;
return nil;
firstRange = nil;
dates = [[[self uniqueChildWithTag: @"dtstart"] valuesForKey: @""] lastObject];
if ([dates count] > 0)
{
start = [[dates lastObject] asCalendarDate]; // ignores timezone
end = [start addTimeInterval: [self occurenceInterval]];
firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end];
}
return firstRange;
}
- (unsigned int) occurenceInterval
- (NSTimeInterval) occurenceInterval
{
[self subclassResponsibility: _cmd];
@@ -123,9 +144,8 @@
NSArray *ranges;
NGCalendarDateRange *checkRange, *firstRange;
NSCalendarDate *startDate, *endDate;
id firstStartDate, firstEndDate, timeZone;
id firstStartDate, timeZone;
BOOL doesOccur;
int offset;
doesOccur = [self isRecurrent];
if (doesOccur)
-2
View File
@@ -27,8 +27,6 @@
@interface iCalToDo (SOGoExtensions)
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer;
@end
#endif /* ICALTODO_SOGO_H */
+6 -39
View File
@@ -38,10 +38,11 @@
@implementation iCalToDo (SOGoExtensions)
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
NSMutableDictionary *row;
NSCalendarDate *startDate, *dueDate, *nextAlarmDate;
NSCalendarDate *startDate, *dueDate;
NSArray *attendees, *categories;
NSString *uid, *title, *location, *status;
NSNumber *sequence;
@@ -55,7 +56,6 @@
startDate = [self startDate];
dueDate = [self due];
nextAlarmDate = nil;
uid = [self uid];
title = [self summary];
if (![title isNotNull])
@@ -168,35 +168,8 @@
[row setObject:partstates forKey: @"c_partstates"];
[partstates release];
nextAlarmDate = nil;
if (![self isRecurrent] && [self hasAlarms])
{
// We currently have the following limitations for alarms:
// - the component must not be recurrent;
// - only the first alarm is considered;
// - the alarm's action must be of type DISPLAY;
//
// Morever, we don't update the quick table if the property X-WebStatus
// of the trigger is set to "triggered".
iCalAlarm *anAlarm;
NSString *webstatus;
anAlarm = [[self alarms] objectAtIndex: 0];
if ([[anAlarm action] caseInsensitiveCompare: @"DISPLAY"]
== NSOrderedSame)
{
webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"];
if (!webstatus
|| ([webstatus caseInsensitiveCompare: @"TRIGGERED"]
!= NSOrderedSame))
nextAlarmDate = [anAlarm nextAlarmDate];
}
}
if ([nextAlarmDate isNotNull])
[row setObject: [NSNumber numberWithInt: [nextAlarmDate timeIntervalSince1970]]
forKey: @"c_nextalarm"];
else
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
/* handle alarms */
[self updateNextAlarmDateInRow: row forContainer: theContainer];
categories = [self categories];
if ([categories count] > 0)
@@ -206,13 +179,7 @@
return row;
}
- (NGCalendarDateRange *) firstOccurenceRange
{
return [NGCalendarDateRange calendarDateRangeWithStartDate: [self startDate]
endDate: [self due]];
}
- (unsigned int) occurenceInterval
- (NSTimeInterval) occurenceInterval
{
return [[self due] timeIntervalSinceDate: [self startDate]];
}
+2 -1
View File
@@ -791,7 +791,8 @@ convention:
return date;
}
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
NSMutableDictionary *fields;
CardElement *element;
+2 -1
View File
@@ -69,7 +69,8 @@
return rc;
}
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
container: (id) theContainer
{
NSMutableDictionary *fields;
NSString *value;