Fix support for recurrent tasks

pull/225/head
Francis Lachapelle 2016-10-24 16:31:59 -04:00
parent d779657fdf
commit 4d0dcc4978
8 changed files with 130 additions and 62 deletions

1
NEWS
View File

@ -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)

View File

@ -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];

View File

@ -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;
}

View File

@ -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

View File

@ -68,6 +68,7 @@
#define taskRecurrenceIdIndex 16
#define taskIsExceptionIndex 17
#define taskDescriptionIndex 18
#define taskStatusFlagIndex 19
@interface NSArray (SOGoEventComparison)

View File

@ -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];
}
}

View File

@ -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"];

View File

@ -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>