sogo/SoObjects/Appointments/SOGoAppointmentFolder.m

2613 lines
74 KiB
Mathematica
Raw Normal View History

/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo 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.
OGo 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 <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSValue.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/SoSecurityManager.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOMessage.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NGLoggerManager.h>
#import <NGExtensions/NSString+misc.h>
#import <GDLContentStore/GCSFolder.h>
#import <DOM/DOMNode.h>
#import <DOM/DOMProtocols.h>
#import <EOControl/EOFetchSpecification.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <NGCards/iCalCalendar.h>
#import <NGCards/iCalFreeBusy.h>
#import <NGCards/iCalDateTime.h>
#import <NGCards/iCalPerson.h>
#import <NGCards/iCalRecurrenceCalculator.h>
#import <NGCards/NSString+NGCards.h>
#import <NGExtensions/NGCalendarDateRange.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <SaxObjC/XMLNamespaces.h>
// #import <NGObjWeb/SoClassSecurityInfo.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSObject+DAV.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoCache.h>
// #import <SOGo/SOGoCustomGroupFolder.h>
#import <SOGo/LDAPUserManager.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoWebDAVAclManager.h>
#import "iCalEntityObject+SOGo.h"
#import "SOGoAppointmentObject.h"
#import "SOGoAppointmentFolders.h"
#import "SOGoFreeBusyObject.h"
#import "SOGoTaskObject.h"
#import "SOGoAppointmentFolder.h"
#define defaultColor @"#AAAAAA"
@implementation SOGoAppointmentFolder
static NGLogger *logger = nil;
static NSNumber *sharedYes = nil;
static NSArray *reportQueryFields = nil;
static Class sogoAppointmentFolderKlass = Nil;
+ (void) initialize
{
NGLoggerManager *lm;
static BOOL didInit = NO;
// SoClassSecurityInfo *securityInfo;
if (didInit) return;
didInit = YES;
if (!sogoAppointmentFolderKlass)
{
sogoAppointmentFolderKlass = self;
[iCalEntityObject initializeSOGoExtensions];
}
if (!reportQueryFields)
reportQueryFields = [[NSArray alloc] initWithObjects: @"c_name",
@"c_content", @"c_creationdate",
@"c_lastmodified", @"c_version",
@"c_component", nil];
NSAssert2([super version] == 0,
@"invalid superclass (%@) version %i !",
NSStringFromClass([self superclass]), [super version]);
lm = [NGLoggerManager defaultLoggerManager];
logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
// securityInfo = [self soClassSecurityInfo];
// [securityInfo declareRole: SOGoRole_Delegate
// asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
// [securityInfo declareRole: SOGoRole_Delegate
// asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
// [securityInfo declareRoles: [NSArray arrayWithObjects:
// SOGoRole_Delegate,
// SOGoRole_Assistant, nil]
// asDefaultForPermission: SoPerm_View];
sharedYes = [[NSNumber numberWithBool: YES] retain];
}
+ (SOGoWebDAVAclManager *) webdavAclManager
{
SOGoWebDAVAclManager *aclManager = nil;
NSString *nsI;
if (!aclManager)
{
nsI = @"urn:inverse:params:xml:ns:inverse-dav";
aclManager = [SOGoWebDAVAclManager new];
[aclManager registerDAVPermission: davElement (@"read", XMLNS_WEBDAV)
abstract: YES
withEquivalent: SoPerm_WebDAVAccess
asChildOf: davElement (@"all", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", XMLNS_WEBDAV)
abstract: YES
withEquivalent: SoPerm_WebDAVAccess
asChildOf: davElement (@"read", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"read-free-busy", XMLNS_WEBDAV)
abstract: NO
withEquivalent: SoPerm_AccessContentsInformation
asChildOf: davElement (@"read", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"write", XMLNS_WEBDAV)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"all", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"bind", XMLNS_WEBDAV)
abstract: NO
withEquivalent: SoPerm_AddDocumentsImagesAndFiles
asChildOf: davElement (@"write", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"schedule",
XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"bind", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"schedule-post",
XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-post-vevent", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-post", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-post-vtodo", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-post", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-post-vjournal", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-post", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-post-vfreebusy", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-post", XMLNS_CALDAV)];
[aclManager registerDAVPermission: davElement (@"schedule-deliver",
XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-deliver-vevent", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-deliver", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-deliver-vtodo", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-deliver", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-deliver-vjournal", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-deliver", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-deliver-vfreebusy", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-deliver", XMLNS_CALDAV)];
[aclManager registerDAVPermission: davElement (@"schedule-respond",
XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-respond-vevent", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-respond", XMLNS_CALDAV)];
[aclManager registerDAVPermission:
davElement (@"schedule-respond-vtodo", XMLNS_CALDAV)
abstract: NO
withEquivalent: nil
asChildOf: davElement (@"schedule-respond", XMLNS_CALDAV)];
[aclManager registerDAVPermission: davElement (@"unbind", XMLNS_WEBDAV)
abstract: NO
withEquivalent: SoPerm_DeleteObjects
asChildOf: davElement (@"write", XMLNS_WEBDAV)];
[aclManager
registerDAVPermission: davElement (@"write-properties", XMLNS_WEBDAV)
abstract: YES
withEquivalent: SoPerm_ChangePermissions /* hackish */
asChildOf: davElement (@"write", XMLNS_WEBDAV)];
[aclManager
registerDAVPermission: davElement (@"write-content", XMLNS_WEBDAV)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"write", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"admin", nsI)
abstract: YES
withEquivalent: nil
asChildOf: davElement (@"all", XMLNS_WEBDAV)];
[aclManager registerDAVPermission: davElement (@"read-acl", XMLNS_WEBDAV)
abstract: YES
withEquivalent: SOGoPerm_ReadAcls
asChildOf: davElement (@"admin", nsI)];
[aclManager registerDAVPermission: davElement (@"write-acl", XMLNS_WEBDAV)
abstract: YES
withEquivalent: SoPerm_ChangePermissions
asChildOf: davElement (@"admin", nsI)];
}
return aclManager;
}
- (id) initWithName: (NSString *) name
inContainer: (id) newContainer
{
if ((self = [super initWithName: name inContainer: newContainer]))
{
timeZone = [[context activeUser] timeZone];
aclMatrix = [NSMutableDictionary new];
stripFields = nil;
uidToFilename = nil;
}
return self;
}
- (void) dealloc
{
[aclMatrix release];
[stripFields release];
[uidToFilename release];
[super dealloc];
}
- (Class) objectClassForComponentName: (NSString *) componentName
{
Class objectClass;
if ([componentName isEqualToString: @"vevent"])
objectClass = [SOGoAppointmentObject class];
else if ([componentName isEqualToString: @"vtodo"])
objectClass = [SOGoTaskObject class];
else
objectClass = Nil;
return objectClass;
}
- (NSString *) calendarColor
{
NSUserDefaults *settings;
NSDictionary *colors;
NSString *color;
settings = [[context activeUser] userSettings];
colors = [[settings objectForKey: @"Calendar"]
objectForKey: @"FolderColors"];
color = [colors objectForKey: [self folderReference]];
if (!color)
color = defaultColor;
return color;
}
- (void) setCalendarColor: (NSString *) newColor
{
NSUserDefaults *settings;
NSMutableDictionary *calendarSettings;
NSMutableDictionary *colors;
settings = [[context activeUser] userSettings];
calendarSettings = [settings objectForKey: @"Calendar"];
if (!calendarSettings)
{
calendarSettings = [NSMutableDictionary dictionary];
[settings setObject: calendarSettings
forKey: @"Calendar"];
}
colors = [calendarSettings objectForKey: @"FolderColors"];
if (!colors)
{
colors = [NSMutableDictionary dictionary];
[calendarSettings setObject: colors forKey: @"FolderColors"];
}
[colors setObject: newColor forKey: [self folderReference]];
[settings synchronize];
}
- (NSString *) syncTag
{
NSUserDefaults *settings;
NSDictionary *syncTags;
NSString *syncTag;
settings = [[context activeUser] userSettings];
syncTags = [[settings objectForKey: @"Calendar"]
objectForKey: @"FolderSyncTags"];
syncTag = [syncTags objectForKey: [self folderReference]];
if (!syncTag)
syncTag = @"";
return syncTag;
}
#warning this code shares a lot with the colour code
- (void) setSyncTag: (NSString *) newSyncTag
{
NSUserDefaults *settings;
NSMutableDictionary *calendarSettings;
NSMutableDictionary *syncTags;
settings = [[context activeUser] userSettings];
calendarSettings = [settings objectForKey: @"Calendar"];
if (!calendarSettings)
{
calendarSettings = [NSMutableDictionary dictionary];
[settings setObject: calendarSettings
forKey: @"Calendar"];
}
syncTags = [calendarSettings objectForKey: @"FolderSyncTags"];
if (!syncTags)
{
syncTags = [NSMutableDictionary dictionary];
[calendarSettings setObject: syncTags forKey: @"FolderSyncTags"];
}
[syncTags setObject: newSyncTag forKey: [self folderReference]];
[settings synchronize];
}
/* logging */
- (id) debugLogger
{
return logger;
}
/* selection */
- (NSArray *) calendarUIDs
{
/* this is used for group calendars (this folder just returns itself) */
NSString *s;
s = [[self container] nameInContainer];
// [self logWithFormat:@"CAL UID: %@", s];
return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
}
/* fetching */
- (NSString *) _sqlStringForComponent: (id) _component
{
NSString *sqlString;
NSArray *components;
if (_component)
{
if ([_component isKindOfClass: [NSArray class]])
components = _component;
else
components = [NSArray arrayWithObject: _component];
sqlString
= [NSString stringWithFormat: @"AND (c_component = '%@')",
[components componentsJoinedByString: @"' OR c_component = '"]];
}
else
sqlString = @"";
return sqlString;
}
- (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
{
unsigned int start, end;
start = (unsigned int) [_startDate timeIntervalSince1970];
end = (unsigned int) [_endDate timeIntervalSince1970];
return [NSString stringWithFormat:
@" AND (c_startdate <= %u) AND (c_enddate >= %u)",
end, start];
}
- (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
{
NSMutableString *classificationString;
NSString *currentRole;
unsigned int counter;
iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
iCalAccessConfidential};
classificationString = [NSMutableString string];
for (counter = 0; counter < 3; counter++)
{
currentRole = [self roleForComponentsWithAccessClass: classes[counter]
forUser: uid];
if ([currentRole length] > 0)
[classificationString appendFormat: @"c_classification = %d or ",
classes[counter]];
}
return classificationString;
}
- (NSString *) _privacySqlString
{
NSString *privacySqlString, *email, *login;
SOGoUser *activeUser;
activeUser = [context activeUser];
if (activeUserIsOwner
|| [[self ownerInContext: context] isEqualToString: [[context activeUser] login]]
|| [[activeUser rolesForObject: self inContext: context]
containsObject: SoRole_Owner])
privacySqlString = @"";
else if ([[activeUser login] isEqualToString: @"freebusy"])
privacySqlString = @"and (c_isopaque = 1)";
else
{
#warning we do not manage all the possible user emails
email = [[activeUser primaryIdentity] objectForKey: @"email"];
login = [activeUser login];
privacySqlString
= [NSString stringWithFormat:
@"(%@(c_orgmail = '%@')"
@" or ((c_partmails caseInsensitiveLike '%@%%'"
@" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
[self _privacyClassificationStringsForUID: login],
email, email, email];
}
return privacySqlString;
}
- (NSArray *) bareFetchFields: (NSArray *) fields
from: (NSCalendarDate *) startDate
to: (NSCalendarDate *) endDate
title: (NSString *) title
component: (id) component
additionalFilters: (NSString *) filters
{
EOQualifier *qualifier;
GCSFolder *folder;
NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
*filterSqlString;
NGCalendarDateRange *r;
folder = [self ocsFolder];
if (startDate && endDate)
{
r = [NGCalendarDateRange calendarDateRangeWithStartDate: startDate
endDate: endDate];
dateSqlString = [self _sqlStringRangeFrom: startDate to: endDate];
}
else
{
r = nil;
dateSqlString = @"";
}
if ([title length])
titleSqlString = [NSString stringWithFormat: @"AND (c_title"
@" isCaseInsensitiveLike: '%%%@%%')", title];
else
titleSqlString = @"";
componentSqlString = [self _sqlStringForComponent: component];
if (filters)
filterSqlString = [NSString stringWithFormat: @"AND (%@)", filters];
else
filterSqlString = @"";
/* prepare mandatory fields */
sql = [[NSString stringWithFormat: @"%@%@%@%@",
dateSqlString, titleSqlString, componentSqlString,
filterSqlString] substringFromIndex: 4];
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
return [folder fetchFields: fields matchingQualifier: qualifier];
}
- (BOOL) _checkIfWeCanRememberRecords: (NSArray *) fields
{
return ([fields containsObject: @"c_name"]
&& [fields containsObject: @"c_version"]
&& [fields containsObject: @"c_lastmodified"]
&& [fields containsObject: @"c_creationdate"]
&& [fields containsObject: @"c_component"]);
}
- (void) _rememberRecords: (NSArray *) records
{
NSEnumerator *recordsEnum;
NSDictionary *currentRecord;
recordsEnum = [records objectEnumerator];
while ((currentRecord = [recordsEnum nextObject]))
[childRecords setObject: currentRecord
forKey: [currentRecord objectForKey: @"c_name"]];
}
- (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
{
NSMutableDictionary *md;
static NSString *fields[] = { @"c_startdate", @"startDate",
@"c_enddate", @"endDate" };
unsigned int count;
NSCalendarDate *date;
NSNumber *dateValue;
BOOL isAllDay;
isAllDay = [[_record objectForKey: @"c_isallday"] boolValue];
md = [[_record mutableCopy] autorelease];
for (count = 0; count < 2; count++)
{
dateValue = [_record objectForKey: fields[count * 2]];
if (dateValue)
{
date = [NSCalendarDate dateWithTimeIntervalSince1970: [dateValue unsignedIntValue]];
if (date)
{
[date setTimeZone: timeZone];
[md setObject: date forKey: fields[count * 2 + 1]];
}
}
else
[self logWithFormat: @"missing '%@' in record?", fields[count * 2]];
}
return md;
}
- (NSArray *) fixupRecords: (NSArray *) records
fetchRange: (NGCalendarDateRange *) r
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
unsigned count, max;
id row; // TODO: what is the type of the record?
if (records)
{
max = [records count];
ma = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
row = [self fixupRecord: [records objectAtIndex: count]];
if (row)
[ma addObject: row];
}
}
else
ma = nil;
return ma;
}
- (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
cycleRange: (NGCalendarDateRange *) _r
{
NSMutableDictionary *md;
NSNumber *dateSecs;
id tmp;
md = [[_record mutableCopy] autorelease];
/* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
the reccurence since we use those when displaying events in SOGo Web */
tmp = [_r startDate];
[tmp setTimeZone: timeZone];
[md setObject: tmp forKey: @"startDate"];
dateSecs = [NSNumber numberWithInt: [tmp timeIntervalSince1970]];
[md setObject: dateSecs forKey: @"c_startdate"];
[md setObject: dateSecs forKey: @"c_recurrence_id"];
tmp = [_r endDate];
[tmp setTimeZone: timeZone];
[md setObject: tmp forKey: @"endDate"];
dateSecs = [NSNumber numberWithInt: [tmp timeIntervalSince1970]];
[md setObject: dateSecs forKey: @"c_enddate"];
return md;
}
- (int) _indexOfRecordMatchingDate: (NSCalendarDate *) matchDate
inArray: (NSArray *) recordArray
{
int count, max, recordIndex;
NSDictionary *currentRecord;
recordIndex = -1;
count = 0;
max = [recordArray count];
while (recordIndex == -1 && count < max)
{
currentRecord = [recordArray objectAtIndex: count];
if ([[currentRecord objectForKey: @"startDate"]
isEqual: matchDate])
recordIndex = count;
else
count++;
}
return recordIndex;
}
- (void) _fixExceptionRecord: (NSMutableDictionary *) recRecord
fromRow: (NSDictionary *) row
{
NSArray *objects;
static NSArray *fields = nil;
if (!fields)
{
fields = [NSArray arrayWithObjects: @"c_name", nil];
[fields retain];
}
objects = [row objectsForKeys: fields notFoundMarker: @""];
[recRecord setObjects: objects forKeys: fields];
}
- (void) _appendCycleException: (iCalRepeatableEntityObject *) component
fromRow: (NSDictionary *) row
forRange: (NGCalendarDateRange *) dateRange
toArray: (NSMutableArray *) ma
{
NSCalendarDate *startDate, *recurrenceId;
NSMutableDictionary *newRecord;
NSDictionary *oldRecord;
NGCalendarDateRange *newRecordRange;
int recordIndex;
newRecord = nil;
recurrenceId = [component recurrenceId];
if ([dateRange containsDate: recurrenceId])
{
recordIndex = [self _indexOfRecordMatchingDate: recurrenceId
inArray: ma];
if (recordIndex > -1)
{
startDate = [component startDate];
if ([dateRange containsDate: startDate])
{
newRecord = [self fixupRecord: [component quickRecord]];
[newRecord setObject: [NSNumber numberWithInt: 1]
forKey: @"c_iscycle"];
oldRecord = [ma objectAtIndex: recordIndex];
[newRecord setObject: [oldRecord objectForKey: @"c_recurrence_id"]
forKey: @"c_recurrence_id"];
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
}
else
[ma removeObjectAtIndex: recordIndex];
}
else
[self errorWithFormat:
@"missing exception record for recurrence-id: %@",
recurrenceId];
}
else
{
newRecord = [self fixupRecord: [component quickRecord]];
newRecordRange = [NGCalendarDateRange calendarDateRangeWithStartDate:
[newRecord objectForKey:
@"startDate"]
endDate: [newRecord objectForKey:
@"endDate"]];
if ([dateRange doesIntersectWithDateRange: newRecordRange])
[ma addObject: newRecord];
}
if (newRecord)
[self _fixExceptionRecord: newRecord fromRow: row];
}
- (void) _appendCycleExceptionsFromRow: (NSDictionary *) row
forRange: (NGCalendarDateRange *) dateRange
toArray: (NSMutableArray *) ma
{
NSArray *elements, *components;
unsigned int count, max;
NSString *content;
content = [row objectForKey: @"c_content"];
if ([content length])
{
elements = [iCalCalendar parseFromSource: content];
if ([elements count])
{
components = [[elements objectAtIndex: 0] allObjects];
max = [components count];
for (count = 1; count < max; count++)
[self _appendCycleException: [components objectAtIndex: count]
fromRow: row
forRange: dateRange
toArray: ma];
}
}
}
- (void) _flattenCycleRecord: (NSDictionary *) _row
forRange: (NGCalendarDateRange *) _r
intoArray: (NSMutableArray *) _ma
{
NSMutableDictionary *row, *fixedRow;
NSMutableArray *recordArray;
NSDictionary *cycleinfo;
NSCalendarDate *startDate, *endDate;
NGCalendarDateRange *fir, *rRange;
NSArray *rules, *exRules, *exDates, *ranges;
unsigned i, count;
NSString *content;
recordArray = [NSMutableArray array];
content = [_row objectForKey: @"c_cycleinfo"];
if (![content isNotNull])
{
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@",
_row];
return;
}
cycleinfo = [content propertyList];
if (!cycleinfo)
{
[self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@",
_row];
return;
}
row = [self fixupRecord: _row];
[row removeObjectForKey: @"c_cycleinfo"];
[row setObject: sharedYes forKey: @"isRecurrentEvent"];
startDate = [row objectForKey: @"startDate"];
endDate = [row objectForKey: @"endDate"];
fir = [NGCalendarDateRange calendarDateRangeWithStartDate: startDate
endDate: endDate];
rules = [cycleinfo objectForKey: @"rules"];
exRules = [cycleinfo objectForKey: @"exRules"];
exDates = [cycleinfo objectForKey: @"exDates"];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: _r
firstInstanceCalendarDateRange: fir
recurrenceRules: rules
exceptionRules: exRules
exceptionDates: exDates];
count = [ranges count];
for (i = 0; i < count; i++)
{
rRange = [ranges objectAtIndex: i];
fixedRow = [self fixupCycleRecord: row cycleRange: rRange];
if (fixedRow)
[recordArray addObject: fixedRow];
}
[self _appendCycleExceptionsFromRow: row
forRange: _r
toArray: recordArray];
[_ma addObjectsFromArray: recordArray];
}
- (NSArray *) fixupCyclicRecords: (NSArray *) _records
fetchRange: (NGCalendarDateRange *) _r
{
// TODO: is the result supposed to be sorted by date?
NSMutableArray *ma;
NSDictionary *row;
unsigned int count, max;
max = [_records count];
ma = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
row = [_records objectAtIndex: count];
[self _flattenCycleRecord: row forRange: _r intoArray: ma];
}
return ma;
}
- (void) _buildStripFieldsFromFields: (NSArray *) fields
{
stripFields = [[NSMutableArray alloc] initWithCapacity: [fields count]];
[stripFields setArray: fields];
[stripFields removeObjectsInArray: [NSArray arrayWithObjects: @"c_name",
@"c_uid", @"c_startdate",
@"c_enddate", @"c_isallday",
@"c_iscycle",
@"c_classification",
@"c_component", nil]];
}
- (void) _fixupProtectedInformation: (NSEnumerator *) ma
inFields: (NSArray *) fields
forUser: (NSString *) uid
{
NSMutableDictionary *currentRecord;
NSString *roles[] = {nil, nil, nil};
iCalAccessClass accessClass;
NSString *fullRole, *role;
if (!stripFields)
[self _buildStripFieldsFromFields: fields];
#warning we do not take the participation status into account
while ((currentRecord = [ma nextObject]))
{
accessClass
= [[currentRecord objectForKey: @"c_classification"] intValue];
role = roles[accessClass];
if (!role)
{
fullRole = [self roleForComponentsWithAccessClass: accessClass
forUser: uid];
if ([fullRole length] > 9)
role = [fullRole substringFromIndex: 9];
roles[accessClass] = role;
}
if ([role isEqualToString: @"DAndTViewer"])
[currentRecord removeObjectsForKeys: stripFields];
}
}
- (NSArray *) fetchFields: (NSArray *) _fields
from: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
title: (NSString *) title
component: (id) _component
additionalFilters: (NSString *) filters
{
EOQualifier *qualifier;
GCSFolder *folder;
NSMutableArray *fields, *ma = nil;
NSArray *records;
NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
*privacySqlString, *currentLogin, *filterSqlString;
NGCalendarDateRange *r;
BOOL rememberRecords;
rememberRecords = [self _checkIfWeCanRememberRecords: _fields];
// if (rememberRecords)
// NSLog (@"we will remember those records!");
folder = [self ocsFolder];
if (!folder)
{
[self errorWithFormat:@"(%s): missing folder for fetch!",
__PRETTY_FUNCTION__];
return nil;
}
if (_startDate && _endDate)
{
r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
endDate: _endDate];
dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
}
else
{
r = nil;
dateSqlString = @"";
}
if ([title length])
titleSqlString = [NSString stringWithFormat: @"AND (c_title"
@" isCaseInsensitiveLike: '%%%@%%')", title];
else
titleSqlString = @"";
componentSqlString = [self _sqlStringForComponent: _component];
privacySqlString = [self _privacySqlString];
if (filters)
filterSqlString = [NSString stringWithFormat: @"AND (%@)", filters];
else
filterSqlString = @"";
/* prepare mandatory fields */
fields = [NSMutableArray arrayWithArray: _fields];
[fields addObjectUniquely: @"c_name"];
[fields addObjectUniquely: @"c_uid"];
[fields addObjectUniquely: @"c_startdate"];
[fields addObjectUniquely: @"c_enddate"];
[fields addObjectUniquely: @"c_isallday"];
if (logger)
[self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@%@%@",
dateSqlString, titleSqlString, componentSqlString,
privacySqlString, filterSqlString];
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
records = [folder fetchFields: fields matchingQualifier: qualifier];
if (records)
{
if (r)
records = [self fixupRecords: records fetchRange: r];
if (logger)
[self debugWithFormat: @"fetched %i records: %@",
[records count], records];
ma = [NSMutableArray arrayWithArray: records];
}
/* fetch recurrent apts now. we do NOT consider the date range when doing that
as the c_startdate/c_enddate of a recurring event is always set to the first
recurrence - others are generated on the fly */
sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@%@", titleSqlString,
componentSqlString, privacySqlString, filterSqlString];
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
records = [folder fetchFields: fields matchingQualifier: qualifier];
if (records)
{
if (r)
records = [self fixupCyclicRecords: records fetchRange: r];
if (ma)
[ma addObjectsFromArray: records];
else
ma = [NSMutableArray arrayWithArray: records];
}
else if (!ma)
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
if (logger)
[self debugWithFormat:@"returning %i records", [ma count]];
currentLogin = [[context activeUser] login];
if (![currentLogin isEqualToString: owner])
[self _fixupProtectedInformation: [ma objectEnumerator]
inFields: _fields
forUser: currentLogin];
// [ma makeObjectsPerform: @selector (setObject:forKey:)
// withObject: owner
// withObject: @"owner"];
if (rememberRecords)
[self _rememberRecords: ma];
return ma;
}
- (void) _appendPropstat: (NSDictionary *) propstat
toResponse: (WOResponse *) r
{
NSArray *properties;
unsigned int count, max;
[r appendContentString: @"<D:propstat><D:prop>"];
properties = [propstat objectForKey: @"properties"];
max = [properties count];
for (count = 0; count < max; count++)
[r appendContentString: [properties objectAtIndex: count]];
[r appendContentString: @"</D:prop><D:status>"];
[r appendContentString: [propstat objectForKey: @"status"]];
[r appendContentString: @"</D:status></D:propstat>"];
}
#warning we should use the EOFetchSpecification for that!!! (see doPROPFIND:)
#warning components in calendar-data query are ignored
#warning the two following methods should be replaced with the new dav rendering mechanism
- (NSString *) _nodeTagForProperty: (NSString *) property
{
NSString *namespace, *nodeName, *nsRep;
NSRange nsEnd;
nsEnd = [property rangeOfString: @"}"];
namespace
= [property substringFromRange: NSMakeRange (1, nsEnd.location - 1)];
nodeName = [property substringFromIndex: nsEnd.location + 1];
if ([namespace isEqualToString: XMLNS_CALDAV])
nsRep = @"C";
else
nsRep = @"D";
return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName];
}
- (NSString *) _nodeTag: (NSString *) property
{
static NSMutableDictionary *tags = nil;
NSString *nodeTag;
if (!tags)
tags = [NSMutableDictionary new];
nodeTag = [tags objectForKey: property];
if (!nodeTag)
{
nodeTag = [self _nodeTagForProperty: property];
[tags setObject: nodeTag forKey: property];
}
return nodeTag;
}
- (NSString **) _properties: (NSString **) properties
ofObject: (NSDictionary *) object
{
NSString **currentProperty;
NSString **values, **currentValue;
SOGoObject *sogoObject;
SoSecurityManager *mgr;
SEL methodSel;
#warning things may crash here...
values = calloc (100, sizeof (NSMutableString *));
// NSLog (@"_properties:ofObject:: %@", [NSDate date]);
#warning this check should be done directly in the query... we should fix this sometime
mgr = [SoSecurityManager sharedSecurityManager];
sogoObject = [self _createChildComponentWithRecord: object];
if (activeUserIsOwner
|| [[self ownerInContext: context]
isEqualToString: [[context activeUser] login]]
|| !([mgr validatePermission: SOGoPerm_AccessObject
onObject: sogoObject
inContext: context]))
{
currentProperty = properties;
currentValue = values;
while (*currentProperty)
{
methodSel = SOGoSelectorForPropertyGetter (*currentProperty);
if (methodSel && [sogoObject respondsToSelector: methodSel])
*currentValue = [[sogoObject performSelector: methodSel]
stringByEscapingXMLString];
currentProperty++;
currentValue++;
}
}
// NSLog (@"/_properties:ofObject:: %@", [NSDate date]);
return values;
}
- (NSArray *) _propstats: (NSString **) properties
ofObject: (NSDictionary *) object
{
NSMutableArray *propstats, *properties200, *properties404, *propDict;
NSString **property, **values, **currentValue;
NSString *propertyValue, *nodeTag;
// NSLog (@"_propstats:ofObject:: %@", [NSDate date]);
propstats = [NSMutableArray array];
properties200 = [NSMutableArray new];
properties404 = [NSMutableArray new];
values = [self _properties: properties ofObject: object];
currentValue = values;
property = properties;
while (*property)
{
nodeTag = [self _nodeTag: *property];
if (*currentValue)
{
propertyValue = [NSString stringWithFormat: @"<%@>%@</%@>",
nodeTag, *currentValue, nodeTag];
propDict = properties200;
}
else
{
propertyValue = [NSString stringWithFormat: @"<%@/>", nodeTag];
propDict = properties404;
}
[propDict addObject: propertyValue];
property++;
currentValue++;
}
free (values);
if ([properties200 count])
[propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys:
properties200, @"properties",
@"HTTP/1.1 200 OK", @"status",
nil]];
[properties200 release];
if ([properties404 count])
[propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys:
properties404, @"properties",
@"HTTP/1.1 404 Not Found", @"status",
nil]];
[properties404 release];
// NSLog (@"/_propstats:ofObject:: %@", [NSDate date]);
return propstats;
}
#warning We need to use the new DAV utilities here...
#warning this is baddddd because we return a single-valued dictionary containing \
a cname which may not event exist... the logic behind appendObject:... should be \
rethought, especially since we may start using SQL views
- (void) appendObject: (NSDictionary *) object
properties: (NSString **) properties
withBaseURL: (NSString *) baseURL
toComplexResponse: (WOResponse *) r
{
NSArray *propstats;
unsigned int count, max;
[r appendContentString: @"<D:response><D:href>"];
[r appendContentString: baseURL];
// if (![baseURL hasSuffix: @"/"])
// [r appendContentString: @"/"];
[r appendContentString: [object objectForKey: @"c_name"]];
[r appendContentString: @"</D:href>"];
// NSLog (@"(appendPropstats...): %@", [NSDate date]);
propstats = [self _propstats: properties ofObject: object];
max = [propstats count];
for (count = 0; count < max; count++)
[self _appendPropstat: [propstats objectAtIndex: count]
toResponse: r];
// NSLog (@"/(appendPropstats...): %@", [NSDate date]);
[r appendContentString: @"</D:response>\r\n"];
}
- (void) appendMissingObjectRef: (NSString *) href
toComplexResponse: (WOResponse *) r
{
[r appendContentString: @"<D:response><D:href>"];
[r appendContentString: href];
[r appendContentString: @"</D:href><D:status>HTTP/1.1 404 Not Found</D:status></D:response>\r\n"];
}
- (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
toFilter: (NSMutableDictionary *) filter
{
NSCalendarDate *parsedDate;
parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
[filter setObject: parsedDate forKey: @"start"];
parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
[filter setObject: parsedDate forKey: @"end"];
}
#warning This method lacks support for timeranges
- (void) _appendPropertyFilter: (id <DOMElement>) propFilter
toFilter: (NSMutableDictionary *) filter
{
NSString *propName, *textMatch;
id <DOMNodeList> matches;
propName = [[propFilter attribute: @"name"] lowercaseString];
matches = [propFilter getElementsByTagName: @"text-match"];
if ([matches length])
textMatch = [[matches objectAtIndex: 0] textValue];
else
{
matches = [propFilter getElementsByTagName: @"is-not-defined"];
if ([matches length])
textMatch = @"NULL";
else
textMatch = @"";
}
[filter setObject: textMatch forKey: propName];
}
- (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
{
NSMutableDictionary *filterData;
id <DOMElement> parentNode;
id <DOMNodeList> elements;
NSString *componentName;
parentNode = (id <DOMElement>) [filterElement parentNode];
if ([[parentNode tagName] isEqualToString: @"comp-filter"]
&& [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
{
componentName = [[filterElement attribute: @"name"] lowercaseString];
filterData = [NSMutableDictionary dictionary];
[filterData setObject: componentName forKey: @"name"];
elements = [filterElement getElementsByTagName: @"time-range"];
if ([elements length])
[self _appendTimeRange: [elements objectAtIndex: 0]
toFilter: filterData];
elements = [filterElement getElementsByTagName: @"prop-filter"];
if ([elements length])
[self _appendPropertyFilter: [elements objectAtIndex: 0]
toFilter: filterData];
}
else
filterData = nil;
return filterData;
}
- (NSArray *) _parseRequestedProperties: (id <DOMElement>) parentNode
{
NSMutableArray *properties;
NSObject <DOMNodeList> *propList, *children;
NSObject <DOMNode> *currentChild;
unsigned int count, max, count2, max2;
NSString *flatProperty;
// NSLog (@"parseRequestProperties: %@", [NSDate date]);
properties = [NSMutableArray array];
propList = [parentNode getElementsByTagName: @"prop"];
max = [propList length];
for (count = 0; count < max; count++)
{
children = [[propList objectAtIndex: count] childNodes];
max2 = [children length];
for (count2 = 0; count2 < max2; count2++)
{
currentChild = [children objectAtIndex: count2];
if ([currentChild conformsToProtocol: @protocol (DOMElement)])
{
flatProperty = [NSString stringWithFormat: @"{%@}%@",
[currentChild namespaceURI],
[currentChild nodeName]];
[properties addObject: flatProperty];
}
}
// while ([children hasChildNodes])
// [properties addObject: [children next]];
}
// NSLog (@"/parseRequestProperties: %@", [NSDate date]);
return properties;
}
- (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
{
id <DOMNodeList> children;
id <DOMElement> node;
NSMutableArray *filters;
NSDictionary *filter;
unsigned int count, max;
// NSLog (@"parseCalendarFilter: %@", [NSDate date]);
filters = [NSMutableArray array];
children = [parentNode getElementsByTagName: @"comp-filter"];
max = [children length];
for (count = 0; count < max; count++)
{
node = [children objectAtIndex: count];
filter = [self _parseCalendarFilter: node];
if (filter)
[filters addObject: filter];
}
// NSLog (@"/parseCalendarFilter: %@", [NSDate date]);
return filters;
}
- (NSString *) _additionalFilterKey: (NSString *) key
value: (NSString *) value
{
NSString *filterString;
if ([value length])
{
if ([value isEqualToString: @"NULL"])
filterString = [NSString stringWithFormat: @"(%@ = '')", key];
else
filterString
= [NSString stringWithFormat: @"(%@ like '%%%@%%')", key, value];
}
else
filterString = [NSString stringWithFormat: @"(%@ != '')", key];
return filterString;
}
- (NSString *) _composeAdditionalFilters: (NSDictionary *) filter
{
NSString *additionalFilter;
NSEnumerator *keys;
NSString *currentKey, *keyField, *filterString;
static NSArray *fields = nil;
NSMutableArray *filters;
#warning the list of fields should be taken from the .ocs description file
if (!fields)
{
fields = [NSArray arrayWithObject: @"c_uid"];
[fields retain];
}
filters = [NSMutableArray new];
keys = [[filter allKeys] objectEnumerator];
while ((currentKey = [keys nextObject]))
{
keyField = [NSString stringWithFormat: @"c_%@", currentKey];
if ([fields containsObject: keyField])
{
filterString = [self _additionalFilterKey: keyField
value: [filter objectForKey: currentKey]];
[filters addObject: filterString];
}
}
if ([filters count])
additionalFilter = [filters componentsJoinedByString: @" and "];
else
additionalFilter = nil;
[filters release];
return additionalFilter;
}
- (NSString *) davCalendarColor
{
NSString *color;
color = [[self calendarColor] uppercaseString];
return [NSString stringWithFormat: @"%@FF", color];
}
- (NSException *) setDavCalendarColor: (NSString *) newColor
{
NSException *error;
NSString *realColor;
if ([newColor length] == 9
&& [newColor hasPrefix: @"#"])
{
realColor = [newColor substringToIndex: 7];
[self setCalendarColor: realColor];
error = nil;
}
else
error = [NSException exceptionWithHTTPStatus: 400
reason: @"Bad color format (should be '#XXXXXXXX')."];
return error;
}
- (NSString *) davCalendarOrder
{
unsigned int order;
order = [[container subFolders] indexOfObject: self];
return [NSString stringWithFormat: @"%d", order];
}
- (NSException *) setDavCalendarOrder: (NSString *) newColor
{
return nil;
}
- (SOGoWebDAVValue *) davCalendarFreeBusySet
{
NSEnumerator *subFolders;
SOGoAppointmentFolder *currentFolder;
NSMutableArray *response;
SOGoWebDAVValue *responseValue;
response = [NSMutableArray new];
subFolders = [[container subFolders] objectEnumerator];
while ((currentFolder = [subFolders nextObject]))
[response addObject: davElementWithContent (@"href", XMLNS_WEBDAV,
[currentFolder davURL])];
responseValue = [davElementWithContent (@"calendar-free-busy-set", XMLNS_CALDAV, response)
asWebDAVValue];
[response release];
return responseValue;
}
/* This method is ignored but we must return a success value. */
- (NSException *) setDavCalendarFreeBusySet: (NSString *) newFreeBusySet
{
return nil;
}
- (void) _appendComponentProperties: (NSString **) properties
matchingFilters: (NSArray *) filters
toResponse: (WOResponse *) response
{
NSArray *apts;
NSDictionary *currentFilter;
NSEnumerator *filterList;
NSString *additionalFilters, *baseURL;
unsigned int count, max;
baseURL = [[self davURL] absoluteString];
filterList = [filters objectEnumerator];
while ((currentFilter = [filterList nextObject]))
{
additionalFilters = [self _composeAdditionalFilters: currentFilter];
// NSLog(@"query");
apts = [self bareFetchFields: reportQueryFields
from: [currentFilter objectForKey: @"start"]
to: [currentFilter objectForKey: @"end"]
title: [currentFilter objectForKey: @"title"]
component: [currentFilter objectForKey: @"name"]
additionalFilters: additionalFilters];
// NSLog(@"adding properties");
max = [apts count];
for (count = 0; count < max; count++)
[self appendObject: [apts objectAtIndex: count]
properties: properties
withBaseURL: baseURL
toComplexResponse: response];
// NSLog(@"done");
}
}
- (id) davCalendarQuery: (id) queryContext
{
WOResponse *r;
id <DOMDocument> document;
id <DOMElement> documentElement;
NSString **properties;
r = [context response];
[r setStatus: 207];
[r setContentEncoding: NSUTF8StringEncoding];
[r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
[r setHeader: @"no-cache" forKey: @"pragma"];
[r setHeader: @"no-cache" forKey: @"cache-control"];
[r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
[r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
@" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
document = [[context request] contentAsDOMDocument];
documentElement = [document documentElement];
properties = [[self _parseRequestedProperties: documentElement]
asPointersOfObjects];
[self _appendComponentProperties: properties
matchingFilters: [self _parseCalendarFilters: documentElement]
toResponse: r];
[r appendContentString:@"</D:multistatus>\r\n"];
free (properties);
return r;
}
- (NSDictionary *) _deduceObjectNamesFromURLs: (NSArray *) urls
{
unsigned int count, max;
NSString *url, *componentURLPath, *cName, *baseURLString;
NSMutableDictionary *cNames;
NSURL *componentURL, *baseURL;
NSArray *urlComponents;
max = [urls count];
cNames = [NSMutableDictionary dictionaryWithCapacity: max];
baseURL = [self davURL];
baseURLString = [baseURL absoluteString];
for (count = 0; count < max; count++)
{
url = [urls objectAtIndex: count];
componentURL = [[NSURL URLWithString: url relativeToURL: baseURL]
standardizedURL];
componentURLPath = [componentURL absoluteString];
if ([componentURLPath rangeOfString: baseURLString].location
!= NSNotFound)
{
urlComponents = [componentURLPath componentsSeparatedByString: @"/"];
cName = [urlComponents objectAtIndex: [urlComponents count] - 1];
[cNames setObject: url forKey: cName];
}
}
return cNames;
}
- (NSArray *) _fetchComponentsWithNames: (NSArray *) cNames
{
NSMutableString *filterString;
NSArray *records;
// NSLog (@"fetchComponentsWithNames");
filterString = [NSMutableString new];
[filterString appendFormat: @"c_name='%@'",
[cNames componentsJoinedByString: @"' OR c_name='"]];
// NSLog (@"fetchComponentsWithNames: query");
records = [self bareFetchFields: reportQueryFields
from: nil
to: nil
title: nil
component: nil
additionalFilters: filterString];
[filterString release];
// NSLog (@"/fetchComponentsWithNames");
return records;
}
#define maxQuerySize 2500
#define baseQuerySize 160
#define idQueryOverhead 13
- (NSArray *) _fetchComponentsMatchingObjectNames: (NSArray *) cNames
{
NSMutableArray *components;
NSArray *records;
NSMutableArray *currentNames;
unsigned int count, max, currentSize, queryNameLength;
NSString *currentName;
// NSLog (@"fetching components matching names");
currentNames = [NSMutableArray new];
currentSize = baseQuerySize;
max = [cNames count];
components = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
currentName = [cNames objectAtIndex: count];
queryNameLength = idQueryOverhead + [currentName length];
if ((currentSize + queryNameLength)
> maxQuerySize)
{
records = [self _fetchComponentsWithNames: currentNames];
[components addObjectsFromArray: records];
[currentNames removeAllObjects];
currentSize = baseQuerySize;
}
[currentNames addObject: currentName];
currentSize += queryNameLength;
}
records = [self _fetchComponentsWithNames: currentNames];
[components addObjectsFromArray: records];
[currentNames release];
// NSLog (@"/fetching components matching names");
return components;
}
- (NSDictionary *) _fetchComponentsMatchingURLs: (NSArray *) urls
{
NSMutableDictionary *components;
NSDictionary *cnames, *record;
NSString *recordURL;
NSArray *records;
unsigned int count, max;
components = [NSMutableDictionary dictionary];
cnames = [self _deduceObjectNamesFromURLs: urls];
records = [self _fetchComponentsMatchingObjectNames: [cnames allKeys]];
max = [records count];
for (count = 0; count < max; count++)
{
record = [records objectAtIndex: count];
recordURL = [cnames objectForKey: [record objectForKey: @"c_name"]];
[components setObject: record forKey: recordURL];
}
return components;
}
- (void) _appendComponentProperties: (NSString **) properties
matchingURLs: (id <DOMNodeList>) refs
toResponse: (WOResponse *) response
{
NSObject <DOMElement> *element;
NSDictionary *currentComponent, *components;
NSString *currentURL, *baseURL;
NSMutableArray *urls;
unsigned int count, max;
baseURL = [[self davURL] absoluteString];
urls = [NSMutableArray new];
max = [refs length];
for (count = 0; count < max; count++)
{
element = [refs objectAtIndex: count];
currentURL = [[element firstChild] nodeValue];
[urls addObject: currentURL];
}
components = [self _fetchComponentsMatchingURLs: urls];
max = [urls count];
// NSLog (@"adding properties with url");
for (count = 0; count < max; count++)
{
currentComponent
= [components objectForKey: [urls objectAtIndex: count]];
if (currentComponent)
[self appendObject: currentComponent
properties: properties
withBaseURL: baseURL
toComplexResponse: response];
else
[self appendMissingObjectRef: currentURL
toComplexResponse: response];
}
// NSLog (@"/adding properties with url");
[urls release];
}
- (id) davCalendarMultiget: (id) queryContext
{
WOResponse *r;
id <DOMDocument> document;
id <DOMElement> documentElement;
NSString **properties;
r = [context response];
[r setStatus: 207];
[r setContentEncoding: NSUTF8StringEncoding];
[r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
[r setHeader: @"no-cache" forKey: @"pragma"];
[r setHeader: @"no-cache" forKey: @"cache-control"];
[r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
[r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
@" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
document = [[context request] contentAsDOMDocument];
documentElement = [document documentElement];
properties = [[self _parseRequestedProperties: documentElement]
asPointersOfObjects];
[self _appendComponentProperties: properties
matchingURLs: [documentElement getElementsByTagName: @"href"]
toResponse: r];
[r appendContentString:@"</D:multistatus>\r\n"];
free (properties);
return r;
}
- (Class) objectClassForContent: (NSString *) content
{
iCalCalendar *calendar;
NSArray *elements;
NSString *firstTag;
Class objectClass;
objectClass = Nil;
calendar = [iCalCalendar parseSingleFromSource: content];
if (calendar)
{
elements = [calendar allObjects];
if ([elements count])
{
firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
if ([firstTag isEqualToString: @"VEVENT"])
objectClass = [SOGoAppointmentObject class];
else if ([firstTag isEqualToString: @"VTODO"])
objectClass = [SOGoTaskObject class];
}
}
return objectClass;
}
- (BOOL) requestNamedIsHandledLater: (NSString *) name
{
return [name isEqualToString: @"OPTIONS"];
}
- (id) lookupComponentByUID: (NSString *) uid
{
NSString *filename;
id component;
filename = [self resourceNameForEventUID: uid];
if (filename)
{
component = [self lookupName: filename inContext: context acquire: NO];
if ([component isKindOfClass: [NSException class]])
component = nil;
}
else
component = nil;
return nil;
}
- (id) lookupName: (NSString *)_key
inContext: (id)_ctx
acquire: (BOOL)_flag
{
id obj;
NSString *url;
BOOL handledLater;
/* first check attributes directly bound to the application */
handledLater = [self requestNamedIsHandledLater: _key];
if (handledLater)
obj = nil;
else
{
obj = [super lookupName:_key inContext:_ctx acquire:NO];
if (!obj)
{
if ([self isValidContentName:_key])
{
url = [[[_ctx request] uri] urlWithoutParameters];
if ([url hasSuffix: @"AsTask"])
obj = [SOGoTaskObject objectWithName: _key
inContainer: self];
else if ([url hasSuffix: @"AsAppointment"])
obj = [SOGoAppointmentObject objectWithName: _key
inContainer: self];
[obj setIsNew: YES];
}
}
if (!obj)
obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
if (obj)
[[SOGoCache sharedCache] registerObject: obj
withName: _key
inContainer: container];
return obj;
}
- (NSDictionary *) freebusyResponseForRecipient: (NSString *) recipient
withUser: (SOGoUser *) user
andCalendarData: (NSString *) calendarData
{
NSDictionary *response;
NSMutableArray *content;
content = [NSMutableArray new];
[content addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, recipient)];
if (user)
{
[content addObject: davElementWithContent (@"request-status", XMLNS_CALDAV,
@"2.0;Success")];
[content addObject: davElementWithContent (@"calendar-data", XMLNS_CALDAV,
calendarData)];
}
else
[content addObject:
davElementWithContent (@"request-status", XMLNS_CALDAV,
@"3.7;Invalid Calendar User")];
response = davElementWithContent (@"response", XMLNS_CALDAV, content);
[content release];
return response;
}
- (NSDictionary *) caldavFreeBusyRequestOnRecipient: (NSString *) recipient
from: (NSCalendarDate *) start
to: (NSCalendarDate *) to
{
LDAPUserManager *um;
SOGoUser *user;
NSString *lRecipient, *login, *calendarData;
SOGoFreeBusyObject *freebusy;
user = nil;
calendarData = nil;
lRecipient = [recipient lowercaseString];
if ([lRecipient hasPrefix: @"mailto:"])
{
um = [LDAPUserManager sharedUserManager];
login = [um getUIDForEmail: [lRecipient substringFromIndex: 7]];
if ([login length])
{
user = [SOGoUser userWithLogin: login roles: nil];
freebusy = [[user homeFolderInContext: context]
freeBusyObject: @"freebusy.ifb"
inContext: context];
calendarData = [freebusy contentAsStringWithMethod: @"REPLY"
from: start to: to];
}
}
return [self freebusyResponseForRecipient: recipient
withUser: user
andCalendarData: calendarData];
}
- (NSDictionary *) caldavFreeBusyRequest: (iCalFreeBusy *) freebusy
from: (NSString *) originator
to: (NSArray *) recipients
{
NSDictionary *responseElement;
NSMutableArray *elements;
NSString *recipient;
NSEnumerator *allRecipients;
NSCalendarDate *startDate, *endDate;
elements = [NSMutableArray new];
[freebusy fillStartDate: &startDate andEndDate: &endDate];
allRecipients = [recipients objectEnumerator];
while ((recipient = [allRecipients nextObject]))
[elements addObject: [self caldavFreeBusyRequestOnRecipient: recipient
from: startDate to: endDate]];
responseElement = davElementWithContent (@"schedule-response",
XMLNS_CALDAV, elements);
[elements release];
return responseElement;
}
#warning we should merge this code with the code from the iTIP interpreter in MailPartViewer
- (NSDictionary *) caldavEventRequest: (iCalEvent *) event
withContent: (NSString *) iCalString
from: (NSString *) originator
to: (NSArray *) recipients
{
NSDictionary *responseElement;
NSArray *elements;
NSString *method, *filename;
SOGoAppointmentObject *apt;
filename = [NSString stringWithFormat: @"%@.ics", [event uid]];
apt = [SOGoAppointmentObject objectWithName: filename
andContent: iCalString
inContainer: self];
method = [[event parent] method];
if ([method isEqualToString: @"REQUEST"])
elements = [apt postCalDAVEventRequestTo: recipients];
else if ([method isEqualToString: @"REPLY"])
elements = [apt postCalDAVEventReplyTo: recipients];
else if ([method isEqualToString: @"CANCEL"])
elements = [apt postCalDAVEventCancelTo: recipients];
else
elements = nil;
if (elements)
responseElement = davElementWithContent (@"schedule-response",
XMLNS_CALDAV, elements);
else
responseElement = nil;
return responseElement;
}
- (WOResponse *) _caldavScheduleResponse: (NSDictionary *) tags
{
WOResponse *response;
response = [context response];
if (tags)
{
[response setStatus: 200];
[response appendContentString:@"<?xml version=\"1.0\""
@" encoding=\"utf-8\"?>\r\n"];
[response setHeader: @"application/xml; charset=utf-8"
forKey: @"Content-Type"];
[response appendContentString:
[tags asWebDavStringWithNamespaces: nil]];
}
else
[response setStatus: 415];
return response;
}
- (WOResponse *) caldavScheduleRequest: (NSString *) iCalString
from: (NSString *) originator
to: (NSArray *) recipients
{
NSString *tag;
iCalCalendar *calendar;
iCalEntityObject *element;
NSDictionary *tags;
#warning needs to handle errors
calendar = [iCalCalendar parseSingleFromSource: iCalString];
element = [[calendar allObjects] objectAtIndex: 0];
tag = [[element tag] uppercaseString];
if ([tag isEqualToString: @"VFREEBUSY"])
tags = [self caldavFreeBusyRequest: (iCalFreeBusy *) element
from: originator to: recipients];
else if ([tag isEqualToString: @"VEVENT"])
tags = [self caldavEventRequest: (iCalEvent *) element
withContent: iCalString
from: originator to: recipients];
else
tags = nil;
return [self _caldavScheduleResponse: tags];
}
- (id) davPOSTRequest: (WORequest *) request
withContentType: (NSString *) cType
inContext: (WOContext *) localContext
{
id obj;
NSString *originator;
NSArray *recipients;
if ([cType hasPrefix: @"text/calendar"])
{
originator = [request headerForKey: @"originator"];
recipients = [[request headerForKey: @"recipient"]
componentsSeparatedByString: @", "];
obj = [self caldavScheduleRequest: [request contentAsString]
from: originator to: recipients];
}
else
obj = [super davPOSTRequest: request withContentType: cType
inContext: localContext];
return obj;
}
- (NSArray *) davComplianceClassesInContext: (id)_ctx
{
NSMutableArray *classes;
NSArray *primaryClasses;
classes = [NSMutableArray new];
[classes autorelease];
primaryClasses = [super davComplianceClassesInContext: _ctx];
if (primaryClasses)
[classes addObjectsFromArray: primaryClasses];
[classes addObject: @"calendar-access"];
[classes addObject: @"calendar-schedule"];
return classes;
}
- (NSArray *) groupDavResourceType
{
return [NSArray arrayWithObjects: @"vevent-collection",
@"vtodo-collection", nil];
}
- (NSArray *) davResourceType
{
static NSArray *colType = nil;
NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
if (!colType)
{
gdRT = [self groupDavResourceType];
gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
XMLNS_GROUPDAV, nil];
gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
XMLNS_GROUPDAV, nil];
cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
colType = [NSArray arrayWithObjects: @"collection", cdCol,
gdVEventCol, gdVTodoCol, nil];
[colType retain];
}
return colType;
}
- (NSString *) davCollectionTag
{
NSArray *records;
GCSFolder *folder;
static EOFetchSpecification *cTagSpec = nil;
EOSortOrdering *ordering;
NSNumber *lastModified;
NSString *cTag;
folder = [self ocsFolder];
if (!cTagSpec)
{
ordering = [EOSortOrdering sortOrderingWithKey: @"c_lastmodified"
selector: EOCompareDescending];
cTagSpec = [EOFetchSpecification
fetchSpecificationWithEntityName: [folder folderName]
qualifier: nil
sortOrderings: [NSArray arrayWithObject: ordering]];
[cTagSpec retain];
}
records = [folder fetchFields: [NSArray arrayWithObject: @"c_lastmodified"]
fetchSpecification: cTagSpec];
if ([records count])
{
lastModified = [[records objectAtIndex: 0] objectForKey: @"c_lastmodified"];
cTag = [lastModified stringValue];
}
else
cTag = @"-1";
return cTag;
}
- (NSString *) davDescription
{
return @"";
}
/* vevent UID handling */
- (NSString *) resourceNameForEventUID: (NSString *) uid
inFolder: (GCSFolder *) folder
{
static NSArray *nameFields = nil;
EOQualifier *qualifier;
NSArray *records;
NSString *filename;
unsigned int count;
filename = nil;
if (!nameFields)
nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
if (uid && folder)
{
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_uid = %@",
uid];
records = [folder fetchFields: nameFields matchingQualifier: qualifier];
count = [records count];
if (count)
{
filename = [[records objectAtIndex:0] valueForKey:@"c_name"];
if (count > 1)
[self errorWithFormat:
@"The storage contains more than file with UID '%@'",
uid];
}
}
return filename;
}
- (NSString *) resourceNameForEventUID: (NSString *) uid
{
/* caches UIDs */
GCSFolder *folder;
NSString *rname;
rname = nil;
if (uid)
{
if (!uidToFilename)
uidToFilename = [NSMutableDictionary new];
rname = [uidToFilename objectForKey: uid];
if (!rname)
{
folder = [self ocsFolder];
rname = [self resourceNameForEventUID: uid inFolder: folder];
if (rname)
[uidToFilename setObject: rname forKey: uid];
}
}
return rname;
}
- (NSArray *) subscriptionRoles
{
return [NSArray arrayWithObjects:
SOGoRole_ObjectCreator, SOGoRole_ObjectEraser,
SOGoCalendarRole_PublicResponder,
SOGoCalendarRole_PublicModifier,
SOGoCalendarRole_PublicViewer,
SOGoCalendarRole_PublicDAndTViewer,
SOGoCalendarRole_PrivateResponder,
SOGoCalendarRole_PrivateModifier,
SOGoCalendarRole_PrivateViewer,
SOGoCalendarRole_PrivateDAndTViewer,
SOGoCalendarRole_ConfidentialResponder,
SOGoCalendarRole_ConfidentialModifier,
SOGoCalendarRole_ConfidentialViewer,
SOGoCalendarRole_ConfidentialDAndTViewer, nil];
}
- (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
forUser: (NSString *) uid
{
NSString *accessRole, *prefix, *currentRole, *suffix;
NSEnumerator *acls;
NSMutableDictionary *userRoles;
accessRole = nil;
if (accessClass == iCalAccessPublic)
prefix = @"Public";
else if (accessClass == iCalAccessPrivate)
prefix = @"Private";
else
prefix = @"Confidential";
userRoles = [aclMatrix objectForKey: uid];
if (!userRoles)
{
userRoles = [NSMutableDictionary dictionaryWithCapacity: 3];
[aclMatrix setObject: userRoles forKey: uid];
}
accessRole = [userRoles objectForKey: prefix];
if (!accessRole)
{
acls = [[self aclsForUser: uid] objectEnumerator];
currentRole = [acls nextObject];
while (currentRole && !accessRole)
if ([currentRole hasPrefix: prefix])
{
suffix = [currentRole substringFromIndex: [prefix length]];
accessRole = [NSString stringWithFormat: @"Component%@", suffix];
}
else
currentRole = [acls nextObject];
if (!accessRole)
accessRole = @"";
[userRoles setObject: accessRole forKey: prefix];
}
return accessRole;
}
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
{
static NSArray *infos = nil; // TODO: move to a plist file
if (!infos)
infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
@"c_isopaque", @"c_status", @"c_cycleinfo", nil];
return [self fetchFields: infos
from: _startDate to: _endDate
title: nil
component: @"vevent"
additionalFilters: nil];
}
- (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
title: (NSString *) title
component: (id) _component
{
return [self fetchCoreInfosFrom: _startDate to: _endDate title: title
component: _component additionalFilters: nil];
}
- (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
title: (NSString *) title
component: (id) _component
additionalFilters: (NSString *) filters
{
static NSArray *infos = nil; // TODO: move to a plist file
if (!infos)
infos = [[NSArray alloc] initWithObjects: @"c_name", @"c_content",
@"c_creationdate", @"c_lastmodified",
@"c_version", @"c_component", @"c_title",
@"c_location", @"c_orgmail", @"c_status",
@"c_classification", @"c_isallday",
@"c_isopaque", @"c_participants", @"c_partmails",
@"c_partstates", @"c_sequence", @"c_priority",
@"c_cycleinfo", @"c_iscycle", nil];
return [self fetchFields: infos from: _startDate to: _endDate title: title
component: _component
additionalFilters: filters];
}
/* URL generation */
- (NSString *) baseURLForAptWithUID: (NSString *)_uid
inContext: (id)_ctx
{
// TODO: who calls this?
NSString *url;
if ([_uid length] == 0)
return nil;
url = [self baseURLInContext:_ctx];
if (![url hasSuffix: @"/"])
url = [url stringByAppendingString: @"/"];
// TODO: this should run a query to determine the uid!
return [url stringByAppendingString:_uid];
}
/* folder management */
- (BOOL) create
{
BOOL rc;
NSUserDefaults *userSettings;
NSMutableDictionary *calendarSettings;
SOGoUser *ownerUser;
rc = [super create];
if (rc)
{
ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]
roles: nil];
userSettings = [ownerUser userSettings];
calendarSettings = [userSettings objectForKey: @"Calendar"];
if (!calendarSettings)
{
calendarSettings = [NSMutableDictionary dictionary];
[userSettings setObject: calendarSettings forKey: @"Calendar"];
}
[userSettings synchronize];
}
return rc;
}
- (id) lookupHomeFolderForUID: (NSString *) _uid
inContext: (id)_ctx
{
// TODO: DUP to SOGoGroupFolder
NSException *error = nil;
NSArray *path;
id ctx, result;
if (![_uid isNotNull])
return nil;
/* create subcontext, so that we don't destroy our environment */
if ((ctx = [context createSubContext]) == nil) {
[self errorWithFormat:@"could not create SOPE subcontext!"];
return nil;
}
/* build path */
path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
/* traverse path */
result = [[ctx application] traversePathArray:path inContext:ctx
error:&error acquire:NO];
if (error != nil) {
[self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
_uid, error];
return nil;
}
[self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
_uid, [path componentsJoinedByString:@"=>"], result];
return result;
}
- (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
{
SOGoFolder *currentContainer;
SOGoAppointmentFolders *parent;
NSException *error;
currentContainer = [[container container] container];
currentContainer = [currentContainer lookupName: uid
inContext: context
acquire: NO];
parent = [currentContainer lookupName: @"Calendar" inContext: context
acquire: NO];
currentContainer = [parent lookupName: @"personal" inContext: context
acquire: NO];
if (!currentContainer)
{
error = [parent newFolderWithName: [parent defaultFolderName]
andNameInContainer: @"personal"];
if (!error)
currentContainer = [parent lookupName: @"personal"
inContext: context
acquire: NO];
}
return (SOGoAppointmentFolder *) currentContainer;
}
- (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
inContext: (id)_ctx
{
/* Note: can return NSNull objects in the array! */
NSMutableArray *folders;
NSEnumerator *e;
NSString *uid, *ownerLogin;
id folder;
ownerLogin = [self ownerInContext: context];
if ([_uids count] == 0) return nil;
folders = [NSMutableArray arrayWithCapacity:16];
e = [_uids objectEnumerator];
while ((uid = [e nextObject]))
{
if ([uid isEqualToString: ownerLogin])
folder = self;
else
{
folder = [self lookupCalendarFolderForUID: uid];
if (![folder isNotNull])
[self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
}
if (folder)
[folders addObject: folder];
}
return folders;
}
- (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
inContext: (id) _ctx
{
/* Note: can return NSNull objects in the array! */
NSMutableArray *objs;
NSEnumerator *e;
NSString *uid;
if ([_uids count] == 0) return nil;
objs = [NSMutableArray arrayWithCapacity:16];
e = [_uids objectEnumerator];
while ((uid = [e nextObject]))
{
id obj;
obj = [self lookupHomeFolderForUID:uid inContext:nil];
if ([obj isNotNull])
{
obj = [obj lookupName: @"freebusy.ifb" inContext: nil acquire: NO];
if ([obj isKindOfClass: [NSException class]])
obj = nil;
}
if (![obj isNotNull])
[self logWithFormat: @"Note: did not find freebusy.ifb for uid: '%@'",
uid];
/* Note: intentionally add 'null' folders to allow a mapping */
if (!obj)
obj = [NSNull null];
[objs addObject: obj];
}
return objs;
}
- (NSArray *) uidsFromICalPersons: (NSArray *) _persons
{
/* Note: can return NSNull objects in the array! */
NSMutableArray *uids;
LDAPUserManager *um;
unsigned i, count;
iCalPerson *person;
NSString *email;
NSString *uid;
if (_persons)
{
count = [_persons count];
uids = [NSMutableArray arrayWithCapacity:count + 1];
um = [LDAPUserManager sharedUserManager];
for (i = 0; i < count; i++)
{
person = [_persons objectAtIndex:i];
email = [person rfc822Email];
if ([email isNotNull])
uid = [um getUIDForEmail:email];
else
uid = nil;
if (!uid)
uid = (NSString *) [NSNull null];
[uids addObject: uid];
}
}
else
uids = nil;
return uids;
}
- (NSArray *) lookupCalendarFoldersForICalPerson: (NSArray *) _persons
inContext: (id) _ctx
{
/* Note: can return NSNull objects in the array! */
NSArray *uids, *folders;
uids = [self uidsFromICalPersons: _persons];
if (uids)
folders = [self lookupCalendarFoldersForUIDs: uids
inContext: _ctx];
else
folders = nil;
return folders;
}
// - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
// inContext: (id)_ctx
// {
// SOGoCustomGroupFolder *folder;
// if (_uids == nil)
// return nil;
// folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
// return [folder autorelease];
// }
// - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
// inContext: (id) _ctx
// {
// SOGoCustomGroupFolder *folder;
// if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
// return nil;
// folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
// if (![folder isNotNull])
// return nil;
// if ([folder isKindOfClass:[NSException class]]) {
// [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
// folder];
// return nil;
// }
// return folder;
// }
/* bulk fetches */
// #warning We only support ONE calendar per user at this time
// - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
// toFolderList: (NSMutableArray *) calendarFolders
// {
// NSEnumerator *keys;
// NSString *currentKey;
// NSMutableDictionary *currentCalendar;
// BOOL firstShouldBeActive;
// unsigned int count;
// firstShouldBeActive = YES;
// keys = [[subscribedFolders allKeys] objectEnumerator];
// currentKey = [keys nextObject];
// count = 1;
// while (currentKey)
// {
// currentCalendar = [NSMutableDictionary new];
// [currentCalendar autorelease];
// [currentCalendar
// setDictionary: [subscribedFolders objectForKey: currentKey]];
// [currentCalendar setObject: currentKey forKey: @"folder"];
// [calendarFolders addObject: currentCalendar];
// if ([[currentCalendar objectForKey: @"active"] boolValue])
// firstShouldBeActive = NO;
// count++;
// currentKey = [keys nextObject];
// }
// return firstShouldBeActive;
// }
// - (NSArray *) calendarFolders
// {
// NSMutableDictionary *userCalendar, *calendarDict;
// NSMutableArray *calendarFolders;
// SOGoUser *calendarUser;
// BOOL firstActive;
// calendarFolders = [NSMutableArray new];
// [calendarFolders autorelease];
// calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
// roles: nil];
// userCalendar = [NSMutableDictionary new];
// [userCalendar autorelease];
// [userCalendar setObject: @"/" forKey: @"folder"];
// [userCalendar setObject: @"Calendar" forKey: @"displayName"];
// [calendarFolders addObject: userCalendar];
// calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
// firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
// firstActive = ([self _appendSubscribedFolders:
// [calendarDict objectForKey: @"SubscribedFolders"]
// toFolderList: calendarFolders]
// || firstActive);
// [userCalendar setObject: [NSNumber numberWithBool: firstActive]
// forKey: @"active"];
// return calendarFolders;
// }
// - (NSArray *) fetchContentObjectNames
// {
// NSMutableArray *objectNames;
// NSArray *records;
// NSCalendarDate *today, *startDate, *endDate;
// #warning this should be user-configurable
// objectNames = [NSMutableArray array];
// today = [[NSCalendarDate calendarDate] beginOfDay];
// [today setTimeZone: timeZone];
// startDate = [today dateByAddingYears: 0 months: 0 days: -1
// hours: 0 minutes: 0 seconds: 0];
// endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
// hours: 0 minutes: 0 seconds: 0];
// records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
// from: startDate to: endDate
// component: @"vevent"];
// [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
// records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
// from: startDate to: endDate
// component: @"vtodo"];
// [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
// return objectNames;
// }
/* folder type */
- (NSString *) folderType
{
return @"Appointment";
}
- (NSString *) outlookFolderClass
{
return @"IPF.Appointment";
}
- (BOOL) isActive
{
NSUserDefaults *settings;
NSArray *inactiveFolders;
settings = [[context activeUser] userSettings];
inactiveFolders
= [[settings objectForKey: @"Calendar"] objectForKey: @"InactiveFolders"];
return (![inactiveFolders containsObject: nameInContainer]);
}
@end /* SOGoAppointmentFolder */