/* 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 #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import // #import #import #import #import #import // #import #import #import #import #import #import "SOGoAppointmentObject.h" #import "SOGoAppointmentFolders.h" #import "SOGoTaskObject.h" #import "SOGoAppointmentFolder.h" #define defaultColor @"#AAAAAA" #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY @interface NSDate(UsedPrivates) - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval; @end #endif @implementation SOGoAppointmentFolder static NGLogger *logger = nil; static NSNumber *sharedYes = nil; + (int) version { return [super version] + 1 /* v1 */; } + (void) initialize { NGLoggerManager *lm; static BOOL didInit = NO; // SoClassSecurityInfo *securityInfo; if (didInit) return; didInit = YES; 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 *webdavAclManager = nil; NSString *nsCD, *nsD, *nsI; if (!webdavAclManager) { nsD = @"DAV:"; nsCD = @"urn:ietf:params:xml:ns:caldav"; nsI = @"urn:inverse:params:xml:ns:inverse-dav"; webdavAclManager = [SOGoWebDAVAclManager new]; [webdavAclManager registerDAVPermission: davElement (@"read", nsD) abstract: YES withEquivalent: SoPerm_WebDAVAccess asChildOf: davElement (@"all", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"read-current-user-privilege-set", nsD) abstract: YES withEquivalent: SoPerm_WebDAVAccess asChildOf: davElement (@"read", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"read-free-busy", nsD) abstract: NO withEquivalent: SoPerm_AccessContentsInformation asChildOf: davElement (@"read", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"write", nsD) abstract: YES withEquivalent: nil asChildOf: davElement (@"all", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"bind", nsD) abstract: NO withEquivalent: SoPerm_AddDocumentsImagesAndFiles asChildOf: davElement (@"write", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"bind", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-post", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-post-vevent", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-post", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-post-vtodo", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-post", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-post-vjournal", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-post", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-post-vfreebusy", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-post", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-deliver", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-deliver-vevent", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-deliver", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-deliver-vtodo", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-deliver", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-deliver-vjournal", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-deliver", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-deliver-vfreebusy", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-deliver", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-respond", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-respond-vevent", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-respond", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"schedule-respond-vtodo", nsCD) abstract: NO withEquivalent: nil asChildOf: davElement (@"schedule-respond", nsCD)]; [webdavAclManager registerDAVPermission: davElement (@"unbind", nsD) abstract: NO withEquivalent: SoPerm_DeleteObjects asChildOf: davElement (@"write", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"write-properties", nsD) abstract: YES withEquivalent: SoPerm_ChangePermissions /* hackish */ asChildOf: davElement (@"write", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"write-content", nsD) abstract: YES withEquivalent: nil asChildOf: davElement (@"write", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"admin", nsI) abstract: YES withEquivalent: nil asChildOf: davElement (@"all", nsD)]; [webdavAclManager registerDAVPermission: davElement (@"read-acl", nsD) abstract: YES withEquivalent: SOGoPerm_ReadAcls asChildOf: davElement (@"admin", nsI)]; [webdavAclManager registerDAVPermission: davElement (@"write-acl", nsD) abstract: YES withEquivalent: SoPerm_ChangePermissions asChildOf: davElement (@"admin", nsI)]; } return webdavAclManager; } - (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]; } - (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]; } /* 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; } /* name lookup */ - (void) _appendPropstat: (NSDictionary *) propstat toResponse: (WOResponse *) r { NSArray *properties; unsigned int count, max; [r appendContentString: @""]; properties = [propstat objectForKey: @"properties"]; max = [properties count]; for (count = 0; count < max; count++) [r appendContentString: [properties objectAtIndex: count]]; [r appendContentString: @""]; [r appendContentString: [propstat objectForKey: @"status"]]; [r appendContentString: @""]; } #warning we should use the EOFetchSpecification for that!!! (see doPROPFIND:) #warning components in calendar-data query are ignored - (NSString *) _property: (NSObject *) property ofObject: (SOGoObject *) sogoObject { NSDictionary *map; NSString *value, *propName, *methodName; SEL methodSel; value = nil; propName = [NSString stringWithFormat: @"{%@}%@", [property namespaceURI], [property nodeName]]; map = [[self class] defaultWebDAVAttributeMap]; methodName = [map objectForKey: propName]; if (methodName) { methodSel = NSSelectorFromString(methodName); if ([sogoObject respondsToSelector: methodSel]) value = [[sogoObject performSelector: methodSel] stringByEscapingXMLString]; } return value; } - (NSString *) _namespaceRep: (NSString *) namespace { NSString *rep; if ([namespace isEqualToString: @"urn:ietf:params:xml:ns:caldav"]) rep = @"C"; else rep = @"D"; return rep; } - (NSString *) _nodeTag: (NSObject *) property { NSMutableString *nodeTag; NSString *nsRep; nodeTag = [NSMutableString string]; nsRep = [self _namespaceRep: [property namespaceURI]]; if (nsRep) [nodeTag appendFormat: @"%@:", nsRep]; [nodeTag appendString: [property nodeName]]; return nodeTag; } - (NSString *) _representProperty: (NSDictionary *) property { NSMutableString *propertyValue; NSString *content, *nodeTag; propertyValue = [NSMutableString string]; nodeTag = [self _nodeTag: [property objectForKey: @"property"]]; content = [property objectForKey: @"content"]; if (content) [propertyValue appendFormat: @"<%@>%@", nodeTag, content, nodeTag]; else [propertyValue appendFormat: @"<%@/>", nodeTag]; return propertyValue; } - (NSArray *) _properties: (NSArray *) properties ofObject: (NSDictionary *) object { NSMutableArray *values; NSEnumerator *list; NSObject *currentProperty; NSMutableDictionary *currentValue; SOGoObject *sogoObject; NSString *content; SoSecurityManager *mgr; values = [NSMutableArray array]; #warning this check should be done directly in the query... we should fix this sometime mgr = [SoSecurityManager sharedSecurityManager]; sogoObject = [self lookupName: [object objectForKey: @"c_name"] inContext: context acquire: NO]; if (!([mgr validatePermission: SOGoPerm_AccessObject onObject: sogoObject inContext: context])) { list = [properties objectEnumerator]; while ((currentProperty = [list nextObject])) { currentValue = [NSMutableDictionary dictionary]; [currentValue setObject: currentProperty forKey: @"property"]; content = [self _property: currentProperty ofObject: sogoObject]; if (content) [currentValue setObject: content forKey: @"content"]; [values addObject: currentValue]; } } return values; } - (NSArray *) _propstats: (NSArray *) properties ofObject: (NSDictionary *) object { NSMutableArray *propstats, *properties200, *properties404; NSEnumerator *values; NSDictionary *currentProperty; NSString *content, *propertyValue; propstats = [NSMutableArray array]; properties200 = [NSMutableArray new]; properties404 = [NSMutableArray new]; values = [[self _properties: properties ofObject: object] objectEnumerator]; while ((currentProperty = [values nextObject])) { content = [currentProperty objectForKey: @"content"]; propertyValue = [self _representProperty: currentProperty]; if (content) [properties200 addObject: propertyValue]; else [properties404 addObject: propertyValue]; } if ([properties200 count]) { [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: properties200, @"properties", @"HTTP/1.1 200 OK", @"status", nil]]; [properties200 autorelease]; } else [properties200 release]; if ([properties404 count]) { [propstats addObject: [NSDictionary dictionaryWithObjectsAndKeys: properties404, @"properties", @"HTTP/1.1 404 Not Found", @"status", nil]]; [properties404 autorelease]; } else [properties404 release]; return propstats; } #warning We need to use the new DAV utilities here... - (void) appendObject: (NSDictionary *) object properties: (NSArray *) properties withBaseURL: (NSString *) baseURL toComplexResponse: (WOResponse *) r { NSEnumerator *propstats; NSDictionary *propstat; [r appendContentString: @""]; [r appendContentString: baseURL]; if (![baseURL hasSuffix: @"/"]) [r appendContentString: @"/"]; [r appendContentString: [object objectForKey: @"c_name"]]; [r appendContentString: @""]; propstats = [[self _propstats: properties ofObject: object] objectEnumerator]; while ((propstat = [propstats nextObject])) [self _appendPropstat: propstat toResponse: r]; [r appendContentString: @"\r\n"]; } - (void) appendMissingObjectRef: (NSString *) href toComplexResponse: (WOResponse *) r { [r appendContentString: @""]; [r appendContentString: href]; [r appendContentString: @"HTTP/1.1 404 Not Found\r\n"]; } - (void) _appendTimeRange: (id ) 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 ) propFilter toFilter: (NSMutableDictionary *) filter { NSString *propName, *textMatch; id 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 ) filterElement { NSMutableDictionary *filterData; id parentNode; id elements; NSString *componentName; parentNode = (id ) [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 ) parentNode { NSMutableArray *properties; NSObject *propList, *children; NSObject *currentChild; unsigned int count, max, count2, max2; 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)]) [properties addObject: currentChild]; } // while ([children hasChildNodes]) // [properties addObject: [children next]]; } return properties; } - (NSArray *) _parseCalendarFilters: (id ) parentNode { id children; id node; NSMutableArray *filters; NSDictionary *filter; unsigned int count, max; 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]; } 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; } - (void) _appendComponentProperties: (NSArray *) properties matchingFilters: (NSArray *) filters toResponse: (WOResponse *) response { NSArray *apts; NSDictionary *currentFilter, *appointment; NSEnumerator *appointments, *filterList; NSString *baseURL, *additionalFilters; baseURL = [self baseURLInContext: context]; filterList = [filters objectEnumerator]; while ((currentFilter = [filterList nextObject])) { additionalFilters = [self _composeAdditionalFilters: currentFilter]; apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"] to: [currentFilter objectForKey: @"end"] title: [currentFilter objectForKey: @"title"] component: [currentFilter objectForKey: @"name"] additionalFilters: additionalFilters]; appointments = [apts objectEnumerator]; while ((appointment = [appointments nextObject])) [self appendObject: appointment properties: properties withBaseURL: baseURL toComplexResponse: response]; } } #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 - (NSDictionary *) _componentMatchingURL: (NSString *) url inBaseURL: (NSString *) baseURL { NSDictionary *component; NSURL *componentURL, *realBaseURL; NSArray *urlComponents; NSString *componentURLPath, *cName; component = nil; realBaseURL = [NSURL URLWithString: baseURL]; if (realBaseURL) /* url has a host part */ { componentURL = [[NSURL URLWithString: url relativeToURL: realBaseURL] standardizedURL]; componentURLPath = [componentURL absoluteString]; if ([componentURLPath rangeOfString: [realBaseURL absoluteString]].location != NSNotFound) { urlComponents = [componentURLPath componentsSeparatedByString: @"/"]; cName = [urlComponents objectAtIndex: [urlComponents count] - 1]; component = [NSDictionary dictionaryWithObject: cName forKey: @"c_name"]; } } else { if ([url hasPrefix: baseURL]) { urlComponents = [[url stringByDeletingPrefix: baseURL] componentsSeparatedByString: @"/"]; cName = [urlComponents objectAtIndex: [urlComponents count] - 1]; component = [NSDictionary dictionaryWithObject: cName forKey: @"c_name"]; } } return component; } - (void) _appendComponentProperties: (NSArray *) properties matchingURLs: (id ) refs toResponse: (WOResponse *) response { NSObject *element; NSDictionary *currentComponent; NSString *baseURL, *currentURL; unsigned int count, max; baseURL = [self baseURLInContext: context]; max = [refs length]; for (count = 0; count < max; count++) { element = [refs objectAtIndex: count]; currentURL = [[element firstChild] nodeValue]; currentComponent = [self _componentMatchingURL: currentURL inBaseURL: baseURL]; if (currentComponent) [self appendObject: currentComponent properties: properties withBaseURL: baseURL toComplexResponse: response]; else [self appendMissingObjectRef: currentURL toComplexResponse: response]; } } - (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; } - (id) davCalendarQuery: (id) queryContext { WOResponse *r; id document; id documentElement; 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:@"\r\n"]; [r appendContentString: @"\r\n"]; document = [[context request] contentAsDOMDocument]; documentElement = [document documentElement]; [self _appendComponentProperties: [self _parseRequestedProperties: documentElement] matchingFilters: [self _parseCalendarFilters: documentElement] toResponse: r]; [r appendContentString:@"\r\n"]; return r; } - (id) davCalendarMultiget: (id) queryContext { WOResponse *r; id document; id documentElement; 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:@"\r\n"]; [r appendContentString: @"\r\n"]; document = [[context request] contentAsDOMDocument]; documentElement = [document documentElement]; [self _appendComponentProperties: [self _parseRequestedProperties: documentElement] matchingURLs: [documentElement getElementsByTagName: @"href"] toResponse: r]; [r appendContentString:@"\r\n"]; 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; } - (id) deduceObjectForName: (NSString *)_key inContext: (id)_ctx { WORequest *request; NSString *method; Class objectClass; id obj; request = [_ctx request]; method = [request method]; if ([method isEqualToString: @"PUT"]) objectClass = [self objectClassForContent: [request contentAsString]]; else objectClass = [self objectClassForResourceNamed: _key]; if (objectClass) obj = [objectClass objectWithName: _key inContainer: self]; else obj = nil; return obj; } - (BOOL) requestNamedIsHandledLater: (NSString *) name { return [name isEqualToString: @"OPTIONS"]; } - (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]; else obj = [self deduceObjectForName: _key inContext: _ctx]; } } if (!obj) obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */]; } if (obj) [[SOGoCache sharedCache] registerObject: obj withName: _key inContainer: container]; 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: @"access-control"]; [classes addObject: @"calendar-access"]; 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; } /* vevent UID handling */ - (NSString *) resourceNameForEventUID: (NSString *)_u inFolder: (GCSFolder *)_f { static NSArray *nameFields = nil; EOQualifier *qualifier; NSArray *records; if (![_u isNotNull]) return nil; if (_f == nil) { [self errorWithFormat:@"(%s): missing folder for fetch!", __PRETTY_FUNCTION__]; return nil; } if (nameFields == nil) nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil]; qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u]; records = [_f fetchFields: nameFields matchingQualifier: qualifier]; if ([records count] == 1) return [[records objectAtIndex:0] valueForKey:@"c_name"]; if ([records count] == 0) return nil; [self errorWithFormat: @"The storage contains more than file with the same UID!"]; return [[records objectAtIndex:0] valueForKey:@"c_name"]; } - (NSString *) resourceNameForEventUID: (NSString *) _uid { /* caches UIDs */ GCSFolder *folder; NSString *rname; if (![_uid isNotNull]) return nil; rname = [uidToFilename objectForKey:_uid]; if (rname) { if (![rname isNotNull]) rname = nil; return rname; } if ((folder = [self ocsFolder]) == nil) { [self errorWithFormat:@"(%s): missing folder for fetch!", __PRETTY_FUNCTION__]; return nil; } if (uidToFilename == nil) uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16]; if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil) [uidToFilename setObject:[NSNull null] forKey:_uid]; else [uidToFilename setObject:rname forKey:_uid]; return rname; } - (Class) objectClassForResourceNamed: (NSString *) name { EOQualifier *qualifier; NSArray *records; NSString *component; Class objectClass; qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name]; records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"] matchingQualifier: qualifier]; if ([records count]) { component = [[records objectAtIndex:0] valueForKey: @"c_component"]; if ([component isEqualToString: @"vevent"]) objectClass = [SOGoAppointmentObject class]; else if ([component isEqualToString: @"vtodo"]) objectClass = [SOGoTaskObject class]; else objectClass = Nil; } else objectClass = Nil; return objectClass; } /* fetching */ - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record fetchRange: (NGCalendarDateRange *) _r { NSMutableDictionary *md; id tmp; md = [[_record mutableCopy] autorelease]; if ((tmp = [_record objectForKey:@"c_startdate"])) { tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: (NSTimeInterval)[tmp unsignedIntValue]]; [tmp setTimeZone: timeZone]; if (tmp) [md setObject:tmp forKey:@"startDate"]; [tmp release]; } else [self logWithFormat:@"missing 'startdate' in record?"]; if ((tmp = [_record objectForKey:@"c_enddate"])) { tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: (NSTimeInterval)[tmp unsignedIntValue]]; [tmp setTimeZone: timeZone]; if (tmp) [md setObject:tmp forKey:@"endDate"]; [tmp release]; } else [self logWithFormat:@"missing 'enddate' in record?"]; return md; } - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record cycleRange: (NGCalendarDateRange *) _r { NSMutableDictionary *md; 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"]; [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"]; tmp = [_r endDate]; [tmp setTimeZone: timeZone]; [md setObject:tmp forKey:@"endDate"]; [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"]; 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] fetchRange: r]; if (row) [ma addObject: row]; } } else ma = nil; return ma; } - (void) _flattenCycleRecord: (NSDictionary *) _row forRange: (NGCalendarDateRange *) _r intoArray: (NSMutableArray *) _ma { NSMutableDictionary *row, *fixedRow; NSDictionary *cycleinfo; NSCalendarDate *startDate, *endDate; NGCalendarDateRange *fir, *rRange; NSArray *rules, *exRules, *exDates, *ranges; unsigned i, count; NSString *content; 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 fetchRange: _r]; [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) [_ma addObject:fixedRow]; } } - (NSArray *) fixupCyclicRecords: (NSArray *) _records fetchRange: (NGCalendarDateRange *) _r { // TODO: is the result supposed to be sorted by date? NSMutableArray *ma; NSDictionary *row; unsigned int i, count; count = [_records count]; ma = [NSMutableArray arrayWithCapacity: count]; if (count > 0) for (i = 0; i < count; i++) { row = [_records objectAtIndex: i]; [self _flattenCycleRecord: row forRange: _r intoArray: ma]; } return ma; } - (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 ([[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 *) 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; } - (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; 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 addObject: @"c_uid"]; [fields addObject: @"c_startdate"]; [fields addObject: @"c_enddate"]; 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 = [NSMutableArray arrayWithCapacity: [records count]]; [ma addObjectsFromArray: 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"]; return ma; } - (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", 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_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", 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; if (_persons == nil) return nil; count = [_persons count]; uids = [NSMutableArray arrayWithCapacity:count + 1]; um = [LDAPUserManager sharedUserManager]; for (i = 0; i < count; i++) { iCalPerson *person; NSString *email; NSString *uid; 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]; } return uids; } - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons inContext: (id) _ctx { /* Note: can return NSNull objects in the array! */ NSArray *uids; if ((uids = [self uidsFromICalPersons:_persons]) == nil) return nil; return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx]; } // - (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 */ - (NSArray *) fetchAllSOGoAppointments { /* Note: very expensive method, do not use unless absolutely required. returns an array of SOGoAppointment objects. Note that we can leave out the filenames, supposed to be stored in the 'uid' field of the iCalendar object! */ NSMutableArray *events; NSDictionary *files; NSEnumerator *contents; NSString *content; /* fetch all raw contents */ files = [self fetchContentStringsAndNamesOfAllObjects]; if (![files isNotNull]) return nil; if ([files isKindOfClass:[NSException class]]) return (id)files; /* transform to SOGo appointments */ events = [NSMutableArray arrayWithCapacity:[files count]]; contents = [files objectEnumerator]; while ((content = [contents nextObject]) != nil) [events addObject: [iCalCalendar parseSingleFromSource: content]]; return events; } // #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; // } - (NSArray *) fetchContentObjectNames { static NSArray *cNameField = nil; if (!cNameField) cNameField = [[NSArray alloc] initWithObjects: @"c_name", nil]; return [[self fetchFields: cNameField from: nil to: nil title: nil component: nil additionalFilters: nil] objectsForKey: @"c_name"]; } /* 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 */