sogo/UI/Scheduler/UIxCalListingActions.m

1747 lines
69 KiB
Matlab
Raw Permalink Normal View History

/* UIxCalListingActions.m - this file is part of SOGo
*
* Copyright (C) 2006-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
* 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,
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSDictionary.h>
#import <Foundation/NSNull.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSUserDefaults.h> /* for locale string constants */
#import <Foundation/NSValue.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
#import <NGCards/iCalPerson.h>
2014-05-08 23:17:05 +02:00
#import <NGCards/iCalCalendar.h>
#import <NGExtensions/NSCalendarDate+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <SOPE/NGCards/iCalRecurrenceRule.h>
#import <SOGo/SOGoDateFormatter.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSObject+Utilities.h>
#import <SOGo/WOResourceManager+SOGo.h>
2017-05-19 18:13:01 +02:00
#import <Appointments/iCalToDo+SOGo.h>
#import <Appointments/SOGoAppointmentFolders.h>
#import <Appointments/SOGoWebAppointmentFolder.h>
#import "NSArray+Scheduler.h"
#import "UIxCalListingActions.h"
static NSArray *eventsFields = nil;
static NSArray *tasksFields = nil;
#define dayLength 86400
#define quarterLength 900 // number of seconds in 15 minutes
#define offsetHours (24 * 5) // number of hours in invitation window
#define offsetSeconds (offsetHours * 60 * 60) // number of seconds in
// invitation window
/* 1 block = 15 minutes */
#define offsetBlocks (offsetHours * 4) // number of 15-minute blocks in invitation window
#define maxBlocks (offsetBlocks * 2) // maximum number of blocks to search
// for a free slot (10 days)
2014-06-20 19:16:55 +02:00
@class SOGoAppointment;
@implementation UIxCalListingActions
+ (void) initialize
{
2015-07-09 22:18:03 +02:00
// NOTE: Modifying one of those two static arrays requires to modify NSArray+Scheduler.h
if (!eventsFields)
2014-05-08 23:17:05 +02:00
{
eventsFields = [NSArray arrayWithObjects: @"c_name", @"c_folder",
@"calendarName",
@"c_status", @"c_isopaque", @"c_title", @"c_startdate", @"startHour",
2014-05-08 23:17:05 +02:00
@"c_enddate", @"c_location", @"c_isallday",
@"c_classification", @"c_category", @"c_priority",
2014-05-08 23:17:05 +02:00
@"c_partmails", @"c_partstates", @"c_owner",
@"c_iscycle", @"c_nextalarm",
@"c_recurrence_id", @"isException", @"viewable", @"editable",
@"erasable", @"ownerIsOrganizer", @"c_description", nil];
2014-05-08 23:17:05 +02:00
[eventsFields retain];
}
if (!tasksFields)
2014-05-08 23:17:05 +02:00
{
tasksFields = [NSArray arrayWithObjects: @"c_name", @"c_folder",
@"calendarName",
@"c_status", @"c_title", @"c_startdate", @"c_enddate",
2014-05-08 23:17:05 +02:00
@"c_classification", @"c_location", @"c_category",
@"viewable", @"editable", @"erasable",
@"c_priority", @"c_owner",
@"c_iscycle", @"c_nextalarm",
@"c_recurrence_id", @"isException", @"c_description", nil];
2014-05-08 23:17:05 +02:00
[tasksFields retain];
}
}
- (id) initWithRequest: (WORequest *) newRequest
{
SOGoUser *user;
2017-09-08 19:24:58 +02:00
if ((self = [super initWithRequest: newRequest]))
2014-05-08 23:17:05 +02:00
{
componentsData = [NSMutableDictionary new];
startDate = nil;
endDate = nil;
ASSIGN (now, [NSCalendarDate calendarDate]);
2014-05-08 23:17:05 +02:00
ASSIGN (request, newRequest);
user = [[self context] activeUser];
ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
ASSIGN (userTimeZone, [[user userDefaults] timeZone]);
2017-08-02 22:51:02 +02:00
[now setTimeZone: userTimeZone];
2014-05-08 23:17:05 +02:00
dayBasedView = NO;
2014-06-20 19:16:55 +02:00
currentView = nil;
ASSIGN (enabledWeekDays, [[user userDefaults] calendarWeekdays]);
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
return self;
}
- (void) dealloc
{
[dateFormatter release];
[request release];
[componentsData release];
[userTimeZone release];
[enabledWeekDays release];
[super dealloc];
}
- (void) _setupDatesWithPopup: (NSString *) popupValue
2014-05-08 23:17:05 +02:00
andUserTZ: (NSTimeZone *) userTZ
{
NSCalendarDate *newDate;
NSString *param;
2017-09-08 19:24:58 +02:00
if ([popupValue isEqualToString: @"view_today"])
2014-05-08 23:17:05 +02:00
{
newDate = [NSCalendarDate calendarDate];
[newDate setTimeZone: userTZ];
startDate = [newDate beginOfDay];
endDate = [newDate endOfDay];
}
else if ([popupValue isEqualToString: @"view_all"])
2014-05-08 23:17:05 +02:00
{
startDate = nil;
endDate = nil;
}
else if ([popupValue isEqualToString: @"view_next7"])
2014-05-08 23:17:05 +02:00
{
2017-08-02 22:51:02 +02:00
startDate = [NSCalendarDate calendarDate];
[startDate setTimeZone: userTZ];
2014-05-08 23:17:05 +02:00
endDate = [[startDate dateByAddingYears: 0 months: 0 days: 6] endOfDay];
}
else if ([popupValue isEqualToString: @"view_next14"])
2014-05-08 23:17:05 +02:00
{
2017-08-02 22:51:02 +02:00
startDate = [NSCalendarDate calendarDate];
[startDate setTimeZone: userTZ];
2014-05-08 23:17:05 +02:00
endDate = [[startDate dateByAddingYears: 0 months: 0 days: 13] endOfDay];
}
else if ([popupValue isEqualToString: @"view_next31"])
2014-05-08 23:17:05 +02:00
{
2017-08-02 22:51:02 +02:00
startDate = [NSCalendarDate calendarDate];
[startDate setTimeZone: userTZ];
2014-05-08 23:17:05 +02:00
endDate = [[startDate dateByAddingYears: 0 months: 0 days: 30] endOfDay];
}
else if ([popupValue isEqualToString: @"view_thismonth"])
{
newDate = [NSCalendarDate calendarDate];
[newDate setTimeZone: userTZ];
startDate = [[newDate firstDayOfMonth] beginOfDay];
endDate = [[newDate lastDayOfMonth] endOfDay];
}
else if ([popupValue isEqualToString: @"view_thisyear"])
2014-05-08 23:17:05 +02:00
{
newDate = [NSCalendarDate dateWithYear: [[NSCalendarDate calendarDate] yearOfCommonEra]
month: 1
day: 1
hour: 0 minute: 0 second: 0
timeZone: userTZ];
2014-05-08 23:17:05 +02:00
startDate = [[newDate firstDayOfMonth] beginOfDay];
newDate = [NSCalendarDate dateWithYear: [[NSCalendarDate calendarDate] yearOfCommonEra]
month: 12
day: 31
hour: 0 minute: 0 second: 0
timeZone: userTZ];
2014-05-08 23:17:05 +02:00
endDate = [[newDate lastDayOfMonth] endOfDay];
}
else if ([popupValue isEqualToString: @"view_future"])
2014-05-08 23:17:05 +02:00
{
2017-08-02 22:51:02 +02:00
startDate = [NSCalendarDate calendarDate];
[startDate setTimeZone: userTZ];
2014-05-08 23:17:05 +02:00
endDate = nil;
}
else if ([popupValue isEqualToString: @"view_selectedday"])
{
param = [request formValueForKey: @"day"];
if ([param length] > 0)
startDate = [[NSCalendarDate dateFromShortDateString: param
andShortTimeString: nil
inTimeZone: userTZ] beginOfDay];
else
{
newDate = [NSCalendarDate calendarDate];
[newDate setTimeZone: userTZ];
startDate = [newDate beginOfDay];
}
2014-05-08 23:17:05 +02:00
endDate = [startDate endOfDay];
}
}
- (void) _setupContext
{
SOGoUser *user;
NSString *param;
2017-09-08 19:24:58 +02:00
user = [context activeUser];
userLogin = [user login];
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
criteria = [request formValueForKey: @"search"];
value = [request formValueForKey: @"value"];
param = [request formValueForKey: @"filterpopup"];
if ([param length])
{
[self _setupDatesWithPopup: param andUserTZ: userTimeZone];
}
else
{
param = [request formValueForKey: @"sd"];
if ([param length] > 0)
startDate = [[NSCalendarDate dateFromShortDateString: param
andShortTimeString: nil
inTimeZone: userTimeZone] beginOfDay];
else
startDate = nil;
2017-09-08 19:24:58 +02:00
param = [request formValueForKey: @"ed"];
if ([param length] > 0)
endDate = [[NSCalendarDate dateFromShortDateString: param
andShortTimeString: nil
inTimeZone: userTimeZone] endOfDay];
else
endDate = nil;
2017-09-08 19:24:58 +02:00
param = [request formValueForKey: @"view"];
currentView = param;
dayBasedView = ![param isEqualToString: @"monthview"];
}
}
- (void) _fixComponentTitle: (NSMutableDictionary *) component
withType: (NSString *) type
{
NSString *labelKey;
2017-09-08 19:24:58 +02:00
labelKey = [NSString stringWithFormat: @"%@_class%@",
2014-05-08 23:17:05 +02:00
type, [component objectForKey: @"c_classification"]];
2015-08-19 16:39:10 +02:00
[component setObject: [self labelForKey: labelKey inContext: context]
2014-05-08 23:17:05 +02:00
forKey: @"c_title"];
}
/*
* Adjust the event start and end dates when there's a time change
* in the period covering the view for the user's timezone.
* @param theRecord the attributes of the event.
*/
- (void) _fixDates: (NSMutableDictionary *) theRecord
{
NSCalendarDate *aDate;
NSNumber *aDateValue;
NSString *aDateField;
int daylightOffset;
unsigned int count;
static NSString *fields[] = { @"startDate", @"c_startdate", @"endDate", @"c_enddate" };
2017-09-08 19:24:58 +02:00
/* WARNING: This condition has been put and removed many times, please leave
2014-05-08 23:17:05 +02:00
it. Here is the story...
If _fixDates: is conditional to dayBasedView, the recurrences are computed
properly but the display time is wrong.
If _fixDates: is non-conditional, the reverse occurs.
If only this part of _fixDates: is conditional, both are right.
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
Regarding all day events, we need to execute this code no matter what the
date and the view are, otherwise the event will span on two days.
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
ref bugs:
http://www.sogo.nu/bugs/view.php?id=909
http://www.sogo.nu/bugs/view.php?id=678
...
*/
2017-09-08 19:24:58 +02:00
//NSLog(@"***[UIxCalListingActions _fixDates:] %@", [theRecord objectForKey: @"c_title"]);
if (currentView && (dayBasedView || [[theRecord objectForKey: @"c_isallday"] boolValue]))
2014-05-08 23:17:05 +02:00
{
for (count = 0; count < 2; count++)
{
2014-05-08 23:17:05 +02:00
aDateField = fields[count * 2];
aDate = [theRecord objectForKey: aDateField];
daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate]
- [userTimeZone secondsFromGMTForDate: startDate]);
//NSLog(@"***[UIxCalListingActions _fixDates:] %@ = %@ (%i)", aDateField, aDate, daylightOffset);
if (daylightOffset)
{
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
minutes: 0 seconds: daylightOffset];
[theRecord setObject: aDate forKey: aDateField];
aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]];
[theRecord setObject: aDateValue forKey: fields[count * 2 + 1]];
}
}
2014-05-08 23:17:05 +02:00
}
}
/**
* Split participants information into arrays and return the string representation of
* their states (0: needs-action, 1: accepted, etc).
*
* @see [iCalPerson descriptionForParticipationStatus:]
*/
- (void) _fixParticipants: (NSMutableDictionary *) theRecord
{
NSArray *mails, *states;
NSMutableArray *statesDescription;
NSString *description;
iCalPersonPartStat stat;
NSUInteger count, max;
states = nil;
if ([[theRecord objectForKey: @"c_partmails"] length] > 0)
{
mails = [[theRecord objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
// We add some robustness here, in case the quick table had c_partmails defined but
// no corresponding c_partstates entries. This can happen if folks toy around the database
// or if Funambol was used in the past, with a broken connector.
if ([[theRecord objectForKey: @"c_partstates"] length] > 0)
states = [[theRecord objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
max = [mails count];
statesDescription = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
stat = (states ? [[states objectAtIndex: count] intValue] : 0);
description = [iCalPerson descriptionForParticipationStatus: stat];
if (description == nil) description = @"";
[statesDescription addObject: [description lowercaseString]];
}
if (max > 0)
{
[theRecord setObject: mails forKey: @"c_partmails"];
[theRecord setObject: statesDescription forKey: @"c_partstates"];
}
}
}
- (NSArray *) _fetchFields: (NSArray *) fields
2014-05-08 23:17:05 +02:00
forComponentOfType: (NSString *) component
{
NSCalendarDate *currentStartDate, *currentEndDate;
NSEnumerator *folders, *currentInfos;
NSMutableDictionary *newInfo;
NSMutableArray *infos, *quickInfos, *allInfos, *quickInfosName;
NSNull *marker;
NSString *owner, *role, *calendarName, *iCalString, *recurrenceTime, *categories, *weekDay;
2014-05-08 23:17:05 +02:00
NSRange match;
iCalCalendar *calendar;
iCalEntityObject *master;
2014-05-08 23:17:05 +02:00
SOGoAppointmentFolder *currentFolder;
SOGoAppointmentFolders *clientObject;
SOGoUser *ownerUser;
2017-09-08 19:24:58 +02:00
BOOL isErasable, folderIsRemote, quickInfosFlag, searchByTitleOrContent;
int i;
2017-09-08 19:24:58 +02:00
infos = [NSMutableArray array];
marker = [NSNull null];
clientObject = [self clientObject];
quickInfosFlag = searchByTitleOrContent = NO;
folders = [[clientObject subFolders] objectEnumerator];
while ((currentFolder = [folders nextObject]))
{
if ([currentFolder isActive])
{
folderIsRemote = [currentFolder isKindOfClass: [SOGoWebAppointmentFolder class]];
2017-09-08 19:24:58 +02:00
//
// criteria can have the following values: "title", "title_Category_Location" and "entireContent"
//
if ([criteria isEqualToString:@"title_Category_Location"])
{
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
to: endDate
title: value
component: component
additionalFilters: criteria] objectEnumerator];
searchByTitleOrContent = ([value length] > 0);
}
else if ([criteria isEqualToString:@"entireContent"])
{
// First search : Through the quick table inside the location, category and title columns
2015-11-04 00:37:34 +01:00
quickInfos = (NSMutableArray *)[currentFolder fetchCoreInfosFrom: startDate
to: endDate
title: value
component: component
additionalFilters: criteria];
searchByTitleOrContent = ([value length] > 0);
2017-09-08 19:24:58 +02:00
// 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"]];
}
2017-09-08 19:24:58 +02:00
// Second research : Every objects except for those already in the quickInfos array
2015-11-04 00:37:34 +01:00
allInfos = (NSMutableArray *)[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 = (iCalEntityObject *)[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]];
}
}
}
}
2017-09-08 19:24:58 +02:00
currentInfos = [quickInfos objectEnumerator];
} // else if ([criteria isEqualToString:@"entireContent"])
else
{
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
to: endDate
title: value
component: component]
objectEnumerator];
}
2017-09-08 19:24:58 +02:00
owner = [currentFolder ownerInContext: context];
ownerUser = [SOGoUser userWithLogin: owner];
isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]);
while ((newInfo = [currentInfos nextObject]))
{
if ([enabledWeekDays count] &&
[newInfo objectForKey: @"startDate"])
{
currentStartDate = [newInfo objectForKey: @"startDate"];
currentEndDate = [newInfo objectForKey: @"endDate"];
if (!currentEndDate || [currentStartDate isDateOnSameDay: currentEndDate])
{
weekDay = iCalWeekDayString[[[newInfo objectForKey: @"startDate"] dayOfWeek]];
if (![enabledWeekDays containsObject: weekDay])
// Skip components that appear on disabled weekdays
continue;
}
}
if ([fields containsObject: @"viewable"])
{
if ([owner isEqualToString: userLogin])
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
else
{
role = [currentFolder roleForComponentsWithAccessClass: [[newInfo objectForKey: @"c_classification"] intValue]
forUser : userLogin];
if ([role isEqualToString: @"ComponentDAndTViewer"])
{
// We skip results that could lead to information "exposure".
// See http://sogo.nu/bugs/view.php?id=3619
if (searchByTitleOrContent)
continue;
[newInfo setObject: [NSNumber numberWithInt: 0] forKey: @"viewable"];
}
else
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
}
}
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"];
}
}
2014-05-08 23:17:05 +02:00
if ([fields containsObject: @"ownerIsOrganizer"])
{
// Identifies whether the owner is the organizer of this event.
NSString *c_orgmail;
c_orgmail = [newInfo objectForKey: @"c_orgmail"];
2017-09-08 19:24:58 +02:00
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"];
}
[newInfo setObject: [NSNumber numberWithBool: isErasable]
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"];
// Split comma-delimited categories
categories = [newInfo objectForKey: @"c_category"];
if ([categories length])
[newInfo setObject: [categories componentsSeparatedByString: @","] forKey: @"c_category"];
// Fix empty title
if (![[newInfo objectForKey: @"c_title"] length])
[self _fixComponentTitle: newInfo withType: component];
// Add prefix to recurrence id
if ((recurrenceTime = [newInfo objectForKey: @"c_recurrence_id"]))
[newInfo setObject: [NSString stringWithFormat: @"occurence%@", recurrenceTime]
forKey: @"c_recurrence_id"];
// Add the formatted starting hour
[self _addStartHour: newInfo];
// Possible improvement: only call _fixDates if event is recurrent
// or the view range span a daylight saving time change
[self _fixDates: newInfo];
// Split linefeed-delimited list of participants emails and statuses
if ([fields containsObject: @"c_partmails"])
[self _fixParticipants: newInfo];
[infos addObject: [newInfo objectsForKeys: fields
notFoundMarker: marker]];
}
}
}
2015-05-13 20:52:10 +02:00
[infos sortUsingSelector: @selector(compareEventsStartDateAscending:)];
return infos;
}
2014-06-27 23:00:32 +02:00
- (WOResponse *) _responseWithData: (id) data
{
return [self responseWithStatus: 200 andJSONRepresentation: data];
}
- (NSString *) _formattedDateForSeconds: (unsigned int) seconds
2014-05-08 23:17:05 +02:00
forAllDay: (BOOL) forAllDay
{
NSCalendarDate *date;
NSUInteger delta;
date = [NSCalendarDate dateWithTimeIntervalSince1970: seconds];
// Adjust for daylight saving time? (wrt to startDate)
//NSLog(@"***[UIxCalListingActions _formattedDateForSeconds] user timezone is %@", userTimeZone);
[date setTimeZone: userTimeZone];
if ([now dayOfCommonEra] == [date dayOfCommonEra])
{
// Same day
if (forAllDay)
2015-08-19 16:39:10 +02:00
return [self labelForKey: @"Today" inContext: context];
else
return [dateFormatter formattedTime: date];
}
else if ([now dayOfCommonEra] - [date dayOfCommonEra] == 1)
{
// Yesterday
2015-08-19 16:39:10 +02:00
return [self labelForKey: @"Yesterday" inContext: context];
}
else if ([date dayOfCommonEra] - [now dayOfCommonEra] == 1)
{
// Tomorrow
2015-08-19 16:39:10 +02:00
return [self labelForKey: @"Tomorrow" inContext: context];
}
else if (abs(delta = [date dayOfCommonEra] - [now dayOfCommonEra]) < 7)
{
NSString *dayOfWeek = [[locale objectForKey: NSWeekDayNameArray] objectAtIndex: [date dayOfWeek]];
if (delta < 7)
// Wihtin the next 7 days
return dayOfWeek;
else
// With the past 7 days
return [NSString stringWithFormat: [self labelForKey: @"Last %@" inContext: context], dayOfWeek];
}
else
{
return [dateFormatter shortFormattedDate: date];
}
}
/**
* @api {get} /so/:username/Calendar/alarmslist Get alarms
* @apiVersion 1.0.0
* @apiName GetAlarmsList
* @apiGroup Calendar
* @apiExample {curl} Example usage:
* curl -i http://localhost/SOGo/so/sogo1/Calendar/alarmslist?browserTime=1286668800
* @apiDescription Called when each module is loaded or whenever a calendar component is created, modified, deleted
* or whenever there's a {un}subscribe to a calendar.
*
* Workflow :
*
* - 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
*
* @apiParam {Number} [browserTime] Epoch time of browser
*
* @apiSuccess (Success 200) {String[]} fields List of fields for each event definition
* @apiSuccess (Success 200) {String[]} alarms List of events
* @apiSuccess (Success 200) {String} alarms.c_folder Calendar ID
* @apiSuccess (Success 200) {String} alarms.c_name Event UID
* @apiSuccess (Success 200) {String} alarms.c_nextalarm Epoch time of event's next alarm
*/
- (WOResponse *) alarmsListAction
{
SOGoAppointmentFolder *currentFolder;
SOGoAppointmentFolders *clientObject;
NSMutableArray *allAlarms;
NSDictionary *data;
NSEnumerator *folders;
unsigned int browserTime, laterTime;
// We look for alarms in the next 48 hours
browserTime = [[[context request] formValueForKey: @"browserTime"] intValue];
laterTime = browserTime + 60*60*48;
clientObject = [self clientObject];
allAlarms = [NSMutableArray array];
2017-09-08 19:24:58 +02:00
folders = [[clientObject subFolders] objectEnumerator];
while ((currentFolder = [folders nextObject]))
{
if ([currentFolder isActive] && [currentFolder showCalendarAlarms])
2014-05-08 23:17:05 +02:00
{
NSDictionary *entry;
NSArray *alarms;
int i;
2017-09-08 19:24:58 +02:00
alarms = [currentFolder fetchAlarmInfosFrom: [NSNumber numberWithInt: browserTime]
to: [NSNumber numberWithInt: laterTime]];
2017-09-08 19:24:58 +02:00
for (i = 0; i < [alarms count]; i++)
{
entry = [alarms objectAtIndex: i];
2017-09-08 19:24:58 +02:00
[allAlarms addObject: [NSArray arrayWithObjects:
[currentFolder nameInContainer],
[entry objectForKey: @"c_name"],
[entry objectForKey: @"c_nextalarm"],
nil]];
}
2014-05-08 23:17:05 +02:00
}
}
data = [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects: @"c_folder", @"c_name", @"c_nextalarm", nil], @"fields",
allAlarms, @"alarms", nil];
2017-09-08 19:24:58 +02:00
return [self _responseWithData: data];
}
- (void) saveFilterValue: (NSString *) submodule
{
NSString *filter;
SOGoUserSettings *us;
NSMutableDictionary *calendarSettings;
2017-09-08 19:24:58 +02:00
filter = [[context request] formValueForKey: @"filterpopup"];
if ([filter length]
&& ![filter isEqualToString: @"view_all"]
&& ![filter isEqualToString: @"view_future"])
2014-05-08 23:17:05 +02:00
{
us = [[context activeUser] userSettings];
calendarSettings = [us objectForKey: @"Calendar"];
// Must create if it doesn't exist
if (!calendarSettings)
{
calendarSettings = [NSMutableDictionary dictionary];
[us setObject: calendarSettings forKey: @"Calendar"];
}
//[us setObject: filter forKey: submodule];
[calendarSettings setObject: filter forKey: submodule];
2014-05-08 23:17:05 +02:00
[us synchronize];
}
}
- (void) saveSortValue: (NSString *) submodule
{
NSString *sort, *ascending;
SOGoUserSettings *us;
NSMutableDictionary *calendarSettings;
2017-09-08 19:24:58 +02:00
sort = [[context request] formValueForKey: @"sort"];
ascending = [[context request] formValueForKey: @"asc"];
if ([sort length])
{
us = [[context activeUser] userSettings];
calendarSettings = [us objectForKey: @"Calendar"];
// Must create if it doesn't exist
if (!calendarSettings)
{
calendarSettings = [NSMutableDictionary dictionary];
[us setObject: calendarSettings forKey: @"Calendar"];
}
[calendarSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", [ascending intValue]], nil]
forKey: submodule];
[us synchronize];
}
}
2017-09-08 19:24:58 +02:00
static inline NSString* _userStateInEvent (NSArray *event)
{
unsigned int count, max;
NSString *state;
NSArray *participants, *states;
SOGoUser *user;
participants = nil;
state = nil;
participants = [event objectAtIndex: eventPartMailsIndex];
// We guard ourself against bogus value coming from the quick tables
if ([participants isKindOfClass: [NSArray class]])
{
states = [event objectAtIndex: eventPartStatesIndex];
count = 0;
max = [participants count];
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
roles: nil];
while (state == nil && count < max)
{
if ([user hasEmail: [participants objectAtIndex: count]])
state = [states objectAtIndex: count];
else
count++;
}
}
return state;
}
/**
* @api {get} /so/:username/Calendar/eventslist List events
* @apiVersion 1.0.0
* @apiName GetEventsList
* @apiGroup Calendar
* @apiExample {curl} Example usage:
* curl -i http://localhost/SOGo/so/sogo1/Calendar/eventslist?day=20141201\&filterpopup=view_selectedday
*
* @apiParam {Boolean} [asc] Descending sort when false. Defaults to true (ascending).
* @apiParam {String} [sort] Sort field. Either title, start, end, location, or calendarName.
* @apiParam {Number} [day] Selected day (YYYYMMDD)
* @apiParam {String} [filterpopup] Time period. Either view_today, view_next7, view_next14, view_next31, view_thismonth, view_thisyear, view_future, view_selectedday, or view_all
* @apiParam {String} [search] Search field criteria. Either title_Category_Location or entireContent.
* @apiParam {String} [value] String to match
*
2017-09-08 19:24:58 +02:00
* @apiSuccess (Success 200) {String[]} fields List of fields for each event definition
* @apiSuccess (Success 200) {String} events.<month>.month Full month and year
* @apiSuccess (Success 200) {Object} events.<month>.days List of days
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.month Abbreviated month name
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.monthDay Day of month
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.weekDay Abbreviated weeday name
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events List of events
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_name Event UID
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_folder Calendar ID
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.calendarName Human readable name of calendar
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_status 0: Cancelled, 1: Normal, 2: Tentative
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_isopaque 1 if event is opaque (not transparent)
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_title Title
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_startdate Epoch time of start date
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_enddate Epoch time of end date
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_location Event's location
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_isallday 1 if event lasts all day
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_classification 0: Public, 1: Private, 2: Confidential
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_category Category
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_priority Priority (0 to 9)
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events.c_partmails Participants email addresses
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events.c_partstates Participants states
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_owner Event's owner
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_iscycle 1 if the event is cyclic/recurring
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_nextalarm Epoch time of next alarm
* @apiSuccess (Success 200) {String} [events.<month>.days.<date>.events.c_recurrence_id] Recurrence ID if event is cyclic
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.isException 1 if recurrence is an exception
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.viewable 1 if active user can view the event
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.editable 1 if active user can edit the event
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.erasable 1 if active user can erase the event
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.ownerIsOrganizer 1 if owner is also the organizer
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.formatted_startdate Localized start date
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.formatted_enddate Localized end date
*/
- (WOResponse *) eventsListAction
{
BOOL isAllDay;
NSArray *oldEvent;
NSCalendarDate *date;
NSDictionary *data;
NSEnumerator *events;
2017-09-08 19:24:58 +02:00
NSMutableArray *fields, *dayEvents, *newEvent, *allDayEvents;
NSMutableDictionary *allEvents, *monthData, *monthEvents, *dayData;
NSString *sort, *ascending, *day, *weekDay, *month, *userState;
unsigned int interval, count, max;
SEL sortSelector;
[self _setupContext];
[self saveFilterValue: @"EventsFilterState"];
[self saveSortValue: @"EventsSortingState"];
2017-09-08 19:24:58 +02:00
allEvents = [NSMutableDictionary dictionary];
allDayEvents = [NSMutableArray array];
events = [[self _fetchFields: eventsFields
2014-05-08 23:17:05 +02:00
forComponentOfType: @"vevent"] objectEnumerator];
while ((oldEvent = [events nextObject]))
2014-05-08 23:17:05 +02:00
{
interval = [[oldEvent objectAtIndex: eventStartDateIndex] intValue];
date = [NSCalendarDate dateWithTimeIntervalSince1970: interval];
[date setTimeZone: userTimeZone];
// Skip components that appear on disabled weekdays
weekDay = iCalWeekDayString[[date dayOfWeek]];
if ([enabledWeekDays count] && ![enabledWeekDays containsObject: weekDay])
continue;
2017-09-08 19:24:58 +02:00
month = [date descriptionWithCalendarFormat: @"%Y%m" locale: locale];
day = [date shortDateString];
if (!(monthData = [allEvents objectForKey: month]))
{
monthEvents = [NSMutableDictionary dictionary];
monthData = [NSMutableDictionary
dictionaryWithObjectsAndKeys:
[date descriptionWithCalendarFormat: @"%B %Y" locale: locale], @"month",
monthEvents, @"days",
nil];
[allEvents setObject: monthData forKey: month];
}
monthEvents = [monthData objectForKey: @"days"];
if (!(dayData = [monthEvents objectForKey: day]))
{
dayEvents = [NSMutableArray array];
[allDayEvents addObject: dayEvents];
dayData = [NSMutableDictionary
dictionaryWithObjectsAndKeys:
[date descriptionWithCalendarFormat: @"%b" locale: locale], @"month",
[date descriptionWithCalendarFormat: @"%e" locale: locale], @"monthDay",
[date descriptionWithCalendarFormat: @"%a" locale: locale], @"weekDay",
dayEvents, @"events",
nil];
[monthEvents setObject: dayData forKey: day];
}
dayEvents = [dayData objectForKey: @"events"];
2014-05-08 23:17:05 +02:00
newEvent = [NSMutableArray arrayWithArray: oldEvent];
isAllDay = [[oldEvent objectAtIndex: eventIsAllDayIndex] boolValue];
[newEvent addObject: [self _formattedDateForSeconds: interval
forAllDay: isAllDay]];
interval = [[oldEvent objectAtIndex: eventEndDateIndex] intValue];
[newEvent addObject: [self _formattedDateForSeconds: interval
forAllDay: isAllDay]];
2017-09-08 19:24:58 +02:00
userState = _userStateInEvent(oldEvent);
if (userState != nil) [newEvent addObject: userState];
[dayEvents addObject: newEvent];
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
// Sort affects events of each day (but not the days)
sort = [[context request] formValueForKey: @"sort"];
if ([sort isEqualToString: @"title"])
2017-09-08 19:24:58 +02:00
sortSelector = @selector (compareEventsTitleAscending:);
else if ([sort isEqualToString: @"end"])
2017-09-08 19:24:58 +02:00
sortSelector = @selector (compareEventsEndDateAscending:);
else if ([sort isEqualToString: @"location"])
2017-09-08 19:24:58 +02:00
sortSelector = @selector (compareEventsLocationAscending:);
else if ([sort isEqualToString: @"calendarName"])
2017-09-08 19:24:58 +02:00
sortSelector = @selector (compareEventsCalendarNameAscending:);
else
2017-09-08 19:24:58 +02:00
sortSelector = @selector (compareEventsStartDateAscending:);
ascending = [[context request] formValueForKey: @"asc"];
2017-09-08 19:24:58 +02:00
max = [allDayEvents count];
for (count = 0; count < max; count++)
{
dayEvents = [allDayEvents objectAtIndex: count];
[dayEvents sortUsingSelector: sortSelector];
if (![ascending boolValue])
[dayEvents reverseArray];
}
// Fields names
fields = [NSMutableArray arrayWithArray: eventsFields];
[fields addObject: @"formatted_startdate"];
[fields addObject: @"formatted_enddate"];
2017-09-08 19:24:58 +02:00
[fields addObject: @"userState"];
2017-09-08 19:24:58 +02:00
data = [NSDictionary dictionaryWithObjectsAndKeys: fields, @"fields", allEvents, @"events", nil];
return [self _responseWithData: data];
}
2014-05-08 23:17:05 +02:00
static inline void _feedBlockWithDayBasedData (NSMutableDictionary *block, unsigned int start,
unsigned int end, unsigned int dayStart)
{
unsigned int delta, quarterStart, length, swap;
2017-09-08 19:24:58 +02:00
if (start > end)
2014-05-08 23:17:05 +02:00
{
swap = end;
end = start;
start = swap;
}
quarterStart = (start - dayStart) / quarterLength;
delta = end - dayStart;
if (delta > dayLength)
delta = dayLength;
if ((delta % quarterLength))
delta += quarterLength;
length = (delta / quarterLength) - quarterStart;
if (!length)
length = 1;
[block setObject: [NSNumber numberWithUnsignedInt: quarterStart]
2014-05-08 23:17:05 +02:00
forKey: @"start"];
[block setObject: [NSNumber numberWithUnsignedInt: length]
2014-05-08 23:17:05 +02:00
forKey: @"length"];
}
// static inline void _feedBlockWithMonthBasedData (NSMutableDictionary *block, unsigned int start,
// NSTimeZone *userTimeZone,
// SOGoDateFormatter *dateFormatter)
// {
// NSCalendarDate *eventStartDate;
// NSString *startHour;
2017-09-08 19:24:58 +02:00
// eventStartDate = [NSCalendarDate dateWithTimeIntervalSince1970: start];
// [eventStartDate setTimeZone: userTimeZone];
// startHour = [dateFormatter formattedTime: eventStartDate];
// [block setObject: startHour forKey: @"starthour"];
// [block setObject: [NSNumber numberWithUnsignedInt: start]
// forKey: @"start"];
// }
- (void) _addStartHour: (NSMutableDictionary *) theRecord
{
NSCalendarDate *eventStartDate;
NSString *startHour;
eventStartDate = [theRecord objectForKey: @"startDate"];
if (eventStartDate)
{
startHour = [dateFormatter formattedTime: eventStartDate];
[theRecord setObject: startHour forKey: @"startHour"];
}
}
- (NSMutableDictionary *) _eventBlockWithStart: (unsigned int) start
2014-05-08 23:17:05 +02:00
end: (unsigned int) end
number: (NSNumber *) number
onDay: (unsigned int) dayStart
recurrenceTime: (unsigned int) recurrenceTime
userState: (NSString *) userState
{
NSMutableDictionary *block;
2017-09-08 19:24:58 +02:00
block = [NSMutableDictionary dictionary];
2017-09-08 19:24:58 +02:00
if (dayBasedView)
_feedBlockWithDayBasedData (block, start, end, dayStart);
// else
// _feedBlockWithMonthBasedData (block, start, userTimeZone, dateFormatter);
[block setObject: number forKey: @"nbr"];
if (recurrenceTime)
[block setObject: [NSNumber numberWithInt: recurrenceTime]
2014-05-08 23:17:05 +02:00
forKey: @"recurrenceTime"];
if (userState != nil)
[block setObject: userState
2014-05-08 23:17:05 +02:00
forKey: @"userState"];
2017-09-08 19:24:58 +02:00
return block;
}
- (void) _fillBlocks: (NSArray *) blocks
2014-05-08 23:17:05 +02:00
withEvent: (NSArray *) event
withNumber: (NSNumber *) number
{
int currentDayStart, startSecs, endsSecs, currentStart, eventStart,
2014-05-08 23:17:05 +02:00
eventEnd, computedEventEnd, offset, recurrenceTime, swap;
NSMutableArray *currentDay;
NSMutableDictionary *eventBlock;
NSString *userState;
2017-09-08 19:24:58 +02:00
eventStart = [[event objectAtIndex: eventStartDateIndex] intValue];
if (eventStart < 0)
[self errorWithFormat: @"event '%@' has negative start: %d (skipped)",
2014-05-08 23:17:05 +02:00
[event objectAtIndex: eventNameIndex], eventStart];
else
2014-05-08 23:17:05 +02:00
{
eventEnd = [[event objectAtIndex: eventEndDateIndex] intValue];
if (eventEnd < 0)
[self errorWithFormat: @"event '%@' has negative end: %d (skipped)",
[event objectAtIndex: eventNameIndex], eventEnd];
else
{
2014-05-08 23:17:05 +02:00
if (eventEnd < eventStart)
{
swap = eventStart;
eventStart = eventEnd;
eventEnd = swap;
[self warnWithFormat: @"event '%@' has end < start: %d < %d",
[event objectAtIndex: eventNameIndex], eventEnd, eventStart];
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
startSecs = (unsigned int) [startDate timeIntervalSince1970];
endsSecs = (unsigned int) [endDate timeIntervalSince1970];
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
if ([[event objectAtIndex: eventIsCycleIndex] boolValue])
recurrenceTime = [[event objectAtIndex: eventRecurrenceIdIndex] unsignedIntValue];
else
recurrenceTime = 0;
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
currentStart = eventStart;
if (currentStart < startSecs)
{
currentStart = startSecs;
offset = 0;
}
else
offset = ((currentStart - startSecs) / dayLength);
2014-05-08 23:17:05 +02:00
if (offset >= [blocks count])
[self errorWithFormat: @"event '%@' has a computed offset that"
@" overflows the amount of blocks (skipped)",
[event objectAtIndex: eventNameIndex]];
else
2014-05-08 23:17:05 +02:00
{
currentDay = [blocks objectAtIndex: offset];
currentDayStart = startSecs + dayLength * offset;
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
if (eventEnd > endsSecs)
eventEnd = endsSecs;
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
if (eventEnd < startSecs)
// The event doesn't end in the covered period.
// This special case occurs with a DST change.
return;
2017-09-08 19:24:58 +02:00
userState = _userStateInEvent(event);
2014-05-08 23:17:05 +02:00
while (currentDayStart + dayLength < eventEnd)
{
2014-05-08 23:17:05 +02:00
eventBlock = [self _eventBlockWithStart: currentStart
end: currentDayStart + dayLength - 1
number: number
onDay: currentDayStart
recurrenceTime: recurrenceTime
userState: userState];
[currentDay addObject: eventBlock];
currentDayStart += dayLength;
currentStart = currentDayStart;
offset++;
currentDay = [blocks objectAtIndex: offset];
}
2017-09-08 19:24:58 +02:00
computedEventEnd = eventEnd;
2017-09-08 19:24:58 +02:00
// We add 5 mins to the end date of an event if the end date
// is equal or smaller than the event's start date.
if (eventEnd <= currentStart)
2014-05-08 23:17:05 +02:00
computedEventEnd = currentStart + (5*60);
2017-09-08 19:24:58 +02:00
eventBlock = [self _eventBlockWithStart: currentStart
2014-05-08 23:17:05 +02:00
end: computedEventEnd
number: number
onDay: currentDayStart
recurrenceTime: recurrenceTime
userState: userState];
[currentDay addObject: eventBlock];
}
}
2014-05-08 23:17:05 +02:00
}
}
- (void) _prepareEventBlocks: (NSMutableArray **) blocks
2014-05-08 23:17:05 +02:00
withAllDays: (NSMutableArray **) allDayBlocks
{
unsigned int count, nbrDays;
int seconds;
2017-09-08 19:24:58 +02:00
seconds = [endDate timeIntervalSinceDate: startDate];
if (seconds > 0)
2014-05-08 23:17:05 +02:00
{
nbrDays = 1 + (unsigned int) (seconds / dayLength);
*blocks = [NSMutableArray arrayWithCapacity: nbrDays];
*allDayBlocks = [NSMutableArray arrayWithCapacity: nbrDays];
for (count = 0; count < nbrDays; count++)
{
2014-05-08 23:17:05 +02:00
[*blocks addObject: [NSMutableArray array]];
[*allDayBlocks addObject: [NSMutableArray array]];
}
2014-05-08 23:17:05 +02:00
}
else
2014-05-08 23:17:05 +02:00
{
*blocks = nil;
*allDayBlocks = nil;
}
}
- (NSArray *) _horizontalBlocks: (NSMutableArray *) day
{
NSMutableArray *quarters[96];
NSMutableArray *currentBlock, *blocks;
NSDictionary *currentEvent;
unsigned int count, max, qCount, qMax, qOffset;
2017-09-08 19:24:58 +02:00
blocks = [NSMutableArray array];
2017-09-08 19:24:58 +02:00
bzero (quarters, 96 * sizeof (NSMutableArray *));
2017-09-08 19:24:58 +02:00
max = [day count];
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
currentEvent = [day objectAtIndex: count];
qMax = [[currentEvent objectForKey: @"length"] unsignedIntValue];
qOffset = [[currentEvent objectForKey: @"start"] unsignedIntValue];
for (qCount = 0; qCount < qMax; qCount++)
{
2014-05-08 23:17:05 +02:00
currentBlock = quarters[qCount + qOffset];
if (!currentBlock)
{
currentBlock = [NSMutableArray array];
quarters[qCount + qOffset] = currentBlock;
[blocks addObject: currentBlock];
}
2014-05-08 23:17:05 +02:00
[currentBlock addObject: currentEvent];
}
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
return blocks;
}
2014-05-08 23:17:05 +02:00
static inline unsigned int _computeMaxBlockSiblings (NSArray *block)
{
unsigned int count, max, maxSiblings, siblings;
NSNumber *nbrEvents;
2017-09-08 19:24:58 +02:00
max = [block count];
maxSiblings = max;
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
nbrEvents = [[block objectAtIndex: count] objectForKey: @"siblings"];
if (nbrEvents)
{
2014-05-08 23:17:05 +02:00
siblings = [nbrEvents unsignedIntValue];
if (siblings > maxSiblings)
maxSiblings = siblings;
}
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
return maxSiblings;
}
2014-05-08 23:17:05 +02:00
static inline void _propagateBlockSiblings (NSArray *block, NSNumber *maxSiblings)
{
unsigned int count, max;
NSMutableDictionary *event;
NSNumber *realSiblings;
2017-09-08 19:24:58 +02:00
max = [block count];
realSiblings = [NSNumber numberWithUnsignedInt: max];
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
event = [block objectAtIndex: count];
[event setObject: maxSiblings forKey: @"siblings"];
[event setObject: realSiblings forKey: @"realSiblings"];
}
}
/* this requires two vertical passes */
2014-05-08 23:17:05 +02:00
static inline void _computeBlocksSiblings (NSArray *blocks)
{
NSArray *currentBlock;
unsigned int count, max, maxSiblings;
2017-09-08 19:24:58 +02:00
max = [blocks count];
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
currentBlock = [blocks objectAtIndex: count];
maxSiblings = _computeMaxBlockSiblings (currentBlock);
_propagateBlockSiblings (currentBlock,
[NSNumber numberWithUnsignedInt: maxSiblings]);
}
}
2014-05-08 23:17:05 +02:00
static inline void _computeBlockPosition (NSArray *block)
{
unsigned int count, max, j, siblings;
NSNumber *position;
NSMutableDictionary *event;
NSMutableDictionary **positions;
2017-09-08 19:24:58 +02:00
max = [block count];
event = [block objectAtIndex: 0];
siblings = [[event objectForKey: @"siblings"] unsignedIntValue];
positions = NSZoneCalloc (NULL, siblings, sizeof (NSMutableDictionary *));
2017-09-08 19:24:58 +02:00
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
event = [block objectAtIndex: count];
position = [event objectForKey: @"position"];
if (position)
*(positions + [position unsignedIntValue]) = event;
else
{
2014-05-08 23:17:05 +02:00
j = 0;
while (j < max && *(positions + j))
j++;
*(positions + j) = event;
[event setObject: [NSNumber numberWithUnsignedInt: j]
forKey: @"position"];
}
2014-05-08 23:17:05 +02:00
}
2017-09-08 19:24:58 +02:00
NSZoneFree (NULL, positions);
}
// static inline void
// _addBlockMultipliers (NSArray *block, NSMutableDictionary **positions)
// {
// unsigned int count, max, limit, multiplier;
// NSMutableDictionary *currentEvent, *event;
// max = [block count];
// event = [block objectAtIndex: 0];
// limit = [[event objectForKey: @"siblings"] unsignedIntValue];
// if (max < limit)
// {
// currentEvent = nil;
// for (count = 0; count < limit; count++)
// {
// multiplier = 1;
// event = positions[count];
// if ([[event objectForKey: @"realSiblings"] unsignedIntValue]
// < limit)
// {
// if (event)
// {
// if (currentEvent && multiplier > 1)
// [currentEvent setObject: [NSNumber numberWithUnsignedInt: multiplier]
// forKey: @"multiplier"];
// currentEvent = event;
// multiplier = 1;
// }
// else
// multiplier++;
// }
// }
// }
// }
static inline void
_computeBlocksPosition (NSArray *blocks)
{
NSArray *block;
unsigned int count, max;
2014-05-08 23:17:05 +02:00
// NSMutableDictionary **positions;
2017-09-08 19:24:58 +02:00
max = [blocks count];
for (count = 0; count < max; count++)
2014-05-08 23:17:05 +02:00
{
block = [blocks objectAtIndex: count];
_computeBlockPosition (block);
// _addBlockMultipliers (block, positions);
// NSZoneFree (NULL, positions);
}
}
- (void) _addBlocksWidth: (NSMutableArray *) day
{
NSArray *blocks;
2017-09-08 19:24:58 +02:00
blocks = [self _horizontalBlocks: day];
_computeBlocksSiblings (blocks);
_computeBlocksSiblings (blocks);
_computeBlocksPosition (blocks);
/* ... _computeBlocksMultiplier() ... */
}
2014-06-27 23:21:18 +02:00
- (NSArray *) _selectedCalendars
{
2014-06-20 19:16:55 +02:00
SOGoAppointmentFolders *co;
SOGoAppointmentFolder *folder;
NSMutableArray *selectedCalendars;
NSArray *folders;
NSString *fUID, *fName;
2014-06-20 19:16:55 +02:00
NSNumber *isActive;
unsigned int count, foldersCount;
2017-09-08 19:24:58 +02:00
2014-06-20 19:16:55 +02:00
co = [self clientObject];
folders = [co subFolders];
foldersCount = [folders count];
selectedCalendars = [[NSMutableArray alloc] initWithCapacity: foldersCount];
for (count = 0; count < foldersCount; count++)
{
folder = [folders objectAtIndex: count];
isActive = [NSNumber numberWithBool: [folder isActive]];
if ([isActive intValue] != 0) {
2014-06-30 19:36:12 +02:00
fUID = [folder nameInContainer];
fName = [folder displayName];
[selectedCalendars addObject: [NSDictionary dictionaryWithObjectsAndKeys:
fUID, @"id",
fName, @"name",
nil]];
2014-06-20 19:16:55 +02:00
}
}
return selectedCalendars;
}
/**
* @api {get} /so/:username/Calendar/eventsblocks Get events blocks
* @apiVersion 1.0.0
* @apiName GetEventsBlocks
* @apiGroup Calendar
* @apiExample {curl} Example usage:
* curl -i http://localhost/SOGo/so/sogo1/Calendar/eventsblocks?day=20141201\&popupfilter=view_selectedday
*
* @apiParam {Number} [sd] Period start date (YYYYMMDD)
* @apiParam {Number} [ed] Period end date (YYYYMMDD)
* @apiParam {String} [view] Formatting view. Either dayview, multicolumndayview, weekview or monthview.
*
2017-09-08 19:24:58 +02:00
* @apiSuccess (Success 200) {Object[]} days
* @apiSuccess (Success 200) {String} days.date
* @apiSuccess (Success 200) {Number} days.number
* @apiSuccess (Success 200) {String[]} eventsFields List of fields for each event definition
* @apiSuccess (Success 200) {String[]} events List of events
* @apiSuccess (Success 200) {String} events.c_name Event UID
* @apiSuccess (Success 200) {String} events.c_folder Calendar ID
* @apiSuccess (Success 200) {String} events.calendarName Human readable name of calendar
* @apiSuccess (Success 200) {Number} events.c_status 0: Cancelled, 1: Normal, 2: Tentative
* @apiSuccess (Success 200) {String} events.c_title Title
* @apiSuccess (Success 200) {String} events.c_startdate Epoch time of start date
* @apiSuccess (Success 200) {String} events.c_enddate Epoch time of end date
* @apiSuccess (Success 200) {String} events.c_location Event's location
* @apiSuccess (Success 200) {Number} events.c_isallday 1 if event lasts all day
* @apiSuccess (Success 200) {Number} events.c_classification 0: Public, 1: Private, 2: Confidential
* @apiSuccess (Success 200) {String} events.c_category Category
* @apiSuccess (Success 200) {String[]} events.c_partmails Participants email addresses
* @apiSuccess (Success 200) {String[]} events.c_partstates Participants states
* @apiSuccess (Success 200) {String} events.c_owner Event's owner
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
* @apiSuccess (Success 200) {Object[]} blocks
* @apiSuccess (Success 200) {Number} blocks.nbr
* @apiSuccess (Success 200) {Number} [blocks.start] Day-based views only
* @apiSuccess (Success 200) {Number} [blocks.position] Day-based views only
* @apiSuccess (Success 200) {Number} [blocks.length] Day-based views only
* @apiSuccess (Success 200) {Number} [blocks.siblings] Day-based views only
* @apiSuccess (Success 200) {Number} [blocks.realSiblings] Day-based views only
* @apiSuccess (Success 200) {Object[]} allDayBlocks
* @apiSuccess (Success 200) {Number} allDayBlocks.nbr
* @apiSuccess (Success 200) {Number} allDayBlocks.start
* @apiSuccess (Success 200) {Number} allDayBlocks.length
*/
- (WOResponse *) eventsBlocksAction
{
int count, max;
NSArray *events, *event, *calendars;
NSCalendarDate *currentDate;
NSDictionary *eventsBlocks, *calendar;
NSMutableArray *allDayBlocks, *blocks, *days, *currentDay, *eventsForCalendar, *eventsByCalendars;
NSNumber *eventNbr;
NSString *calendarName, *calendarId;
BOOL isAllDay;
2014-06-20 19:16:55 +02:00
int i, j;
[self _setupContext];
2014-06-20 19:16:55 +02:00
events = [self _fetchFields: eventsFields forComponentOfType: @"vevent"];
2017-09-08 19:24:58 +02:00
2014-06-20 19:16:55 +02:00
if ([currentView isEqualToString: @"multicolumndayview"])
2014-05-08 23:17:05 +02:00
{
2015-11-04 00:38:47 +01:00
calendars = (NSMutableArray *)[self _selectedCalendars];
2014-06-20 19:16:55 +02:00
eventsByCalendars = [NSMutableArray arrayWithCapacity:[calendars count]];
for (i = 0; i < [calendars count]; i++) // For each calendar
{
calendar = [calendars objectAtIndex:i];
calendarName = [calendar objectForKey: @"name"];
calendarId = [calendar objectForKey: @"id"];
days = [NSMutableArray array];
2014-06-20 19:16:55 +02:00
eventsForCalendar = [NSMutableArray array];
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
for (j = 0; j < [events count]; j++) {
if ([[[events objectAtIndex:j] objectAtIndex:eventFolderIndex] isEqualToString: calendarId]) {
// Event is in current calendar
2014-06-20 19:16:55 +02:00
[eventsForCalendar addObject: [events objectAtIndex:j]];
}
}
eventsBlocks = [NSDictionary dictionaryWithObjectsAndKeys:
calendarId , @"id",
calendarName, @"calendarName",
eventsFields, @"eventsFields",
eventsForCalendar, @"events",
allDayBlocks, @"allDayBlocks",
blocks, @"blocks",
days, @"days", nil];
2014-06-20 19:16:55 +02:00
max = [eventsForCalendar count];
for (count = 0; count < max; count++)
{
event = [eventsForCalendar objectAtIndex: count];
eventNbr = [NSNumber numberWithUnsignedInt: count];
isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue];
if (dayBasedView && isAllDay)
[self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr];
else
[self _fillBlocks: blocks withEvent: event withNumber: eventNbr];
}
currentDate = [[startDate copy] autorelease];
[currentDate setTimeZone: userTimeZone];
2014-06-20 19:16:55 +02:00
max = [blocks count];
for (count = 0; count < max; count++)
{
currentDay = [blocks objectAtIndex: count];
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
[self _addBlocksWidth: currentDay];
[days addObject: [NSDictionary dictionaryWithObjectsAndKeys:
[currentDate shortDateString], @"date",
[NSNumber numberWithInt: count], @"number", nil]];
currentDate = [currentDate addYear:0 month:0 day:1 hour:0 minute:0 second:0];
2014-06-20 19:16:55 +02:00
}
[eventsByCalendars addObject: eventsBlocks];
2014-06-20 19:16:55 +02:00
}
return [self _responseWithData: eventsByCalendars];
2014-05-08 23:17:05 +02:00
}
2014-06-20 19:16:55 +02:00
else
2014-05-08 23:17:05 +02:00
{
days = [NSMutableArray array];
2014-06-20 19:16:55 +02:00
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
eventsBlocks = [NSDictionary dictionaryWithObjectsAndKeys:
eventsFields, @"eventsFields",
events, @"events",
allDayBlocks, @"allDayBlocks",
blocks, @"blocks",
days, @"days", nil];
2014-06-20 19:16:55 +02:00
max = [events count];
for (count = 0; count < max; count++)
{
event = [events objectAtIndex: count];
// NSLog(@"***[UIxCalListingActions eventsBlocksAction] %i = %@ : %@ / %@ / %@", count,
// [event objectAtIndex: eventTitleIndex],
// [event objectAtIndex: eventStartDateIndex],
// [event objectAtIndex: eventEndDateIndex],
// [event objectAtIndex: eventRecurrenceIdIndex]);
eventNbr = [NSNumber numberWithUnsignedInt: count];
isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue];
if (dayBasedView && isAllDay)
[self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr];
else
[self _fillBlocks: blocks withEvent: event withNumber: eventNbr];
}
currentDate = [[startDate copy] autorelease];
2016-05-06 20:28:10 +02:00
[currentDate setTimeZone: userTimeZone];
2014-06-20 19:16:55 +02:00
max = [blocks count];
for (count = 0; count < max; count++)
{
currentDay = [blocks objectAtIndex: count];
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
[self _addBlocksWidth: currentDay];
[days addObject: [NSDictionary dictionaryWithObjectsAndKeys:
[currentDate shortDateString], @"date",
[NSNumber numberWithInt: count], @"number", nil]];
currentDate = [currentDate addYear:0 month:0 day:1 hour:0 minute:0 second:0];
2014-06-20 19:16:55 +02:00
}
if ([enabledWeekDays count] > 0 && [enabledWeekDays count] < 7)
{
// Remove the days that are disabled in the user's defaults
int weekDay, weekDayCount;
weekDayCount= [currentDate dayOfWeek];
for (count = max - 1; count >= 0; count--)
{
weekDayCount--;
if (weekDayCount < 0)
weekDay = ((weekDayCount % 7) + 7) % 7;
else
weekDay = weekDayCount;
if (![enabledWeekDays containsObject: iCalWeekDayString[weekDay]])
{
[allDayBlocks removeObjectAtIndex: count];
[blocks removeObjectAtIndex: count];
[days removeObjectAtIndex: count];
}
}
}
return [self _responseWithData: [NSArray arrayWithObject: eventsBlocks]];
2014-05-08 23:17:05 +02:00
}
}
- (NSString *) _getStatusClassForStatusCode: (int) statusCode
2014-05-08 23:17:05 +02:00
andEndDateStamp: (unsigned int) endDateStamp
{
NSCalendarDate *taskDate;
NSString *statusClass;
2017-09-08 19:24:58 +02:00
if (statusCode == 1)
statusClass = @"completed";
else
2014-05-08 23:17:05 +02:00
{
if (endDateStamp)
{
taskDate = [NSCalendarDate dateWithTimeIntervalSince1970: endDateStamp];
2014-05-08 23:17:05 +02:00
[taskDate setTimeZone: userTimeZone];
if ([taskDate earlierDate: now] == taskDate)
statusClass = @"overdue";
else
2014-05-08 23:17:05 +02:00
{
if ([taskDate isToday])
statusClass = @"duetoday";
else
statusClass = @"duelater";
}
}
2014-05-08 23:17:05 +02:00
else
statusClass = @"noduedate";
}
2017-09-08 19:24:58 +02:00
return statusClass;
}
/**
* @api {get} /so/:username/Calendar/taskslist List tasks
* @apiVersion 1.0.0
* @apiName GetTasksList
* @apiGroup Calendar
* @apiExample {curl} Example usage:
* curl -i http://localhost/SOGo/so/sogo1/Calendar/taskslist?filterpopup=view_all
*
* @apiParam {Number} [show_completed] Show completed tasks when set to 1. Defaults to ignore completed tasks.
* @apiParam {Boolean} [asc] Descending sort when false. Defaults to true (ascending).
* @apiParam {Boolean} [sort] Sort field. Either title, priority, end, location, category, calendarname, or status.
* @apiParam {Number} [day] Selected day (YYYYMMDD)
* @apiParam {String} [filterpopup] Time period. Either view_today, view_next7, view_next14, view_next31, view_thismonth, view_thisyear, view_overdue, view_incomplete, view_not_started, or view_all
* @apiParam {String} [search] Search field criteria. Either title_Category_Location or entireContent.
*
* @apiSuccess (Success 200) {String[]} fields List of fields for each event definition
* @apiSuccess (Success 200) {String[]} tasks List of events
* @apiSuccess (Success 200) {String} tasks.c_name Todo UID
* @apiSuccess (Success 200) {String} tasks.c_folder Calendar ID
* @apiSuccess (Success 200) {String} tasks.calendarName Human readable name of calendar
* @apiSuccess (Success 200) {Number} tasks.c_status 0: Needs-action, 1: Completed, 2: In-process, 3: Cancelled
* @apiSuccess (Success 200) {String} tasks.c_title Title
* @apiSuccess (Success 200) {String} tasks.c_enddate End date (epoch time)
* @apiSuccess (Success 200) {Number} tasks.c_classification 0: Public, 1: Private, 2: Confidential
* @apiSuccess (Success 200) {String} tasks.c_location Location
* @apiSuccess (Success 200) {String} tasks.c_category Category
* @apiSuccess (Success 200) {Number} tasks.viewable 1 if task is viewable by the active user
* @apiSuccess (Success 200) {Number} tasks.editable 1 if task is editable by the active user
* @apiSuccess (Success 200) {Number} tasks.erasable 1 if task is erasable by the active user
* @apiSuccess (Success 200) {String} tasks.c_priority Priority (0-9)
* @apiSuccess (Success 200) {String} tasks.c_owner Username of owner
* @apiSuccess (Success 200) {String} [tasks.c_recurrence_id] Recurrence ID if task is cyclic
* @apiSuccess (Success 200) {Number} tasks.isException 1 if task is cyclic and an exception
* @apiSuccess (Success 200) {String} tasks.status Either completed, overdue, duetoday, or duelater
* @apiSuccess (Success 200) {String} tasks.formatted_enddate Localized end date
*/
- (WOResponse *) tasksListAction
{
NSDictionary *data;
NSMutableArray *filteredTasks, *filteredTask, *fields;
NSString *sort, *ascending;
2014-05-08 23:17:05 +02:00
NSString *statusFlag, *tasksView;
SOGoUserSettings *us;
NSEnumerator *tasks;
NSArray *task;
2017-09-08 19:24:58 +02:00
unsigned int endDateStamp;
BOOL showCompleted;
int statusCode;
2014-05-08 23:17:05 +02:00
int startSecs;
filteredTasks = [NSMutableArray array];
2017-09-08 19:24:58 +02:00
[self _setupContext];
[self saveFilterValue: @"TasksFilterState"];
[self saveSortValue: @"TasksSortingState"];
2017-09-08 19:24:58 +02:00
2014-05-08 23:17:05 +02:00
startSecs = (unsigned int) [startDate timeIntervalSince1970];
tasksView = [request formValueForKey: @"filterpopup"];
2017-09-08 19:24:58 +02:00
showCompleted = [[request formValueForKey: @"show_completed"] intValue];
us = [[context activeUser] userSettings];
[us setBool: showCompleted forKey: @"ShowCompletedTasks"];
[us synchronize];
2017-09-08 19:24:58 +02:00
tasks = [[self _fetchFields: tasksFields
2014-05-08 23:17:05 +02:00
forComponentOfType: @"vtodo"] objectEnumerator];
2017-09-08 19:24:58 +02:00
while ((task = [tasks nextObject]))
2014-05-08 23:17:05 +02:00
{
statusCode = [[task objectAtIndex: taskStatusIndex] intValue];
2014-05-08 23:17:05 +02:00
if (statusCode != 1 || showCompleted)
{
2014-05-08 23:17:05 +02:00
filteredTask = [NSMutableArray arrayWithArray: task];
2015-07-09 22:18:03 +02:00
endDateStamp = [[task objectAtIndex: taskEndDateIndex] intValue];
2014-05-08 23:17:05 +02:00
statusFlag = [self _getStatusClassForStatusCode: statusCode
andEndDateStamp: endDateStamp];
[filteredTask addObject: statusFlag];
2017-05-19 18:13:01 +02:00
statusFlag = [iCalToDo statusForCode: statusCode];
[filteredTask replaceObjectAtIndex: taskStatusIndex withObject: statusFlag];
2014-05-08 23:17:05 +02:00
if (endDateStamp > 0)
[filteredTask addObject: [self _formattedDateForSeconds: endDateStamp
forAllDay: NO]];
else
[filteredTask addObject: [NSNull null]];
if (([tasksView isEqualToString:@"view_today"] ||
[tasksView isEqualToString:@"view_next7"] ||
[tasksView isEqualToString:@"view_next14"] ||
[tasksView isEqualToString:@"view_next31"] ||
[tasksView isEqualToString:@"view_thismonth"] ||
[tasksView isEqualToString:@"view_thisyear"]) &&
2016-10-24 22:31:59 +02:00
(endDateStamp == 0 || endDateStamp >= startSecs))
2014-05-08 23:17:05 +02:00
[filteredTasks addObject: filteredTask];
else if ([tasksView isEqualToString:@"view_all"])
[filteredTasks addObject: filteredTask];
2016-10-24 22:31:59 +02:00
else if (([tasksView isEqualToString:@"view_overdue"]) &&
([[filteredTask objectAtIndex:taskStatusFlagIndex] isEqualToString:@"overdue"]))
2014-05-08 23:17:05 +02:00
[filteredTasks addObject: filteredTask];
2016-10-24 22:31:59 +02:00
else if ([tasksView isEqualToString:@"view_incomplete"] &&
(![[filteredTask objectAtIndex:taskStatusFlagIndex] isEqualToString:@"completed"]))
2014-05-08 23:17:05 +02:00
[filteredTasks addObject: filteredTask];
2016-10-24 22:31:59 +02:00
else if ([tasksView isEqualToString:@"view_not_started"] &&
([[[filteredTask objectAtIndex:taskStatusIndex] stringValue] isEqualToString:@"0"]))
2014-05-08 23:17:05 +02:00
[filteredTasks addObject: filteredTask];
}
2014-05-08 23:17:05 +02:00
}
sort = [[context request] formValueForKey: @"sort"];
if ([sort isEqualToString: @"title"])
[filteredTasks sortUsingSelector: @selector (compareTasksTitleAscending:)];
else if ([sort isEqualToString: @"priority"])
[filteredTasks sortUsingSelector: @selector (compareTasksPriorityAscending:)];
else if ([sort isEqualToString: @"start"])
[filteredTasks sortUsingSelector: @selector (compareTasksStartDateAscending:)];
else if ([sort isEqualToString: @"end"])
[filteredTasks sortUsingSelector: @selector (compareTasksEndDateAscending:)];
else if ([sort isEqualToString: @"location"])
[filteredTasks sortUsingSelector: @selector (compareTasksLocationAscending:)];
else if ([sort isEqualToString: @"category"])
[filteredTasks sortUsingSelector: @selector (compareTasksCategoryAscending:)];
else if ([sort isEqualToString: @"calendarName"])
[filteredTasks sortUsingSelector: @selector (compareTasksCalendarNameAscending:)];
2014-05-08 23:17:05 +02:00
else
[filteredTasks sortUsingSelector: @selector (compareTasksAscending:)];
2017-09-08 19:24:58 +02:00
ascending = [[context request] formValueForKey: @"asc"];
if (![ascending boolValue])
[filteredTasks reverseArray];
// Fields names
fields = [NSMutableArray arrayWithArray: tasksFields];
[fields addObject: @"status"];
[fields addObject: @"formatted_enddate"];
data = [NSDictionary dictionaryWithObjectsAndKeys: fields, @"fields", filteredTasks, @"tasks", nil];
return [self _responseWithData: data];
}
2014-06-27 23:00:32 +02:00
- (WOResponse *) activeTasksAction
{
2014-06-28 14:31:07 +02:00
NSMutableDictionary *activeTasksByCalendars;
2014-06-27 23:00:32 +02:00
SOGoAppointmentFolder *folder;
2014-06-28 14:31:07 +02:00
SOGoAppointmentFolders *co;
2014-06-27 23:00:32 +02:00
NSArray *folders;
2014-06-28 14:31:07 +02:00
int i;
2014-06-27 23:00:32 +02:00
co = [self clientObject];
folders = [co subFolders];
2014-06-28 14:31:07 +02:00
activeTasksByCalendars = [NSMutableDictionary dictionaryWithCapacity: [folders count]];
for (i = 0; i < [folders count]; i++)
{
folder = [folders objectAtIndex: i];
2017-09-08 19:24:58 +02:00
2014-06-28 14:31:07 +02:00
[activeTasksByCalendars setObject: [folder activeTasks]
forKey: [folder nameInContainer]];
2014-06-27 23:00:32 +02:00
}
2017-09-08 19:24:58 +02:00
2014-06-27 23:00:32 +02:00
return [self _responseWithData: activeTasksByCalendars];
}
@end