diff --git a/NEWS b/NEWS index 5ddc441e4..f9aa9db55 100644 --- a/NEWS +++ b/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) diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 4e6711fb8..8ea61da6e 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -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]; diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index f31ba3c21..cd15fa38a 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -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; } diff --git a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m index f804c8819..7065dbb1e 100644 --- a/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalRepeatableEntityObject+SOGo.m @@ -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 diff --git a/UI/Scheduler/NSArray+Scheduler.h b/UI/Scheduler/NSArray+Scheduler.h index b641e48c8..10048f0ef 100644 --- a/UI/Scheduler/NSArray+Scheduler.h +++ b/UI/Scheduler/NSArray+Scheduler.h @@ -68,6 +68,7 @@ #define taskRecurrenceIdIndex 16 #define taskIsExceptionIndex 17 #define taskDescriptionIndex 18 +#define taskStatusFlagIndex 19 @interface NSArray (SOGoEventComparison) diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 07791e3ba..2cbe8dbed 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -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]; } } diff --git a/UI/Scheduler/UIxTaskEditor.m b/UI/Scheduler/UIxTaskEditor.m index e4bbad5c9..fe06b714c 100644 --- a/UI/Scheduler/UIxTaskEditor.m +++ b/UI/Scheduler/UIxTaskEditor.m @@ -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 #import #import +#import #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"]; diff --git a/UI/Templates/SchedulerUI/UIxTaskViewTemplate.wox b/UI/Templates/SchedulerUI/UIxTaskViewTemplate.wox index 488d6ae70..a2e11c19d 100644 --- a/UI/Templates/SchedulerUI/UIxTaskViewTemplate.wox +++ b/UI/Templates/SchedulerUI/UIxTaskViewTemplate.wox @@ -104,38 +104,68 @@ - + + + + - - - arrow_drop_down - - - - - - - - - - - - - -
+ + + + + arrow_drop_down + + + + + repeat_one + + + + + repeat + + + + +
+ + + arrow_drop_down + + + + + repeat_one + + + + + repeat + + + + +