2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #import <Foundation/NSCalendarDate.h>
23 #import <Foundation/NSEnumerator.h>
24 #import <Foundation/NSValue.h>
26 #import <NGObjWeb/NSException+HTTP.h>
27 #import <NGObjWeb/SoObject+SoDAV.h>
28 #import <NGObjWeb/WOContext+SoObjects.h>
29 #import <NGObjWeb/WOMessage.h>
30 #import <NGObjWeb/WORequest.h>
31 #import <NGObjWeb/WOResponse.h>
32 #import <NGExtensions/NGLoggerManager.h>
33 #import <NGExtensions/NSString+misc.h>
34 #import <GDLContentStore/GCSFolder.h>
35 #import <DOM/DOMProtocols.h>
36 #import <EOControl/EOQualifier.h>
37 #import <NGCards/iCalDateTime.h>
38 #import <NGCards/iCalPerson.h>
39 #import <NGCards/iCalRecurrenceCalculator.h>
40 #import <NGCards/NSString+NGCards.h>
41 #import <NGExtensions/NGCalendarDateRange.h>
42 #import <NGExtensions/NSNull+misc.h>
43 #import <NGExtensions/NSObject+Logs.h>
44 #import <SaxObjC/SaxObjC.h>
45 #import <SaxObjC/XMLNamespaces.h>
47 // #import <NGObjWeb/SoClassSecurityInfo.h>
48 #import <SOGo/SOGoCustomGroupFolder.h>
49 #import <SOGo/LDAPUserManager.h>
50 #import <SOGo/SOGoPermissions.h>
51 #import <SOGo/NSString+Utilities.h>
52 #import <SOGo/SOGoUser.h>
54 #import "SOGoAppointmentObject.h"
55 #import "SOGoTaskObject.h"
57 #import "SOGoAppointmentFolder.h"
59 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
60 @interface NSDate(UsedPrivates)
61 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
65 @implementation SOGoAppointmentFolder
67 static NGLogger *logger = nil;
68 static NSNumber *sharedYes = nil;
72 return [super version] + 1 /* v1 */;
78 static BOOL didInit = NO;
79 // SoClassSecurityInfo *securityInfo;
84 NSAssert2([super version] == 0,
85 @"invalid superclass (%@) version %i !",
86 NSStringFromClass([self superclass]), [super version]);
88 lm = [NGLoggerManager defaultLoggerManager];
89 logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
91 // securityInfo = [self soClassSecurityInfo];
92 // [securityInfo declareRole: SOGoRole_Delegate
93 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
94 // [securityInfo declareRole: SOGoRole_Delegate
95 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
96 // [securityInfo declareRoles: [NSArray arrayWithObjects:
98 // SOGoRole_Assistant, nil]
99 // asDefaultForPermission: SoPerm_View];
101 sharedYes = [[NSNumber numberWithBool: YES] retain];
104 - (id) initWithName: (NSString *) name
105 inContainer: (id) newContainer
107 if ((self = [super initWithName: name inContainer: newContainer]))
109 timeZone = [[context activeUser] timeZone];
117 [uidToFilename release];
128 - (BOOL) folderIsMandatory
135 - (NSArray *) calendarUIDs
137 /* this is used for group calendars (this folder just returns itself) */
140 s = [[self container] nameInContainer];
141 // [self logWithFormat:@"CAL UID: %@", s];
142 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
147 - (BOOL) isValidAppointmentName: (NSString *)_key
149 return ([_key length] != 0);
152 - (void) appendObject: (NSDictionary *) object
153 withBaseURL: (NSString *) baseURL
154 toREPORTResponse: (WOResponse *) r
156 SOGoCalendarComponent *component;
157 Class componentClass;
158 NSString *name, *etagLine, *calString;
160 name = [object objectForKey: @"c_name"];
162 if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
163 componentClass = [SOGoAppointmentObject class];
165 componentClass = [SOGoTaskObject class];
167 component = [componentClass objectWithName: name inContainer: self];
169 [r appendContentString: @" <D:response>\r\n"];
170 [r appendContentString: @" <D:href>"];
171 [r appendContentString: baseURL];
172 if (![baseURL hasSuffix: @"/"])
173 [r appendContentString: @"/"];
174 [r appendContentString: name];
175 [r appendContentString: @"</D:href>\r\n"];
177 [r appendContentString: @" <D:propstat>\r\n"];
178 [r appendContentString: @" <D:prop>\r\n"];
179 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
180 [component davEntityTag]];
181 [r appendContentString: etagLine];
182 [r appendContentString: @" </D:prop>\r\n"];
183 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
184 [r appendContentString: @" </D:propstat>\r\n"];
185 [r appendContentString: @" <C:calendar-data>"];
186 calString = [[component contentAsString] stringByEscapingXMLString];
187 [r appendContentString: calString];
188 [r appendContentString: @"</C:calendar-data>\r\n"];
189 [r appendContentString: @" </D:response>\r\n"];
192 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
193 toFilter: (NSMutableDictionary *) filter
195 NSCalendarDate *parsedDate;
197 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
198 [filter setObject: parsedDate forKey: @"start"];
199 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
200 [filter setObject: parsedDate forKey: @"end"];
203 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
205 NSMutableDictionary *filterData;
206 id <DOMNode> parentNode;
207 id <DOMNodeList> ranges;
208 NSString *componentName;
210 parentNode = [filterElement parentNode];
211 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
212 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
214 componentName = [[filterElement attribute: @"name"] lowercaseString];
215 filterData = [NSMutableDictionary new];
216 [filterData autorelease];
217 [filterData setObject: componentName forKey: @"name"];
218 ranges = [filterElement getElementsByTagName: @"time-range"];
220 [self _appendTimeRange: [ranges objectAtIndex: 0]
221 toFilter: filterData];
229 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
231 NSEnumerator *children;
233 NSMutableArray *filters;
234 NSDictionary *filter;
236 filters = [NSMutableArray new];
238 children = [[parentNode getElementsByTagName: @"comp-filter"]
240 node = [children nextObject];
243 filter = [self _parseCalendarFilter: node];
245 [filters addObject: filter];
246 node = [children nextObject];
252 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
253 toResponse: (WOResponse *) response
256 unsigned int count, max;
257 NSDictionary *currentFilter, *appointment;
258 NSEnumerator *appointments;
261 baseURL = [self baseURLInContext: context];
263 max = [filters count];
264 for (count = 0; count < max; count++)
266 currentFilter = [filters objectAtIndex: 0];
267 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
268 to: [currentFilter objectForKey: @"end"]
269 component: [currentFilter objectForKey: @"name"]];
270 appointments = [apts objectEnumerator];
271 appointment = [appointments nextObject];
274 [self appendObject: appointment
276 toREPORTResponse: response];
277 appointment = [appointments nextObject];
282 - (NSArray *) davNamespaces
284 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
287 - (id) davCalendarQuery: (id) queryContext
291 id <DOMDocument> document;
293 r = [context response];
295 [r setContentEncoding: NSUTF8StringEncoding];
296 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
297 [r setHeader: @"no-cache" forKey: @"pragma"];
298 [r setHeader: @"no-cache" forKey: @"cache-control"];
299 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
300 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
301 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
303 document = [[context request] contentAsDOMDocument];
304 filters = [self _parseCalendarFilters: [document documentElement]];
305 [self _appendComponentsMatchingFilters: filters
307 [r appendContentString:@"</D:multistatus>\r\n"];
312 - (Class) objectClassForContent: (NSString *) content
314 iCalCalendar *calendar;
321 calendar = [iCalCalendar parseSingleFromSource: content];
324 elements = [calendar allObjects];
325 if ([elements count])
327 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
328 if ([firstTag isEqualToString: @"VEVENT"])
329 objectClass = [SOGoAppointmentObject class];
330 else if ([firstTag isEqualToString: @"VTODO"])
331 objectClass = [SOGoTaskObject class];
338 - (id) deduceObjectForName: (NSString *)_key
346 request = [_ctx request];
347 method = [request method];
348 if ([method isEqualToString: @"PUT"])
349 objectClass = [self objectClassForContent: [request contentAsString]];
351 objectClass = [self objectClassForResourceNamed: _key];
354 obj = [objectClass objectWithName: _key inContainer: self];
361 - (BOOL) requestNamedIsHandledLater: (NSString *) name
363 return [name isEqualToString: @"OPTIONS"];
366 - (id) lookupName: (NSString *)_key
374 /* first check attributes directly bound to the application */
375 handledLater = [self requestNamedIsHandledLater: _key];
380 obj = [super lookupName:_key inContext:_ctx acquire:NO];
383 if ([self isValidAppointmentName:_key])
385 url = [[[_ctx request] uri] urlWithoutParameters];
386 if ([url hasSuffix: @"AsTask"])
387 obj = [SOGoTaskObject objectWithName: _key
389 else if ([url hasSuffix: @"AsAppointment"])
390 obj = [SOGoAppointmentObject objectWithName: _key
393 obj = [self deduceObjectForName: _key
398 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
404 - (NSArray *) davComplianceClassesInContext: (id)_ctx
406 NSMutableArray *classes;
407 NSArray *primaryClasses;
409 classes = [NSMutableArray new];
410 [classes autorelease];
412 primaryClasses = [super davComplianceClassesInContext: _ctx];
414 [classes addObjectsFromArray: primaryClasses];
415 [classes addObject: @"access-control"];
416 [classes addObject: @"calendar-access"];
421 - (NSArray *) groupDavResourceType
423 return [NSArray arrayWithObjects: @"vevent-collection",
424 @"vtodo-collection", nil];
427 - (NSArray *) davResourceType
429 static NSArray *colType = nil;
430 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
434 gdRT = [self groupDavResourceType];
435 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
436 XMLNS_GROUPDAV, nil];
437 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
438 XMLNS_GROUPDAV, nil];
439 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
440 colType = [NSArray arrayWithObjects: @"collection", cdCol,
441 gdVEventCol, gdVTodoCol, nil];
448 /* vevent UID handling */
450 - (NSString *) resourceNameForEventUID: (NSString *)_u
451 inFolder: (GCSFolder *)_f
453 static NSArray *nameFields = nil;
454 EOQualifier *qualifier;
457 if (![_u isNotNull]) return nil;
459 [self errorWithFormat:@"(%s): missing folder for fetch!",
460 __PRETTY_FUNCTION__];
464 if (nameFields == nil)
465 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
467 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
468 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
470 if ([records count] == 1)
471 return [[records objectAtIndex:0] valueForKey:@"c_name"];
472 if ([records count] == 0)
475 [self errorWithFormat:
476 @"The storage contains more than file with the same UID!"];
477 return [[records objectAtIndex:0] valueForKey:@"c_name"];
480 - (NSString *) resourceNameForEventUID: (NSString *) _uid
486 if (![_uid isNotNull])
488 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
489 return [rname isNotNull] ? rname : nil;
491 if ((folder = [self ocsFolder]) == nil) {
492 [self errorWithFormat:@"(%s): missing folder for fetch!",
493 __PRETTY_FUNCTION__];
497 if (uidToFilename == nil)
498 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
500 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
501 [uidToFilename setObject:[NSNull null] forKey:_uid];
503 [uidToFilename setObject:rname forKey:_uid];
508 - (Class) objectClassForResourceNamed: (NSString *) name
510 EOQualifier *qualifier;
515 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
516 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
517 matchingQualifier: qualifier];
521 component = [[records objectAtIndex:0] valueForKey: @"c_component"];
522 if ([component isEqualToString: @"vevent"])
523 objectClass = [SOGoAppointmentObject class];
524 else if ([component isEqualToString: @"vtodo"])
525 objectClass = [SOGoTaskObject class];
537 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
538 fetchRange: (NGCalendarDateRange *) _r
540 NSMutableDictionary *md;
543 md = [[_record mutableCopy] autorelease];
545 if ((tmp = [_record objectForKey:@"c_startdate"])) {
546 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
547 (NSTimeInterval)[tmp unsignedIntValue]];
548 [tmp setTimeZone: timeZone];
549 if (tmp) [md setObject:tmp forKey:@"startDate"];
553 [self logWithFormat:@"missing 'startdate' in record?"];
555 if ((tmp = [_record objectForKey:@"c_enddate"])) {
556 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
557 (NSTimeInterval)[tmp unsignedIntValue]];
558 [tmp setTimeZone: timeZone];
559 if (tmp) [md setObject:tmp forKey:@"endDate"];
563 [self logWithFormat:@"missing 'enddate' in record?"];
568 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
569 cycleRange: (NGCalendarDateRange *) _r
571 NSMutableDictionary *md;
574 md = [[_record mutableCopy] autorelease];
577 tmp = [_r startDate];
578 [tmp setTimeZone: timeZone];
579 [md setObject:tmp forKey:@"startDate"];
581 [tmp setTimeZone: timeZone];
582 [md setObject:tmp forKey:@"endDate"];
587 - (NSArray *) fixupRecords: (NSArray *) records
588 fetchRange: (NGCalendarDateRange *) r
590 // TODO: is the result supposed to be sorted by date?
593 id row; // TODO: what is the type of the record?
597 max = [records count];
598 ma = [NSMutableArray arrayWithCapacity: max];
599 for (count = 0; count < max; count++)
601 row = [self fixupRecord: [records objectAtIndex: count]
613 - (void) _flattenCycleRecord: (NSDictionary *) _row
614 forRange: (NGCalendarDateRange *) _r
615 intoArray: (NSMutableArray *) _ma
617 NSMutableDictionary *row;
618 NSDictionary *cycleinfo;
619 NSCalendarDate *startDate, *endDate;
620 NGCalendarDateRange *fir;
621 NSArray *rules, *exRules, *exDates, *ranges;
624 cycleinfo = [[_row objectForKey:@"c_cycleinfo"] propertyList];
625 if (cycleinfo == nil) {
626 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
630 row = [self fixupRecord:_row fetchRange: _r];
631 [row removeObjectForKey: @"c_cycleinfo"];
632 [row setObject: sharedYes forKey:@"isRecurrentEvent"];
634 startDate = [row objectForKey:@"startDate"];
635 endDate = [row objectForKey:@"endDate"];
636 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
638 rules = [cycleinfo objectForKey:@"rules"];
639 exRules = [cycleinfo objectForKey:@"exRules"];
640 exDates = [cycleinfo objectForKey:@"exDates"];
642 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
643 firstInstanceCalendarDateRange:fir
644 recurrenceRules:rules
645 exceptionRules:exRules
646 exceptionDates:exDates];
647 count = [ranges count];
648 for (i = 0; i < count; i++) {
649 NGCalendarDateRange *rRange;
652 rRange = [ranges objectAtIndex:i];
653 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
654 if (fixedRow != nil) [_ma addObject:fixedRow];
658 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
659 fetchRange: (NGCalendarDateRange *) _r
661 // TODO: is the result supposed to be sorted by date?
665 if (_records == nil) return nil;
666 if ((count = [_records count]) == 0)
669 ma = [NSMutableArray arrayWithCapacity:count];
670 for (i = 0; i < count; i++) {
671 id row; // TODO: what is the type of the record?
673 row = [_records objectAtIndex:i];
674 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
679 - (NSString *) _sqlStringForComponent: (id) _component
686 if ([_component isKindOfClass: [NSArray class]])
687 components = _component;
689 components = [NSArray arrayWithObject: _component];
692 = [NSString stringWithFormat: @" AND (c_component = '%@')",
693 [components componentsJoinedByString: @"' OR c_component = '"]];
701 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
702 to: (NSCalendarDate *) _endDate
704 unsigned int start, end;
706 start = (unsigned int) [_startDate timeIntervalSince1970];
707 end = (unsigned int) [_endDate timeIntervalSince1970];
709 return [NSString stringWithFormat:
710 @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
714 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
716 NSMutableString *classificationString;
717 NSString *currentRole;
718 unsigned int counter;
719 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
720 iCalAccessConfidential};
722 classificationString = [NSMutableString string];
723 for (counter = 0; counter < 3; counter++)
725 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
727 if ([currentRole length] > 0)
728 [classificationString appendFormat: @"c_classification = %d or ",
732 return classificationString;
735 - (NSString *) _privacySqlString
737 NSString *privacySqlString, *login, *email;
738 SOGoUser *activeUser;
740 activeUser = [context activeUser];
741 login = [activeUser login];
743 if ([login isEqualToString: owner])
744 privacySqlString = @"";
745 else if ([login isEqualToString: @"freebusy"])
746 privacySqlString = @"and (c_isopaque = 1)";
749 #warning we do not manage all the user's possible emails
750 email = [[activeUser primaryIdentity] objectForKey: @"email"];
753 = [NSString stringWithFormat:
754 @"(%@(c_orgmail = '%@')"
755 @" or ((c_partmails caseInsensitiveLike '%@%%'"
756 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
757 [self _privacyClassificationStringsForUID: login],
758 email, email, email];
761 return privacySqlString;
764 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
765 forUser: (NSString *) uid
767 NSString *accessRole, *prefix, *currentRole, *suffix;
772 if (accessClass == iCalAccessPublic)
774 else if (accessClass == iCalAccessPrivate)
777 prefix = @"Confidential";
779 acls = [[self aclsForUser: uid] objectEnumerator];
780 currentRole = [acls nextObject];
781 while (currentRole && !accessRole)
782 if ([currentRole hasPrefix: prefix])
784 suffix = [currentRole substringFromIndex: [prefix length]];
785 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
788 currentRole = [acls nextObject];
793 - (NSArray *) fetchFields: (NSArray *) _fields
794 fromFolder: (GCSFolder *) _folder
795 from: (NSCalendarDate *) _startDate
796 to: (NSCalendarDate *) _endDate
797 component: (id) _component
799 EOQualifier *qualifier;
800 NSMutableArray *fields, *ma = nil;
802 NSString *sql, *dateSqlString, *componentSqlString, *privacySqlString;
803 NGCalendarDateRange *r;
805 if (_folder == nil) {
806 [self errorWithFormat:@"(%s): missing folder for fetch!",
807 __PRETTY_FUNCTION__];
811 if (_startDate && _endDate)
813 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
815 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
823 componentSqlString = [self _sqlStringForComponent: _component];
824 privacySqlString = [self _privacySqlString];
826 /* prepare mandatory fields */
828 fields = [NSMutableArray arrayWithArray: _fields];
829 [fields addObject: @"c_uid"];
830 [fields addObject: @"c_startdate"];
831 [fields addObject: @"c_enddate"];
834 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
836 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@",
837 dateSqlString, componentSqlString, privacySqlString];
839 /* fetch non-recurrent apts first */
840 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
842 records = [_folder fetchFields: fields matchingQualifier: qualifier];
846 records = [self fixupRecords: records fetchRange: r];
848 [self debugWithFormat: @"fetched %i records: %@",
849 [records count], records];
850 ma = [NSMutableArray arrayWithArray: records];
853 /* fetch recurrent apts now */
854 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@",
855 dateSqlString, componentSqlString, privacySqlString];
856 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
858 [fields addObject: @"c_cycleinfo"];
860 records = [_folder fetchFields: fields matchingQualifier: qualifier];
864 [self debugWithFormat: @"fetched %i cyclic records: %@",
865 [records count], records];
867 records = [self fixupCyclicRecords: records fetchRange: r];
869 ma = [NSMutableArray arrayWithCapacity: [records count]];
871 [ma addObjectsFromArray: records];
875 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
880 [self debugWithFormat:@"returning %i records", [ma count]];
882 // [ma makeObjectsPerform: @selector (setObject:forKey:)
884 // withObject: @"owner"];
889 /* override this in subclasses */
890 - (NSArray *) fetchFields: (NSArray *) _fields
891 from: (NSCalendarDate *) _startDate
892 to: (NSCalendarDate *) _endDate
893 component: (id) _component
897 if ((folder = [self ocsFolder]) == nil) {
898 [self errorWithFormat:@"(%s): missing folder for fetch!",
899 __PRETTY_FUNCTION__];
903 return [self fetchFields: _fields fromFolder: folder
904 from: _startDate to: _endDate
905 component: _component];
909 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
910 to: (NSCalendarDate *) _endDate
912 static NSArray *infos = nil; // TODO: move to a plist file
915 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
916 @"c_isopaque", @"c_status", nil];
918 return [self fetchFields: infos from: _startDate to: _endDate
919 component: @"vevent"];
922 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
923 to: (NSCalendarDate *) _endDate
924 component: (id) _component
926 static NSArray *infos = nil; // TODO: move to a plist file
929 infos = [[NSArray alloc] initWithObjects:
930 @"c_name", @"c_component",
931 @"c_title", @"c_location", @"c_orgmail",
932 @"c_status", @"c_classification",
933 @"c_isallday", @"c_isopaque",
934 @"c_participants", @"c_partmails",
935 @"c_partstates", @"c_sequence", @"c_priority",
938 return [self fetchFields: infos from: _startDate to: _endDate
939 component: _component];
942 - (void) deleteEntriesWithIds: (NSArray *) ids
945 unsigned int count, max;
950 for (count = 0; count < max; count++)
952 currentId = [ids objectAtIndex: count];
954 = [self objectClassForResourceNamed: currentId];
955 deleteObject = [objectClass objectWithName: currentId
957 [deleteObject delete];
958 [deleteObject primaryDelete];
964 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
967 // TODO: who calls this?
970 if ([_uid length] == 0)
973 url = [self baseURLInContext:_ctx];
974 if (![url hasSuffix: @"/"])
975 url = [url stringByAppendingString: @"/"];
977 // TODO: this should run a query to determine the uid!
978 return [url stringByAppendingString:_uid];
981 /* folder management */
983 - (id) lookupHomeFolderForUID: (NSString *) _uid
986 // TODO: DUP to SOGoGroupFolder
987 NSException *error = nil;
991 if (![_uid isNotNull])
994 /* create subcontext, so that we don't destroy our environment */
996 if ((ctx = [context createSubContext]) == nil) {
997 [self errorWithFormat:@"could not create SOPE subcontext!"];
1003 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1007 result = [[ctx application] traversePathArray:path inContext:ctx
1008 error:&error acquire:NO];
1010 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1015 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1016 _uid, [path componentsJoinedByString:@"=>"], result];
1020 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1022 SOGoFolder *upperContainer;
1023 SOGoUserFolder *userFolder;
1024 SOGoAppointmentFolder *calendarFolder;
1026 upperContainer = [[self container] container];
1027 userFolder = [SOGoUserFolder objectWithName: uid
1028 inContainer: upperContainer];
1029 calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
1030 inContainer: userFolder];
1032 setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]];
1033 [calendarFolder setOwner: uid];
1035 return calendarFolder;
1038 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1041 /* Note: can return NSNull objects in the array! */
1042 NSMutableArray *folders;
1046 if ([_uids count] == 0) return nil;
1047 folders = [NSMutableArray arrayWithCapacity:16];
1048 e = [_uids objectEnumerator];
1049 while ((uid = [e nextObject])) {
1052 folder = [self lookupCalendarFolderForUID: uid];
1053 if (![folder isNotNull])
1054 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1056 /* Note: intentionally add 'null' folders to allow a mapping */
1057 [folders addObject:folder ? folder : [NSNull null]];
1062 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1063 inContext: (id) _ctx
1065 /* Note: can return NSNull objects in the array! */
1066 NSMutableArray *objs;
1070 if ([_uids count] == 0) return nil;
1071 objs = [NSMutableArray arrayWithCapacity:16];
1072 e = [_uids objectEnumerator];
1073 while ((uid = [e nextObject])) {
1076 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1077 if ([obj isNotNull]) {
1078 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1079 if ([obj isKindOfClass:[NSException class]])
1082 if (![obj isNotNull])
1083 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1085 /* Note: intentionally add 'null' folders to allow a mapping */
1086 [objs addObject:obj ? obj : [NSNull null]];
1091 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1093 /* Note: can return NSNull objects in the array! */
1094 NSMutableArray *uids;
1095 LDAPUserManager *um;
1098 if (_persons == nil)
1101 count = [_persons count];
1102 uids = [NSMutableArray arrayWithCapacity:count + 1];
1103 um = [LDAPUserManager sharedUserManager];
1105 for (i = 0; i < count; i++) {
1110 person = [_persons objectAtIndex:i];
1111 email = [person rfc822Email];
1112 if ([email isNotNull]) {
1113 uid = [um getUIDForEmail:email];
1118 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1123 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1124 inContext: (id) _ctx
1126 /* Note: can return NSNull objects in the array! */
1129 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1132 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1135 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1138 SOGoCustomGroupFolder *folder;
1143 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1144 return [folder autorelease];
1147 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1148 inContext: (id) _ctx
1150 SOGoCustomGroupFolder *folder;
1152 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1155 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1156 if (![folder isNotNull])
1158 if ([folder isKindOfClass:[NSException class]]) {
1159 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1169 - (NSArray *) fetchAllSOGoAppointments
1172 Note: very expensive method, do not use unless absolutely required.
1173 returns an array of SOGoAppointment objects.
1175 Note that we can leave out the filenames, supposed to be stored
1176 in the 'uid' field of the iCalendar object!
1178 NSMutableArray *events;
1179 NSDictionary *files;
1180 NSEnumerator *contents;
1183 /* fetch all raw contents */
1185 files = [self fetchContentStringsAndNamesOfAllObjects];
1186 if (![files isNotNull]) return nil;
1187 if ([files isKindOfClass:[NSException class]]) return (id)files;
1189 /* transform to SOGo appointments */
1191 events = [NSMutableArray arrayWithCapacity:[files count]];
1192 contents = [files objectEnumerator];
1193 while ((content = [contents nextObject]) != nil)
1194 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1199 #warning We only support ONE calendar per user at this time
1200 - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1201 toFolderList: (NSMutableArray *) calendarFolders
1204 NSString *currentKey;
1205 NSMutableDictionary *currentCalendar;
1206 BOOL firstShouldBeActive;
1209 firstShouldBeActive = YES;
1211 keys = [[subscribedFolders allKeys] objectEnumerator];
1212 currentKey = [keys nextObject];
1216 currentCalendar = [NSMutableDictionary new];
1217 [currentCalendar autorelease];
1219 setDictionary: [subscribedFolders objectForKey: currentKey]];
1220 [currentCalendar setObject: currentKey forKey: @"folder"];
1221 [calendarFolders addObject: currentCalendar];
1222 if ([[currentCalendar objectForKey: @"active"] boolValue])
1223 firstShouldBeActive = NO;
1225 currentKey = [keys nextObject];
1228 return firstShouldBeActive;
1231 - (NSArray *) calendarFolders
1233 NSMutableDictionary *userCalendar, *calendarDict;
1234 NSMutableArray *calendarFolders;
1235 SOGoUser *calendarUser;
1238 calendarFolders = [NSMutableArray new];
1239 [calendarFolders autorelease];
1241 calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1243 userCalendar = [NSMutableDictionary new];
1244 [userCalendar autorelease];
1245 [userCalendar setObject: @"/" forKey: @"folder"];
1246 [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1247 [calendarFolders addObject: userCalendar];
1249 calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1250 firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1251 firstActive = ([self _appendSubscribedFolders:
1252 [calendarDict objectForKey: @"SubscribedFolders"]
1253 toFolderList: calendarFolders]
1255 [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1258 return calendarFolders;
1261 // - (NSArray *) fetchContentObjectNames
1263 // NSMutableArray *objectNames;
1264 // NSArray *records;
1265 // NSCalendarDate *today, *startDate, *endDate;
1267 // #warning this should be user-configurable
1268 // objectNames = [NSMutableArray array];
1269 // today = [[NSCalendarDate calendarDate] beginOfDay];
1270 // [today setTimeZone: timeZone];
1272 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1273 // hours: 0 minutes: 0 seconds: 0];
1274 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1275 // hours: 0 minutes: 0 seconds: 0];
1276 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1277 // from: startDate to: endDate
1278 // component: @"vevent"];
1279 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1280 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1281 // from: startDate to: endDate
1282 // component: @"vtodo"];
1283 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1285 // return objectNames;
1290 - (NSString *) folderType
1292 return @"Appointment";
1295 - (NSString *) outlookFolderClass
1297 return @"IPF.Appointment";
1300 @end /* SOGoAppointmentFolder */