Fix support for recurrent tasks
parent
d779657fdf
commit
4d0dcc4978
1
NEWS
1
NEWS
|
@ -15,6 +15,7 @@ Bug fixes
|
|||
- [web] fixed vCard generation for tags with no type (#3826)
|
||||
- [web] only show the organizer field of an IMIP REPLY if one is defined
|
||||
- [web] fixed saving the note of a card (#3849)
|
||||
- [web] fixed support for recurrent tasks
|
||||
- [eas] improve handling of email folders without a parent
|
||||
- [eas] never send IMIP reply when the "initiator" is Outlook 2013/2016
|
||||
- [core] only consider SMTP addresses for AD's proxyAddresses (#3842)
|
||||
|
|
|
@ -856,16 +856,22 @@ static Class iCalEventK = nil;
|
|||
}
|
||||
[record setObject: dateSecs forKey: @"c_recurrence_id"];
|
||||
|
||||
date = [theCycle endDate];
|
||||
if (theEventTimeZone)
|
||||
date = [record valueForKey: @"c_enddate"];
|
||||
if ([date isNotNull])
|
||||
{
|
||||
secondsOffsetFromGMT = (int) [[theEventTimeZone periodForDate: date] secondsOffsetFromGMT];
|
||||
date = [date dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -secondsOffsetFromGMT];
|
||||
date = [theCycle endDate];
|
||||
if (theEventTimeZone)
|
||||
{
|
||||
secondsOffsetFromGMT = (int) [[theEventTimeZone periodForDate: date] secondsOffsetFromGMT];
|
||||
date = [date dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -secondsOffsetFromGMT];
|
||||
}
|
||||
[date setTimeZone: timeZone];
|
||||
[record setObject: date forKey: @"endDate"];
|
||||
dateSecs = [NSNumber numberWithInt: [date timeIntervalSince1970]];
|
||||
[record setObject: dateSecs forKey: @"c_enddate"];
|
||||
}
|
||||
[date setTimeZone: timeZone];
|
||||
[record setObject: date forKey: @"endDate"];
|
||||
dateSecs = [NSNumber numberWithInt: [date timeIntervalSince1970]];
|
||||
[record setObject: dateSecs forKey: @"c_enddate"];
|
||||
else
|
||||
[record removeObjectForKey: @"endDate"];
|
||||
|
||||
return record;
|
||||
}
|
||||
|
@ -967,7 +973,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
toArray: (NSMutableArray *) ma
|
||||
{
|
||||
NGCalendarDateRange *recurrenceIdRange;
|
||||
NSCalendarDate *recurrenceId;
|
||||
NSCalendarDate *recurrenceId, *masterEndDate, *endDate;
|
||||
NSMutableDictionary *newRecord;
|
||||
NGCalendarDateRange *newRecordRange;
|
||||
NSComparisonResult compare;
|
||||
|
@ -997,8 +1003,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
[recurrenceId setTimeZone: tz];
|
||||
}
|
||||
|
||||
master = [[[component parent] events] objectAtIndex: 0];
|
||||
delta = [[master endDate] timeIntervalSinceDate: [master startDate]];
|
||||
if ([component isKindOfClass: [iCalEvent class]])
|
||||
{
|
||||
master = [[[component parent] events] objectAtIndex: 0];
|
||||
masterEndDate = [master endDate];
|
||||
endDate = [component endDate];
|
||||
}
|
||||
else
|
||||
{
|
||||
master = [[[component parent] todos] objectAtIndex: 0];
|
||||
masterEndDate = [master due];
|
||||
endDate = [component due];
|
||||
}
|
||||
|
||||
delta = [masterEndDate timeIntervalSinceDate: [master startDate]];
|
||||
recurrenceIdRange = [NGCalendarDateRange calendarDateRangeWithStartDate: recurrenceId
|
||||
endDate: [recurrenceId dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds: delta]];
|
||||
if ([dateRange doesIntersectWithDateRange: recurrenceIdRange])
|
||||
|
@ -1009,8 +1027,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
|||
if (recordIndex > -1)
|
||||
{
|
||||
if ([dateRange containsDate: [component startDate]] ||
|
||||
[dateRange containsDate: [component endDate]])
|
||||
{
|
||||
[dateRange containsDate: endDate])
|
||||
{
|
||||
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]];
|
||||
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
||||
|
|
|
@ -84,20 +84,23 @@
|
|||
|
||||
newOccurence = (iCalToDo *) [super newOccurenceWithID: theRecurrenceID];
|
||||
date = [newOccurence recurrenceId];
|
||||
[newOccurence setStartDate: date];
|
||||
|
||||
master = [self component: NO secure: NO];
|
||||
firstDate = [master startDate];
|
||||
|
||||
interval = [[master due]
|
||||
timeIntervalSinceDate: (NSDate *)firstDate];
|
||||
if ([master due])
|
||||
{
|
||||
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]];
|
||||
[newOccurence setDue: [date addYear: 0
|
||||
month: 0
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: interval]];
|
||||
}
|
||||
|
||||
return newOccurence;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
*/
|
||||
- (NSDictionary *) attributesInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *allEvents, *rules;
|
||||
NSArray *allComponents, *rules;
|
||||
NSCalendarDate *untilDate;
|
||||
NSMutableDictionary *data, *repeat;
|
||||
NSString *frequency;
|
||||
|
@ -93,8 +93,11 @@
|
|||
{
|
||||
// If the component is an occurrence of a recurrent component,
|
||||
// consider the recurrence rules of the master component.
|
||||
allEvents = [[self parent] events];
|
||||
masterComponent = [allEvents objectAtIndex: 0];
|
||||
if ([self isKindOfClass: [iCalEvent class]])
|
||||
allComponents = [[self parent] events];
|
||||
else
|
||||
allComponents = [[self parent] todos];
|
||||
masterComponent = [allComponents objectAtIndex: 0];
|
||||
rules = [masterComponent recurrenceRules];
|
||||
}
|
||||
else
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
#define taskRecurrenceIdIndex 16
|
||||
#define taskIsExceptionIndex 17
|
||||
#define taskDescriptionIndex 18
|
||||
#define taskStatusFlagIndex 19
|
||||
|
||||
@interface NSArray (SOGoEventComparison)
|
||||
|
||||
|
|
|
@ -1562,7 +1562,6 @@ _computeBlocksPosition (NSArray *blocks)
|
|||
BOOL showCompleted;
|
||||
int statusCode;
|
||||
int startSecs;
|
||||
int endsSecs;
|
||||
|
||||
filteredTasks = [NSMutableArray array];
|
||||
|
||||
|
@ -1571,7 +1570,6 @@ _computeBlocksPosition (NSArray *blocks)
|
|||
[self saveSortValue: @"TasksSortingState"];
|
||||
|
||||
startSecs = (unsigned int) [startDate timeIntervalSince1970];
|
||||
endsSecs = (unsigned int) [endDate timeIntervalSince1970];
|
||||
tasksView = [request formValueForKey: @"filterpopup"];
|
||||
|
||||
showCompleted = [[request formValueForKey: @"show_completed"] intValue];
|
||||
|
@ -1602,15 +1600,19 @@ _computeBlocksPosition (NSArray *blocks)
|
|||
[tasksView isEqualToString:@"view_next7"] ||
|
||||
[tasksView isEqualToString:@"view_next14"] ||
|
||||
[tasksView isEqualToString:@"view_next31"] ||
|
||||
[tasksView isEqualToString:@"view_thismonth"]) && ((endDateStamp <= endsSecs) && (endDateStamp >= startSecs)))
|
||||
[tasksView isEqualToString:@"view_thismonth"]) &&
|
||||
(endDateStamp == 0 || endDateStamp >= startSecs))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_all"])
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if (([tasksView isEqualToString:@"view_overdue"]) && ([[filteredTask objectAtIndex:18] isEqualToString:@"overdue"]))
|
||||
else if (([tasksView isEqualToString:@"view_overdue"]) &&
|
||||
([[filteredTask objectAtIndex:taskStatusFlagIndex] isEqualToString:@"overdue"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_incomplete"] && (![[filteredTask objectAtIndex:18] isEqualToString:@"completed"]))
|
||||
else if ([tasksView isEqualToString:@"view_incomplete"] &&
|
||||
(![[filteredTask objectAtIndex:taskStatusFlagIndex] isEqualToString:@"completed"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
else if ([tasksView isEqualToString:@"view_not_started"] && ([[[filteredTask objectAtIndex:taskStatusIndex] stringValue] isEqualToString:@"0"]))
|
||||
else if ([tasksView isEqualToString:@"view_not_started"] &&
|
||||
([[[filteredTask objectAtIndex:taskStatusIndex] stringValue] isEqualToString:@"0"]))
|
||||
[filteredTasks addObject: filteredTask];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* UIxTaskEditor.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2015 Inverse inc.
|
||||
* Copyright (C) 2007-2016 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -42,6 +42,7 @@
|
|||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoTaskObject.h>
|
||||
#import <Appointments/SOGoTaskOccurence.h>
|
||||
|
||||
#import "UIxComponentEditor.h"
|
||||
#import "UIxTaskEditor.h"
|
||||
|
@ -325,6 +326,8 @@
|
|||
|
||||
todo = [self todo];
|
||||
co = [self clientObject];
|
||||
if ([co isKindOfClass: [SOGoTaskOccurence class]])
|
||||
co = [co container];
|
||||
previousCalendar = [co container];
|
||||
sm = [SoSecurityManager sharedSecurityManager];
|
||||
|
||||
|
@ -493,7 +496,6 @@
|
|||
NSCalendarDate *startDate, *dueDate, *completedDate;
|
||||
NSTimeZone *timeZone;
|
||||
SOGoCalendarComponent *co;
|
||||
SOGoAppointmentFolder *thisFolder;
|
||||
SOGoUserDefaults *ud;
|
||||
iCalAlarm *anAlarm;
|
||||
iCalToDo *todo;
|
||||
|
@ -503,7 +505,6 @@
|
|||
todo = [self todo];
|
||||
|
||||
co = [self clientObject];
|
||||
thisFolder = [co container];
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
timeZone = [ud timeZone];
|
||||
|
@ -533,7 +534,7 @@
|
|||
iCalToDo *master;
|
||||
|
||||
master = todo;
|
||||
[thisFolder findEntityForClosestAlarm: &todo
|
||||
[componentCalendar findEntityForClosestAlarm: &todo
|
||||
timezone: timeZone
|
||||
startDate: &startDate
|
||||
endDate: &dueDate];
|
||||
|
@ -556,13 +557,22 @@
|
|||
}
|
||||
|
||||
data = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[co nameInContainer], @"id",
|
||||
[thisFolder nameInContainer], @"pid",
|
||||
[thisFolder displayName], @"calendar",
|
||||
[componentCalendar nameInContainer], @"pid",
|
||||
[componentCalendar displayName], @"calendar",
|
||||
[NSNumber numberWithBool: [self isReadOnly]], @"isReadOnly",
|
||||
[self alarm], @"alarm",
|
||||
nil];
|
||||
|
||||
if ([self isChildOccurrence])
|
||||
{
|
||||
[data setObject: [[co container] nameInContainer] forKey: @"id"];
|
||||
[data setObject: [co nameInContainer] forKey: @"occurrenceId"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[data setObject: [co nameInContainer] forKey: @"id"];
|
||||
}
|
||||
|
||||
if (startDate)
|
||||
{
|
||||
[data setObject: [dateFormatter formattedDate: startDate] forKey: @"localizedStartDate"];
|
||||
|
|
|
@ -104,38 +104,68 @@
|
|||
</md-list-item>
|
||||
</md-list>
|
||||
</md-dialog-content>
|
||||
<!-- edit -->
|
||||
|
||||
<!-- actions -->
|
||||
|
||||
<!-- editable but not recurrent -->
|
||||
<md-dialog-actions ng-show="editor.component.isEditable()">
|
||||
<md-button class="md-warn" label:aria-label="Delete Task"
|
||||
ng-show="editor.component.isEditable() || editor.component.isInvitation()"
|
||||
ng-click="editor.deleteAllOccurrences()">
|
||||
<var:string label:value="Delete"/>
|
||||
</md-button>
|
||||
<md-menu ng-show="editor.component.isEditableOccurrence() || editor.component.isInvitationOccurrence()">
|
||||
<md-button class="md-warn" label:aria-label="Delete Task"
|
||||
ng-click="$mdOpenMenu()"
|
||||
md-menu-origin="md-menu-origin">
|
||||
<var:string label:value="Delete"/><md-icon>arrow_drop_down</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button class="md-warn"
|
||||
ng-click="editor.deleteOccurrence()">
|
||||
<var:string label:value="Delete This Occurrence"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button class="md-warn"
|
||||
ng-click="editor.deleteAllOccurrences()">
|
||||
<var:string label:value="Delete All Occurrences"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button ng-click="editor.edit()">
|
||||
<var:string label:value="Edit"/>
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
<!-- editable and recurrent -->
|
||||
<md-dialog-actions ng-show="editor.component.isEditableOccurrence()">
|
||||
<md-menu>
|
||||
<md-button class="md-warn"
|
||||
label:aria-label="Delete Task"
|
||||
ng-click="$mdOpenMenu()"
|
||||
md-menu-origin="md-menu-origin">
|
||||
<var:string label:value="Delete"/> <md-icon>arrow_drop_down</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button class="md-warn"
|
||||
label:aria-label="Delete This Occurrence"
|
||||
ng-click="editor.deleteOccurrence()">
|
||||
<md-icon>repeat_one</md-icon> <var:string label:value="Delete This Occurrence"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button class="md-warn"
|
||||
label:aria-label="Delete All Occurrences"
|
||||
ng-click="editor.deleteAllOccurrences()">
|
||||
<md-icon>repeat</md-icon> <var:string label:value="Delete All Occurrences"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-menu>
|
||||
<md-button label:aria-label="Edit"
|
||||
ng-click="$mdOpenMenu()"
|
||||
md-menu-origin="md-menu-origin">
|
||||
<var:string label:value="Edit"/> <md-icon>arrow_drop_down</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content>
|
||||
<md-menu-item>
|
||||
<md-button type="button" label:aria-label="Edit This Occurrence"
|
||||
ng-click="editor.edit()">
|
||||
<md-icon>repeat_one</md-icon> <var:string label:value="Edit This Occurrence"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button type="button" label:aria-label="Edit All Occurrences"
|
||||
ng-click="editor.editAllOccurrences()">
|
||||
<md-icon>repeat</md-icon> <var:string label:value="Edit All Occurrences"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
</container>
|
||||
|
|
Loading…
Reference in New Issue