448 lines
14 KiB
Objective-C
448 lines
14 KiB
Objective-C
/*
|
|
Copyright (C) 2007-2014 Inverse inc.
|
|
|
|
This file is part of SOGo
|
|
|
|
SOGo is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
later version.
|
|
|
|
SOGo 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 Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with OGo; see the file COPYING. If not, write to the
|
|
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
*/
|
|
|
|
|
|
#import <NGObjWeb/WOContext+SoObjects.h>
|
|
#import <NGObjWeb/WOResponse.h>
|
|
#import <NGExtensions/NSCalendarDate+misc.h>
|
|
#import <NGCards/iCalCalendar.h>
|
|
#import <NGCards/iCalFreeBusy.h>
|
|
#import <NGCards/iCalPerson.h>
|
|
|
|
#import <SOGo/SOGoBuild.h>
|
|
#import <SOGo/SOGoDomainDefaults.h>
|
|
#import <SOGo/SOGoSource.h>
|
|
#import <SOGo/SOGoUser.h>
|
|
#import <SOGo/SOGoUserManager.h>
|
|
|
|
#import "SOGoAppointmentFolder.h"
|
|
#import "SOGoAppointmentFolders.h"
|
|
|
|
#import "MSExchangeFreeBusy.h"
|
|
|
|
#import "SOGoFreeBusyObject.h"
|
|
|
|
@interface SOGoFreeBusyObject (PrivateAPI)
|
|
- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos
|
|
from: (NSCalendarDate *) _startDate
|
|
to: (NSCalendarDate *) _endDate;
|
|
@end
|
|
|
|
@implementation SOGoFreeBusyObject
|
|
|
|
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid
|
|
{
|
|
iCalPerson *person;
|
|
SOGoUserManager *um;
|
|
NSString *domain;
|
|
NSDictionary *contactInfos;
|
|
|
|
um = [SOGoUserManager sharedUserManager];
|
|
contactInfos = [um contactInfosForUserWithUIDorEmail: uid];
|
|
if (contactInfos == nil)
|
|
{
|
|
// Search among global addressbooks
|
|
domain = [[context activeUser] domain];
|
|
contactInfos = [um fetchContactWithUID: uid inDomain: domain];
|
|
}
|
|
|
|
/* iCal.app compatibility:
|
|
- don't add "cn"; */
|
|
person = [iCalPerson new];
|
|
[person autorelease];
|
|
[person setEmail: [contactInfos objectForKey: @"c_email"]];
|
|
|
|
return person;
|
|
}
|
|
|
|
/* Private API */
|
|
- (iCalFreeBusyType) _fbTypeForEventStatus: (int) eventStatus
|
|
{
|
|
//unsigned int status;
|
|
iCalFreeBusyType fbType;
|
|
|
|
//status = [eventStatus unsignedIntValue];
|
|
if (eventStatus == 0)
|
|
fbType = iCalFBBusyTentative;
|
|
else if (eventStatus == 1)
|
|
fbType = iCalFBBusy;
|
|
else
|
|
fbType = iCalFBFree;
|
|
|
|
return fbType;
|
|
}
|
|
|
|
- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos
|
|
withMethod: (NSString *) method
|
|
andUID: (NSString *) uid
|
|
andOrganizer: (iCalPerson *) organizer
|
|
andContact: (NSString *) contactID
|
|
from: (NSCalendarDate *) _startDate
|
|
to: (NSCalendarDate *) _endDate
|
|
{
|
|
NSArray *emails, *partstates;
|
|
NSEnumerator *events;
|
|
iCalCalendar *calendar;
|
|
iCalFreeBusy *freebusy;
|
|
NSDictionary *info;
|
|
iCalFreeBusyType type;
|
|
SOGoUser *user;
|
|
NSString *login;
|
|
int i;
|
|
|
|
login = [container ownerInContext: context];
|
|
user = [SOGoUser userWithLogin: login];
|
|
|
|
calendar = [iCalCalendar groupWithTag: @"vcalendar"];
|
|
[calendar setProdID: [NSString stringWithFormat:
|
|
@"-//Inverse inc./SOGo %@//EN",
|
|
SOGoVersion]];
|
|
[calendar setVersion: @"2.0"];
|
|
if (method)
|
|
[calendar setMethod: method];
|
|
|
|
freebusy = [iCalFreeBusy groupWithTag: @"vfreebusy"];
|
|
if (uid)
|
|
[freebusy setUid: uid];
|
|
if (organizer)
|
|
[freebusy setOrganizer: organizer];
|
|
if (contactID)
|
|
[freebusy addToAttendees: [self iCalPersonWithUID: contactID]];
|
|
else
|
|
[freebusy addToAttendees: [self iCalPersonWithUID: login]];
|
|
[freebusy setTimeStampAsDate: [NSCalendarDate calendarDate]];
|
|
[freebusy setStartDate: _startDate];
|
|
[freebusy setEndDate: _endDate];
|
|
|
|
/* ORGANIZER - strictly required but missing for now */
|
|
|
|
/* ATTENDEE */
|
|
// person = [self iCalPersonWithUid: login];
|
|
// [person setTag: @"ATTENDEE"];
|
|
// [ms appendString: [person versitString]];
|
|
|
|
/* FREEBUSY */
|
|
events = [_infos objectEnumerator];
|
|
while ((info = [events nextObject]))
|
|
if ([[info objectForKey: @"c_isopaque"] boolValue])
|
|
{
|
|
type = iCalFBFree;
|
|
|
|
// If the event has NO organizer (which means it's the user that has created it) OR
|
|
// If we are the organizer of the event THEN we are automatically busy
|
|
if ([[info objectForKey: @"c_orgmail"] length] == 0 ||
|
|
[user hasEmail: [info objectForKey: @"c_orgmail"]])
|
|
{
|
|
type = iCalFBBusy;
|
|
}
|
|
else
|
|
{
|
|
// We check if the user has accepted/declined or needs action
|
|
// on the current event.
|
|
emails = [[info objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
|
|
|
|
for (i = 0; i < [emails count]; i++)
|
|
{
|
|
if ([user hasEmail: [emails objectAtIndex: i]])
|
|
{
|
|
// We now fetch the c_partstates array and get the participation
|
|
// status of the user for the event
|
|
partstates = [[info objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
|
|
|
|
if (i < [partstates count])
|
|
{
|
|
type = [self _fbTypeForEventStatus: [[partstates objectAtIndex: i] intValue]];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == iCalFBBusy
|
|
|| type == iCalFBBusyTentative
|
|
|| type == iCalFBBusyUnavailable)
|
|
[freebusy addFreeBusyFrom: [info objectForKey: @"startDate"]
|
|
to: [info objectForKey: @"endDate"]
|
|
type: type];
|
|
}
|
|
|
|
[calendar setUniqueChild: freebusy];
|
|
|
|
return [calendar versitString];
|
|
}
|
|
|
|
- (NSString *) contentAsString
|
|
{
|
|
NSCalendarDate *today, *startDate, *endDate;
|
|
SOGoUserDefaults *ud;
|
|
SOGoDomainDefaults *dd;
|
|
NSArray *interval;
|
|
int start, end;
|
|
|
|
today = [[NSCalendarDate calendarDate] beginOfDay];
|
|
ud = [[context activeUser] userDefaults];
|
|
[today setTimeZone: [ud timeZone]];
|
|
|
|
dd = [[context activeUser] domainDefaults];
|
|
interval = [dd freeBusyDefaultInterval];
|
|
if ([interval count] > 1)
|
|
{
|
|
start = [[interval objectAtIndex: 0] unsignedIntValue];
|
|
end = [[interval objectAtIndex: 1] unsignedIntValue];
|
|
}
|
|
else
|
|
{
|
|
start = 7;
|
|
end = 7;
|
|
}
|
|
|
|
startDate = [today dateByAddingYears: 0 months: 0 days: -start
|
|
hours: 0 minutes: 0 seconds: 0];
|
|
endDate = [today dateByAddingYears: 0 months: 0 days: end
|
|
hours: 0 minutes: 0 seconds: 0];
|
|
|
|
return [self contentAsStringFrom: startDate to: endDate];
|
|
}
|
|
|
|
- (NSString *) contentAsStringWithMethod: (NSString *) method
|
|
andUID: (NSString *) UID
|
|
andOrganizer: (iCalPerson *) organizer
|
|
andContact: (NSString *) contactID
|
|
from: (NSCalendarDate *) _startDate
|
|
to: (NSCalendarDate *) _endDate
|
|
{
|
|
NSArray *infos;
|
|
|
|
infos = [self fetchFreeBusyInfosFrom: _startDate to: _endDate
|
|
forContact: contactID];
|
|
|
|
return [self iCalStringForFreeBusyInfos: infos
|
|
withMethod: method
|
|
andUID: UID andOrganizer: organizer
|
|
andContact: contactID
|
|
from: _startDate to: _endDate];
|
|
}
|
|
|
|
- (NSString *) contentAsStringFrom: (NSCalendarDate *) _startDate
|
|
to: (NSCalendarDate *) _endDate
|
|
{
|
|
return [self contentAsStringWithMethod: nil andUID: nil
|
|
andOrganizer: nil
|
|
andContact: nil
|
|
from: _startDate
|
|
to: _endDate];
|
|
}
|
|
|
|
/**
|
|
* Fetch freebusy information for a user that exists in a contact source
|
|
* (not an authentication source) for which freebusy information is available
|
|
* (currently limited to a Microsoft Exchange server with Web Services enabled).
|
|
* @param startDate the beginning of the covered period
|
|
* @param endDate the ending of the covered period
|
|
* @param uid the ID of the contact within the current domain
|
|
* @return an array of dictionaries containing the start and end dates of each busy period
|
|
* @see MSExchangeFreeBusy.m
|
|
*/
|
|
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate
|
|
to: (NSCalendarDate *) endDate
|
|
forContact: (NSString *) uid
|
|
{
|
|
if ([uid length])
|
|
{
|
|
SOGoUserManager *um;
|
|
NSString *domain, *email;
|
|
NSDictionary *contact;
|
|
MSExchangeFreeBusy *exchangeFreeBusy;
|
|
NSObject <SOGoDNSource> *source;
|
|
|
|
um = [SOGoUserManager sharedUserManager];
|
|
domain = [[context activeUser] domain];
|
|
contact = [um fetchContactWithUID: uid inDomain: domain];
|
|
if (contact)
|
|
{
|
|
email = [contact valueForKey: @"c_email"];
|
|
source = [contact objectForKey: @"source"];
|
|
if ([email length]
|
|
&& [source conformsToProtocol: @protocol (SOGoDNSource)]
|
|
&& [source MSExchangeHostname])
|
|
{
|
|
exchangeFreeBusy = [[MSExchangeFreeBusy alloc] init];
|
|
[exchangeFreeBusy autorelease];
|
|
|
|
return [exchangeFreeBusy fetchFreeBusyInfosFrom: startDate
|
|
to: endDate
|
|
forEmail: email
|
|
inSource: source
|
|
inContext: context];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return [self fetchFreeBusyInfosFrom: startDate to: endDate];
|
|
}
|
|
|
|
// No freebusy information found
|
|
return nil;
|
|
}
|
|
|
|
|
|
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate
|
|
to: (NSCalendarDate *) endDate
|
|
{
|
|
SOGoAppointmentFolder *calFolder;
|
|
SOGoAppointmentFolders *calFolders;
|
|
SOGoUser *user;
|
|
SOGoUserDefaults *ud;
|
|
NSArray *folders;
|
|
NSMutableArray *infos;
|
|
NSString *login;
|
|
unsigned int count, max;
|
|
|
|
infos = [NSMutableArray array];
|
|
|
|
calFolders = [container lookupName: @"Calendar"
|
|
inContext: context
|
|
acquire: NO];
|
|
[calFolders appendSubscribedSources];
|
|
folders = [calFolders subFolders];
|
|
|
|
max = [folders count];
|
|
for (count = 0; count < max; count++)
|
|
{
|
|
calFolder = [folders objectAtIndex: count];
|
|
if ([calFolder includeInFreeBusy])
|
|
[infos addObjectsFromArray: [calFolder fetchFreeBusyInfosFrom: startDate
|
|
to: endDate]];
|
|
}
|
|
|
|
login = [container ownerInContext: context];
|
|
user = [SOGoUser userWithLogin: login];
|
|
ud = [user userDefaults];
|
|
|
|
if ([ud busyOffHours])
|
|
{
|
|
NSCalendarDate *currentStartDate, *currentEndDate, *weekendStartDate, *weekendEndDate;
|
|
NSTimeZone *timeZone;
|
|
unsigned int dayStartHour, dayEndHour, intervalHours;
|
|
BOOL firstRange;
|
|
|
|
dayStartHour = [ud dayStartHour];
|
|
dayEndHour = [ud dayEndHour];
|
|
intervalHours = dayStartHour + 24 - dayEndHour;
|
|
timeZone = [ud timeZone];
|
|
firstRange = YES;
|
|
|
|
currentStartDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra]
|
|
month: [startDate monthOfYear]
|
|
day: [startDate dayOfMonth]
|
|
hour: 0
|
|
minute: 0
|
|
second: 0
|
|
timeZone: timeZone];
|
|
currentEndDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra]
|
|
month: [startDate monthOfYear]
|
|
day: [startDate dayOfMonth]
|
|
hour: dayStartHour
|
|
minute: 0
|
|
second: 0
|
|
timeZone: timeZone];
|
|
|
|
while ([currentStartDate compare: endDate] == NSOrderedAscending ||
|
|
[currentStartDate compare: endDate] == NSOrderedSame)
|
|
{
|
|
if ([endDate compare: currentEndDate] == NSOrderedAscending)
|
|
currentEndDate = endDate;
|
|
|
|
[infos addObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithBool: YES], @"c_isopaque",
|
|
([currentStartDate compare: startDate] == NSOrderedAscending)? startDate : currentStartDate, @"startDate",
|
|
currentEndDate, @"endDate", nil]];
|
|
|
|
if (currentEndDate != endDate
|
|
&& ([currentEndDate dayOfWeek] == 6 || [currentEndDate dayOfWeek] == 0))
|
|
{
|
|
// Fill weekend days
|
|
weekendStartDate = currentEndDate;
|
|
weekendEndDate = [weekendStartDate addYear:0 month:0 day:0 hour:(-[weekendStartDate hourOfDay] + dayEndHour) minute:0 second:0];
|
|
[infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: YES], @"c_isopaque",
|
|
weekendStartDate, @"startDate",
|
|
weekendEndDate, @"endDate", nil]];
|
|
}
|
|
|
|
// Compute next range
|
|
if (firstRange)
|
|
{
|
|
currentStartDate = [currentStartDate addYear:0 month:0 day:0 hour:dayEndHour minute:0 second:0];
|
|
firstRange = NO;
|
|
}
|
|
else
|
|
{
|
|
currentStartDate = [currentStartDate addYear:0 month:0 day:1 hour:0 minute:0 second:0];
|
|
}
|
|
currentEndDate = [currentStartDate addYear:0 month:0 day:0 hour:intervalHours minute:0 second:0];
|
|
}
|
|
}
|
|
|
|
return infos;
|
|
}
|
|
|
|
- (NSString *) iCalString
|
|
{
|
|
// for UI-X appointment viewer
|
|
return [self contentAsString];
|
|
}
|
|
|
|
/* deliver content without need for view method */
|
|
|
|
- (id) GETAction: (id)_ctx
|
|
{
|
|
WOResponse *r;
|
|
NSData *contentData;
|
|
|
|
contentData = [[self contentAsString]
|
|
dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
r = [(WOContext *) _ctx response];
|
|
[r setHeader: @"text/calendar" forKey: @"content-type"];
|
|
[r setContent: contentData];
|
|
[r setStatus: 200];
|
|
|
|
return r;
|
|
}
|
|
|
|
- (BOOL) isFolderish
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSString *) davContentType
|
|
{
|
|
return @"text/calendar";
|
|
}
|
|
|
|
- (NSArray *) aclsForUser: (NSString *) uid
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
@end
|