mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-04-20 12:29:29 +00:00
Support for repetitive alarms and tasks
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
/* GCSAlarmsFolder.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
|
||||
|
||||
@@ -944,7 +944,7 @@ andAttribute: (EOAttribute *)_attribute
|
||||
|| *_baseVersion == [storedVersion unsignedIntValue])
|
||||
{
|
||||
/* extract quick info */
|
||||
quickRow = [theComponent performSelector: @selector(quickRecordForContainer:) withObject: theContainer];
|
||||
quickRow = [theComponent performSelector: @selector(quickRecordFromContent:container:) withObject: _content withObject: theContainer];
|
||||
if (quickRow)
|
||||
{
|
||||
[quickRow setObject:_name forKey:@"c_name"];
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/* CardElement.h - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-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
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* This file 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 General Public License for more details.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* CardElement.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2012 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-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 @@
|
||||
/* iCalDateTime.m - this file is part of SOPE
|
||||
*
|
||||
* Copyright (C) 2006-2011 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-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
|
||||
@@ -92,8 +90,8 @@
|
||||
calendar
|
||||
= (iCalCalendar *) [self searchParentOfClass: [iCalCalendar class]];
|
||||
timeZone = [calendar timeZoneWithId: tzId];
|
||||
if (!timeZone)
|
||||
[self logWithFormat: @"timezone '%@' not found in calendar", tzId];
|
||||
//if (!timeZone)
|
||||
//[self logWithFormat: @"timezone '%@' not found in calendar", tzId];
|
||||
}
|
||||
|
||||
return timeZone;
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
- (BOOL) isWithinCalendarDateRange: (NGCalendarDateRange *) _range;
|
||||
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *)_r;
|
||||
|
||||
- (NSCalendarDate *) lastPossibleRecurrenceStartDate;
|
||||
|
||||
/* calculating changes */
|
||||
|
||||
- (iCalEventChanges *) getChangesRelativeToEvent: (iCalEvent *) _event;
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
|
||||
- (NSCalendarDate *) firstRecurrenceStartDateWithEndDate: (NSCalendarDate *) endDate;
|
||||
|
||||
- (NSCalendarDate *) lastPossibleRecurrenceStartDate;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __NGCards_iCalRepeatableEntityObject_H_ */
|
||||
|
||||
@@ -417,4 +417,11 @@ lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: (NGCalendarD
|
||||
return firstOccurrenceStartDate;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) lastPossibleRecurrenceStartDate
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#import "iCalDateTime.h"
|
||||
#import "iCalToDo.h"
|
||||
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
|
||||
@implementation iCalToDo
|
||||
|
||||
- (Class) classForTag: (NSString *) classTag
|
||||
@@ -119,4 +121,17 @@
|
||||
// return ms;
|
||||
// }
|
||||
|
||||
- (NSCalendarDate *) lastPossibleRecurrenceStartDate
|
||||
{
|
||||
NGCalendarDateRange *fir;
|
||||
|
||||
if (![self isRecurrent])
|
||||
return nil;
|
||||
|
||||
fir = [NGCalendarDateRange calendarDateRangeWithStartDate: [self startDate]
|
||||
endDate: [self due]];
|
||||
|
||||
return [self lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: fir];
|
||||
}
|
||||
|
||||
@end /* iCalToDo */
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
- (BOOL) isStillRelevant;
|
||||
- (unsigned int) occurenceInterval;
|
||||
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer;
|
||||
- (void) updateRecurrenceRulesUntilDate: (NSCalendarDate *) previousEndDate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
|
||||
@interface iCalToDo (SOGoExtensions)
|
||||
|
||||
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* ICALTODO_SOGO_H */
|
||||
|
||||
@@ -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]];
|
||||
}
|
||||
|
||||
@@ -791,7 +791,8 @@ convention:
|
||||
return date;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
|
||||
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
|
||||
container: (id) theContainer
|
||||
{
|
||||
NSMutableDictionary *fields;
|
||||
CardElement *element;
|
||||
|
||||
@@ -69,7 +69,8 @@
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) quickRecordForContainer: (id) theContainer
|
||||
- (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent
|
||||
container: (id) theContainer
|
||||
{
|
||||
NSMutableDictionary *fields;
|
||||
NSString *value;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* SOGoEAlarmsNotifier.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 @@
|
||||
/* SOGoEAlarmsNotifier.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2011-2013 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2011-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,6 +1,6 @@
|
||||
/* UIxAppointmentEditor.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2013 Inverse inc.
|
||||
* Copyright (C) 2007-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,6 +1,6 @@
|
||||
/* UIxAppointmentEditor.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2013 Inverse inc.
|
||||
* Copyright (C) 2007-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
|
||||
@@ -23,6 +23,7 @@
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGObjWeb/SoObject.h>
|
||||
#import <NGObjWeb/SoPermissions.h>
|
||||
@@ -31,6 +32,7 @@
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NGCalendarDateRange.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
@@ -49,8 +51,10 @@
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <Appointments/iCalCalendar+SOGo.h>
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/iCalPerson+SOGo.h>
|
||||
#import <Appointments/iCalRepeatableEntityObject+SOGo.h>
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
#import <Appointments/SOGoAppointmentOccurence.h>
|
||||
@@ -470,6 +474,7 @@
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoCalendarComponent *co;
|
||||
NSString *created_by;
|
||||
iCalAlarm *anAlarm;
|
||||
|
||||
BOOL resetAlarm;
|
||||
unsigned int snoozeAlarm;
|
||||
@@ -492,12 +497,26 @@
|
||||
componentCalendar = [componentCalendar container];
|
||||
[componentCalendar retain];
|
||||
}
|
||||
|
||||
created_by = [event createdBy];
|
||||
|
||||
if ([event hasAlarms] && ![event hasRecurrenceRules])
|
||||
// resetAlarm=yes is set only when we are about to show the alarm popup in the Web
|
||||
// interface of SOGo. See generic.js for details. snoozeAlarm=X is called when the
|
||||
// user clicks on "Snooze for" X minutes, when the popup is being displayed.
|
||||
// If either is set to yes, we must find the right alarm.
|
||||
resetAlarm = [[[context request] formValueForKey: @"resetAlarm"] boolValue];
|
||||
snoozeAlarm = [[[context request] formValueForKey: @"snoozeAlarm"] intValue];
|
||||
|
||||
if (resetAlarm || snoozeAlarm)
|
||||
{
|
||||
iCalAlarm *anAlarm;
|
||||
resetAlarm = [[[context request] formValueForKey: @"resetAlarm"] boolValue];
|
||||
snoozeAlarm = [[[context request] formValueForKey: @"snoozeAlarm"] intValue];
|
||||
iCalEvent *master;
|
||||
|
||||
master = event;
|
||||
[componentCalendar findEntityForClosestAlarm: &event
|
||||
timezone: timeZone
|
||||
startDate: &eventStartDate
|
||||
endDate: &eventEndDate];
|
||||
|
||||
if (resetAlarm)
|
||||
{
|
||||
iCalTrigger *aTrigger;
|
||||
@@ -506,7 +525,7 @@
|
||||
aTrigger = [anAlarm trigger];
|
||||
[aTrigger setValue: 0 ofAttribute: @"x-webstatus" to: @"triggered"];
|
||||
|
||||
[co saveComponent: event];
|
||||
[co saveComponent: master];
|
||||
}
|
||||
else if (snoozeAlarm)
|
||||
{
|
||||
@@ -516,8 +535,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
created_by = [event createdBy];
|
||||
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[[componentCalendar displayName] stringByEscapingHTMLString], @"calendar",
|
||||
[event tag], @"component",
|
||||
@@ -525,7 +542,6 @@
|
||||
[dateFormatter formattedTime: eventStartDate], @"startTime",
|
||||
[dateFormatter formattedDate: eventEndDate], @"endDate",
|
||||
[dateFormatter formattedTime: eventEndDate], @"endTime",
|
||||
//([event hasRecurrenceRules] ? @"1": @"0"), @"isRecurring",
|
||||
([event isAllDay] ? @"1": @"0"), @"isAllDay",
|
||||
[[event summary] stringByEscapingHTMLString], @"summary",
|
||||
[[event location] stringByEscapingHTMLString], @"location",
|
||||
|
||||
@@ -99,7 +99,7 @@ static NSArray *tasksFields = nil;
|
||||
@"c_status", @"c_title", @"c_enddate",
|
||||
@"c_classification", @"c_location", @"c_category",
|
||||
@"editable", @"erasable",
|
||||
@"c_priority", @"c_owner", nil];
|
||||
@"c_priority", @"c_owner", @"c_recurrence_id", @"isException", nil];
|
||||
[tasksFields retain];
|
||||
}
|
||||
}
|
||||
@@ -331,150 +331,158 @@ static NSArray *tasksFields = nil;
|
||||
|
||||
folders = [[clientObject subFolders] objectEnumerator];
|
||||
while ((currentFolder = [folders nextObject]))
|
||||
{
|
||||
if ([currentFolder isActive])
|
||||
{
|
||||
folderIsRemote = [currentFolder isKindOfClass: [SOGoWebAppointmentFolder class]];
|
||||
if ([currentFolder isActive])
|
||||
{
|
||||
folderIsRemote = [currentFolder isKindOfClass: [SOGoWebAppointmentFolder class]];
|
||||
|
||||
if ([criteria isEqualToString:@"title_Category_Location"])
|
||||
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component
|
||||
additionalFilters: criteria] objectEnumerator];
|
||||
|
||||
else if ([criteria isEqualToString:@"entireContent"])
|
||||
{
|
||||
// First research : Through the quick table inside the location, category and title columns
|
||||
quickInfos = [currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component
|
||||
additionalFilters: criteria];
|
||||
|
||||
// Save the c_name in another array to compare with
|
||||
if ([quickInfos count] > 0)
|
||||
{
|
||||
quickInfosFlag = YES;
|
||||
quickInfosName = [NSMutableArray arrayWithCapacity:[quickInfos count]];
|
||||
for (i = 0; i < [quickInfos count]; i++)
|
||||
[quickInfosName addObject:[[quickInfos objectAtIndex:i] objectForKey:@"c_name"]];
|
||||
}
|
||||
|
||||
// Second research : Every objects except for those already in the quickInfos array
|
||||
allInfos = [currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: nil
|
||||
component: component];
|
||||
if (quickInfosFlag == YES)
|
||||
{
|
||||
for (i = ([allInfos count] - 1); i >= 0 ; i--) {
|
||||
if([quickInfosName containsObject:[[allInfos objectAtIndex:i] objectForKey:@"c_name"]])
|
||||
[allInfos removeObjectAtIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < [allInfos count]; i++)
|
||||
{
|
||||
iCalString = [[allInfos objectAtIndex:i] objectForKey:@"c_content"];
|
||||
calendar = [iCalCalendar parseSingleFromSource: iCalString];
|
||||
master = [calendar firstChildWithTag:component];
|
||||
if (master) {
|
||||
if ([[master comment] length] > 0)
|
||||
//
|
||||
// criteria can have the following values: "title", "title_Category_Location" and "entireContent"
|
||||
//
|
||||
if ([criteria isEqualToString:@"title_Category_Location"])
|
||||
{
|
||||
match = [[master comment] rangeOfString:value options:NSCaseInsensitiveSearch];
|
||||
if (match.length > 0) {
|
||||
[quickInfos addObject:[allInfos objectAtIndex:i]];
|
||||
}
|
||||
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component
|
||||
additionalFilters: criteria] objectEnumerator];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([criteria isEqualToString:@"entireContent"])
|
||||
{
|
||||
// First search : Through the quick table inside the location, category and title columns
|
||||
quickInfos = [currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component
|
||||
additionalFilters: criteria];
|
||||
|
||||
currentInfos = [quickInfos objectEnumerator];
|
||||
}
|
||||
|
||||
// Save the c_name in another array to compare with
|
||||
if ([quickInfos count] > 0)
|
||||
{
|
||||
quickInfosFlag = YES;
|
||||
quickInfosName = [NSMutableArray arrayWithCapacity:[quickInfos count]];
|
||||
for (i = 0; i < [quickInfos count]; i++)
|
||||
[quickInfosName addObject:[[quickInfos objectAtIndex:i] objectForKey:@"c_name"]];
|
||||
}
|
||||
|
||||
// Second research : Every objects except for those already in the quickInfos array
|
||||
allInfos = [currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: nil
|
||||
component: component];
|
||||
if (quickInfosFlag == YES)
|
||||
{
|
||||
for (i = ([allInfos count] - 1); i >= 0 ; i--) {
|
||||
if([quickInfosName containsObject:[[allInfos objectAtIndex:i] objectForKey:@"c_name"]])
|
||||
[allInfos removeObjectAtIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < [allInfos count]; i++)
|
||||
{
|
||||
iCalString = [[allInfos objectAtIndex:i] objectForKey:@"c_content"];
|
||||
calendar = [iCalCalendar parseSingleFromSource: iCalString];
|
||||
master = [calendar firstChildWithTag:component];
|
||||
if (master) {
|
||||
if ([[master comment] length] > 0)
|
||||
{
|
||||
match = [[master comment] rangeOfString:value options:NSCaseInsensitiveSearch];
|
||||
if (match.length > 0) {
|
||||
[quickInfos addObject:[allInfos objectAtIndex:i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentInfos = [quickInfos objectEnumerator];
|
||||
}
|
||||
else
|
||||
{
|
||||
id foo;
|
||||
|
||||
else
|
||||
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component] objectEnumerator];
|
||||
foo = [currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component];
|
||||
currentInfos = [foo objectEnumerator];
|
||||
}
|
||||
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]);
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]);
|
||||
|
||||
while ((newInfo = [currentInfos nextObject]))
|
||||
{
|
||||
if ([fields containsObject: @"editable"])
|
||||
{
|
||||
if (folderIsRemote)
|
||||
// .ics subscriptions are not editable
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"editable"];
|
||||
else
|
||||
{
|
||||
// Identifies whether the active user can edit the event.
|
||||
role = [currentFolder roleForComponentsWithAccessClass:[[newInfo objectForKey: @"c_classification"] intValue]
|
||||
forUser: userLogin];
|
||||
if ([role isEqualToString: @"ComponentModifier"] || [role length] == 0)
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"editable"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"editable"];
|
||||
}
|
||||
}
|
||||
while ((newInfo = [currentInfos nextObject]))
|
||||
{
|
||||
if ([fields containsObject: @"editable"])
|
||||
{
|
||||
if (folderIsRemote)
|
||||
// .ics subscriptions are not editable
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"editable"];
|
||||
else
|
||||
{
|
||||
// Identifies whether the active user can edit the event.
|
||||
role = [currentFolder roleForComponentsWithAccessClass:[[newInfo objectForKey: @"c_classification"] intValue]
|
||||
forUser: userLogin];
|
||||
if ([role isEqualToString: @"ComponentModifier"] || [role length] == 0)
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"editable"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"editable"];
|
||||
}
|
||||
}
|
||||
if ([fields containsObject: @"ownerIsOrganizer"])
|
||||
{
|
||||
// Identifies whether the active user is the organizer
|
||||
// of this event.
|
||||
NSString *c_orgmail;
|
||||
c_orgmail = [newInfo objectForKey: @"c_orgmail"];
|
||||
{
|
||||
// Identifies whether the active user is the organizer
|
||||
// of this event.
|
||||
NSString *c_orgmail;
|
||||
c_orgmail = [newInfo objectForKey: @"c_orgmail"];
|
||||
|
||||
if ([c_orgmail isKindOfClass: [NSString class]] && [ownerUser hasEmail: c_orgmail])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"ownerIsOrganizer"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"ownerIsOrganizer"];
|
||||
}
|
||||
if ([c_orgmail isKindOfClass: [NSString class]] && [ownerUser hasEmail: c_orgmail])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"ownerIsOrganizer"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"ownerIsOrganizer"];
|
||||
}
|
||||
if (isErasable)
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"erasable"];
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1]
|
||||
forKey: @"erasable"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"erasable"];
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0]
|
||||
forKey: @"erasable"];
|
||||
|
||||
[newInfo setObject: [currentFolder nameInContainer]
|
||||
forKey: @"c_folder"];
|
||||
[newInfo setObject: [currentFolder ownerInContext: context]
|
||||
forKey: @"c_owner"];
|
||||
calendarName = [currentFolder displayName];
|
||||
if (calendarName == nil)
|
||||
calendarName = @"";
|
||||
[newInfo setObject: calendarName
|
||||
forKey: @"calendarName"];
|
||||
if (![[newInfo objectForKey: @"c_title"] length])
|
||||
[self _fixComponentTitle: newInfo withType: component];
|
||||
forKey: @"c_folder"];
|
||||
[newInfo setObject: [currentFolder ownerInContext: context]
|
||||
forKey: @"c_owner"];
|
||||
calendarName = [currentFolder displayName];
|
||||
if (calendarName == nil)
|
||||
calendarName = @"";
|
||||
[newInfo setObject: calendarName
|
||||
forKey: @"calendarName"];
|
||||
if (![[newInfo objectForKey: @"c_title"] length])
|
||||
[self _fixComponentTitle: newInfo withType: component];
|
||||
|
||||
// Possible improvement: only call _fixDates if event is recurrent
|
||||
// or the view range span a daylight saving time change
|
||||
[self _fixDates: newInfo];
|
||||
newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields
|
||||
notFoundMarker: marker]];
|
||||
// Escape HTML
|
||||
count = [newInfoForComponent count];
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
currentInfo = [newInfoForComponent objectAtIndex: i];
|
||||
if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
|
||||
[newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]];
|
||||
[self _fixDates: newInfo];
|
||||
newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields
|
||||
notFoundMarker: marker]];
|
||||
// Escape HTML
|
||||
count = [newInfoForComponent count];
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
currentInfo = [newInfoForComponent objectAtIndex: i];
|
||||
if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)])
|
||||
[newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]];
|
||||
}
|
||||
[infos addObject: newInfoForComponent];
|
||||
}
|
||||
}
|
||||
[infos addObject: newInfoForComponent];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
@@ -520,7 +528,6 @@ static NSArray *tasksFields = nil;
|
||||
// - for ALL subscribed and ACTIVE calendars
|
||||
// - returns alarms that will occur in the next 48 hours or the non-triggered alarms
|
||||
// for non-completed events
|
||||
// - recurring events are currently ignored
|
||||
//
|
||||
- (WOResponse *) alarmsListAction
|
||||
{
|
||||
@@ -539,33 +546,28 @@ static NSArray *tasksFields = nil;
|
||||
|
||||
folders = [[clientObject subFolders] objectEnumerator];
|
||||
while ((currentFolder = [folders nextObject]))
|
||||
{
|
||||
if ([currentFolder isActive] && [currentFolder showCalendarAlarms])
|
||||
{
|
||||
NSDictionary *entry;
|
||||
NSArray *alarms;
|
||||
BOOL isCycle;
|
||||
int i;
|
||||
|
||||
alarms = [currentFolder fetchAlarmInfosFrom: [NSNumber numberWithInt: browserTime]
|
||||
to: [NSNumber numberWithInt: laterTime]];
|
||||
|
||||
for (i = 0; i < [alarms count]; i++)
|
||||
{
|
||||
entry = [alarms objectAtIndex: i];
|
||||
isCycle = [[entry objectForKey: @"c_iscycle"] boolValue];
|
||||
|
||||
if (!isCycle)
|
||||
if ([currentFolder isActive] && [currentFolder showCalendarAlarms])
|
||||
{
|
||||
[allAlarms addObject: [NSArray arrayWithObjects:
|
||||
[currentFolder nameInContainer],
|
||||
[entry objectForKey: @"c_name"],
|
||||
[entry objectForKey: @"c_nextalarm"],
|
||||
nil]];
|
||||
NSDictionary *entry;
|
||||
NSArray *alarms;
|
||||
int i;
|
||||
|
||||
alarms = [currentFolder fetchAlarmInfosFrom: [NSNumber numberWithInt: browserTime]
|
||||
to: [NSNumber numberWithInt: laterTime]];
|
||||
|
||||
for (i = 0; i < [alarms count]; i++)
|
||||
{
|
||||
entry = [alarms objectAtIndex: i];
|
||||
|
||||
[allAlarms addObject: [NSArray arrayWithObjects:
|
||||
[currentFolder nameInContainer],
|
||||
[entry objectForKey: @"c_name"],
|
||||
[entry objectForKey: @"c_nextalarm"],
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response = [self responseWithStatus: 200];
|
||||
@@ -1242,9 +1244,9 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_all"])
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if (([tasksView isEqualToString:@"view_overdue"]) && ([[filteredTask objectAtIndex:12] isEqualToString:@"overdue"]))
|
||||
else if (([tasksView isEqualToString:@"view_overdue"]) && ([[filteredTask objectAtIndex:15] isEqualToString:@"overdue"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_incomplete"] && (![[filteredTask objectAtIndex:12] isEqualToString:@"completed"]))
|
||||
else if ([tasksView isEqualToString:@"view_incomplete"] && (![[filteredTask objectAtIndex:15] isEqualToString:@"completed"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_not_started"] && ([[[filteredTask objectAtIndex:3] stringValue] isEqualToString:@"0"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
|
||||
@@ -176,7 +176,6 @@
|
||||
////////////////////////////////// JUNK ////////////////////////////////////////
|
||||
|
||||
/* access */
|
||||
- (BOOL) isMyComponent;
|
||||
- (BOOL) canEditComponent;
|
||||
- (unsigned int) firstDayOfWeek;
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/* UIxComponentEditor.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2013 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Francis Lachapelle <flachapelle@inverse.ca>
|
||||
* Copyright (C) 2006-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,10 +30,15 @@
|
||||
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalByDayMask.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalRepeatableEntityObject.h>
|
||||
#import <NGCards/iCalRecurrenceRule.h>
|
||||
#import <NGCards/iCalToDo.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
|
||||
|
||||
#import <NGCards/NSString+NGCards.h>
|
||||
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||
#import <NGObjWeb/SoSecurityManager.h>
|
||||
@@ -46,6 +48,7 @@
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGExtensions/NSCalendarDate+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSString+misc.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
@@ -1717,14 +1720,9 @@ RANGE(2);
|
||||
|
||||
/* access */
|
||||
|
||||
- (BOOL) isMyComponent
|
||||
{
|
||||
return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
|
||||
}
|
||||
|
||||
- (BOOL) canEditComponent
|
||||
{
|
||||
return [self isMyComponent];
|
||||
return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
|
||||
}
|
||||
|
||||
/* response generation */
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* UIxOccurenceDialog.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
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
{
|
||||
if (!todo)
|
||||
{
|
||||
todo = (iCalToDo *) [[self clientObject] component: YES secure: YES];
|
||||
todo = (iCalToDo *) [[self clientObject] occurence];
|
||||
[[todo parent] retain];
|
||||
}
|
||||
|
||||
@@ -424,12 +424,17 @@
|
||||
WOResponse *result;
|
||||
NSDictionary *data;
|
||||
NSCalendarDate *startDate, *dueDate;
|
||||
SOGoCalendarComponent *co;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoUserDefaults *ud;
|
||||
iCalAlarm *anAlarm;
|
||||
BOOL resetAlarm;
|
||||
BOOL snoozeAlarm;
|
||||
|
||||
[self todo];
|
||||
|
||||
co = [self clientObject];
|
||||
|
||||
result = [self responseWithStatus: 200];
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
@@ -437,21 +442,41 @@
|
||||
[startDate setTimeZone: timeZone];
|
||||
dueDate = [todo due];
|
||||
[dueDate setTimeZone: timeZone];
|
||||
|
||||
resetAlarm = [[[context request] formValueForKey: @"resetAlarm"] boolValue];
|
||||
if (resetAlarm && [todo hasAlarms] && ![todo hasRecurrenceRules])
|
||||
{
|
||||
iCalAlarm *anAlarm;
|
||||
iCalTrigger *aTrigger;
|
||||
SOGoCalendarComponent *co;
|
||||
|
||||
anAlarm = [[todo alarms] objectAtIndex: 0];
|
||||
aTrigger = [anAlarm trigger];
|
||||
[aTrigger setValue: 0 ofAttribute: @"x-webstatus" to: @"triggered"];
|
||||
|
||||
co = [self clientObject];
|
||||
[co saveComponent: todo];
|
||||
// resetAlarm=yes is set only when we are about to show the alarm popup in the Web
|
||||
// interface of SOGo. See generic.js for details. snoozeAlarm=X is called when the
|
||||
// user clicks on "Snooze for" X minutes, when the popup is being displayed.
|
||||
// If either is set to yes, we must find the right alarm.
|
||||
resetAlarm = [[[context request] formValueForKey: @"resetAlarm"] boolValue];
|
||||
snoozeAlarm = [[[context request] formValueForKey: @"snoozeAlarm"] intValue];
|
||||
|
||||
if (resetAlarm || snoozeAlarm)
|
||||
{
|
||||
iCalToDo *master;
|
||||
|
||||
master = todo;
|
||||
[[co container] findEntityForClosestAlarm: &todo
|
||||
timezone: timeZone
|
||||
startDate: &startDate
|
||||
endDate: &dueDate];
|
||||
if (resetAlarm)
|
||||
{
|
||||
iCalTrigger *aTrigger;
|
||||
|
||||
anAlarm = [[todo alarms] objectAtIndex: 0];
|
||||
aTrigger = [anAlarm trigger];
|
||||
[aTrigger setValue: 0 ofAttribute: @"x-webstatus" to: @"triggered"];
|
||||
|
||||
[co saveComponent: master];
|
||||
}
|
||||
else if (snoozeAlarm)
|
||||
{
|
||||
anAlarm = [[todo alarms] objectAtIndex: 0];
|
||||
if ([[anAlarm action] caseInsensitiveCompare: @"DISPLAY"] == NSOrderedSame)
|
||||
[co snoozeAlarm: snoozeAlarm];
|
||||
}
|
||||
}
|
||||
resetAlarm = [[[context request] formValueForKey: @"resetAlarm"] boolValue];
|
||||
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[todo tag], @"component",
|
||||
@@ -538,26 +563,6 @@
|
||||
|
||||
}
|
||||
|
||||
// TODO: add tentatively
|
||||
|
||||
// - (id) acceptOrDeclineAction: (BOOL) _accept
|
||||
// {
|
||||
// [[self clientObject] changeParticipationStatus:
|
||||
// _accept ? @"ACCEPTED" : @"DECLINED"];
|
||||
|
||||
// return self;
|
||||
// }
|
||||
|
||||
// - (id) acceptAction
|
||||
// {
|
||||
// return [self acceptOrDeclineAction: YES];
|
||||
// }
|
||||
|
||||
// - (id) declineAction
|
||||
// {
|
||||
// return [self acceptOrDeclineAction: NO];
|
||||
// }
|
||||
|
||||
- (id) changeStatusAction
|
||||
{
|
||||
NSString *newStatus;
|
||||
|
||||
@@ -399,6 +399,11 @@
|
||||
pageName = "UIxTaskEditor";
|
||||
actionName = "save";
|
||||
};
|
||||
delete = {
|
||||
protectedBy = "Delete Object";
|
||||
pageName = "UIxOccurenceDialog";
|
||||
actionName = "delete";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -678,10 +678,10 @@ function _deleteEventFromTables(calendar, cname, occurenceTime) {
|
||||
}
|
||||
|
||||
// Delete task from tasks list
|
||||
var row = $(calendar + basename);
|
||||
if (row) {
|
||||
var rows = $$("tr[id^='" + calendar + basename + "']");
|
||||
rows.each(function(row) {
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _deleteCalendarEventCache(calendar, cname, occurenceTime) {
|
||||
@@ -725,7 +725,6 @@ function _deleteCalendarEventCache(calendar, cname, occurenceTime) {
|
||||
function deleteEventCallback(http) {
|
||||
if (http.readyState == 4) {
|
||||
if (isHttpStatus204(http.status)) {
|
||||
var isTask = false;
|
||||
var calendar = http.callbackData.calendar;
|
||||
var events = http.callbackData.events;
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
@@ -901,14 +900,14 @@ function performEventDeletion(folder, event, recurrence) {
|
||||
if (recurrence) {
|
||||
// Only one recurrence
|
||||
var occurenceTime = recurrence.substring(9);
|
||||
var nodes = _eventBlocksMatching(folder, event, occurenceTime);
|
||||
//var nodes = _eventBlocksMatching(folder, event, occurenceTime);
|
||||
var urlstr = ApplicationBaseURL + "/" + folder + "/" + event + "/" + recurrence + "/delete";
|
||||
|
||||
if (nodes)
|
||||
document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr,
|
||||
performDeleteEventCallback,
|
||||
{ nodes: nodes,
|
||||
occurence: occurenceTime });
|
||||
document.deleteEventAjaxRequest = triggerAjaxRequest(urlstr,
|
||||
performDeleteEventCallback,
|
||||
{ calendar: folder,
|
||||
cname: event,
|
||||
occurence: occurenceTime });
|
||||
}
|
||||
else {
|
||||
// All recurrences
|
||||
@@ -926,11 +925,11 @@ function performEventDeletion(folder, event, recurrence) {
|
||||
function performDeleteEventCallback(http) {
|
||||
if (http.readyState == 4) {
|
||||
if (isHttpStatus204(http.status)) {
|
||||
|
||||
var occurenceTime = http.callbackData.occurence;
|
||||
var nodes = http.callbackData.nodes;
|
||||
var cname = nodes[0].cname;
|
||||
var calendar = nodes[0].calendar;
|
||||
|
||||
var cname = http.callbackData.cname;
|
||||
var calendar = http.callbackData.calendar;
|
||||
|
||||
_deleteCalendarEventBlocks(calendar, cname, occurenceTime);
|
||||
_deleteEventFromTables(calendar, cname, occurenceTime);
|
||||
_deleteCalendarEventCache(calendar, cname, occurenceTime);
|
||||
@@ -1178,8 +1177,10 @@ function tasksListCallback(http) {
|
||||
// [10] Erasable?
|
||||
// [11] Priority (0, 1 = important, 9 = low)
|
||||
// [12] Owner
|
||||
// [13] Status CSS class (duelater, completed, etc)
|
||||
// [14] Due date (formatted)
|
||||
// [13] recurrence-id
|
||||
// [14] isException
|
||||
// [15] Status CSS class (duelater, completed, etc)
|
||||
// [16] Due date (formatted)
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var row = createElement("tr");
|
||||
@@ -1189,9 +1190,22 @@ function tasksListCallback(http) {
|
||||
|
||||
var calendar = escape(data[i][1]);
|
||||
var cname = escape(data[i][0]);
|
||||
row.setAttribute("id", calendar + "-" + cname);
|
||||
|
||||
var rTime = data[i][13];
|
||||
var id = escape(data[i][1] + "-" + data[i][0]);
|
||||
if (rTime)
|
||||
id += "-" + escape(rTime);
|
||||
row.setAttribute("id", id);
|
||||
//row.cname = escape(data[i][0]);
|
||||
//row.calendar = calendar;
|
||||
if (rTime)
|
||||
row.recurrenceTime = escape(rTime);
|
||||
row.isException = data[i][14];
|
||||
|
||||
|
||||
//row.setAttribute("id", calendar + "-" + cname);
|
||||
//listItem.addClassName(data[i][5]); // Classification
|
||||
row.addClassName(data[i][14]); // status
|
||||
//row.addClassName(data[i][14]); // status
|
||||
row.addClassName("taskRow");
|
||||
row.calendar = calendar;
|
||||
row.cname = cname;
|
||||
@@ -1236,8 +1250,8 @@ function tasksListCallback(http) {
|
||||
|
||||
cell = createElement("td");
|
||||
row.appendChild(cell);
|
||||
if (data[i][14])
|
||||
cell.update(data[i][14]); // end date
|
||||
if (data[i][16])
|
||||
cell.update(data[i][16]); // end date
|
||||
|
||||
cell = createElement("td");
|
||||
row.appendChild(cell);
|
||||
@@ -3086,7 +3100,12 @@ function marksTasksAsCompleted () {
|
||||
|
||||
function _updateTaskCompletion (task, value) {
|
||||
url = (ApplicationBaseURL + "/" + task.calendar
|
||||
+ "/" + task.cname + "/changeStatus?status=" + value);
|
||||
+ "/" + task.cname);
|
||||
|
||||
if (task.recurrenceTime)
|
||||
url += ("/occurence" + task.recurrenceTime);
|
||||
|
||||
url += ("/changeStatus?status=" + value);
|
||||
|
||||
triggerAjaxRequest(url, refreshTasks, null);
|
||||
|
||||
|
||||
@@ -110,8 +110,10 @@ function onTimeControlCheck(checkBox) {
|
||||
for (var i = 0; i < selects.length; i++)
|
||||
if (selects[i] != checkBox)
|
||||
selects[i].disabled = !checkBox.checked;
|
||||
if (checkBox.id == "startDateCB")
|
||||
if (checkBox.id == "startDateCB") {
|
||||
$("repeatList").disabled = !checkBox.checked;
|
||||
$("reminderList").disabled = !checkBox.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user