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 email = [activeUser primaryEmail];
752 = [NSString stringWithFormat:
753 @"(%@(c_orgmail = '%@')"
754 @" or ((c_partmails caseInsensitiveLike '%@%%'"
755 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
756 [self _privacyClassificationStringsForUID: login],
757 email, email, email];
760 return privacySqlString;
763 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
764 forUser: (NSString *) uid
766 NSString *accessRole, *prefix, *currentRole, *suffix;
771 if (accessClass == iCalAccessPublic)
773 else if (accessClass == iCalAccessPrivate)
776 prefix = @"Confidential";
778 acls = [[self aclsForUser: uid] objectEnumerator];
779 currentRole = [acls nextObject];
780 while (currentRole && !accessRole)
781 if ([currentRole hasPrefix: prefix])
783 suffix = [currentRole substringFromIndex: [prefix length]];
784 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
787 currentRole = [acls nextObject];
792 - (NSArray *) fetchFields: (NSArray *) _fields
793 fromFolder: (GCSFolder *) _folder
794 from: (NSCalendarDate *) _startDate
795 to: (NSCalendarDate *) _endDate
796 component: (id) _component
798 EOQualifier *qualifier;
799 NSMutableArray *fields, *ma = nil;
801 NSString *sql, *dateSqlString, *componentSqlString, *privacySqlString;
802 NGCalendarDateRange *r;
804 if (_folder == nil) {
805 [self errorWithFormat:@"(%s): missing folder for fetch!",
806 __PRETTY_FUNCTION__];
810 if (_startDate && _endDate)
812 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
814 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
822 componentSqlString = [self _sqlStringForComponent: _component];
823 privacySqlString = [self _privacySqlString];
825 /* prepare mandatory fields */
827 fields = [NSMutableArray arrayWithArray: _fields];
828 [fields addObject: @"c_uid"];
829 [fields addObject: @"c_startdate"];
830 [fields addObject: @"c_enddate"];
833 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
835 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@",
836 dateSqlString, componentSqlString, privacySqlString];
838 /* fetch non-recurrent apts first */
839 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
841 records = [_folder fetchFields: fields matchingQualifier: qualifier];
845 records = [self fixupRecords: records fetchRange: r];
847 [self debugWithFormat: @"fetched %i records: %@",
848 [records count], records];
849 ma = [NSMutableArray arrayWithArray: records];
852 /* fetch recurrent apts now */
853 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@",
854 dateSqlString, componentSqlString, privacySqlString];
855 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
857 [fields addObject: @"c_cycleinfo"];
859 records = [_folder fetchFields: fields matchingQualifier: qualifier];
863 [self debugWithFormat: @"fetched %i cyclic records: %@",
864 [records count], records];
866 records = [self fixupCyclicRecords: records fetchRange: r];
868 ma = [NSMutableArray arrayWithCapacity: [records count]];
870 [ma addObjectsFromArray: records];
874 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
879 [self debugWithFormat:@"returning %i records", [ma count]];
881 // [ma makeObjectsPerform: @selector (setObject:forKey:)
883 // withObject: @"owner"];
888 /* override this in subclasses */
889 - (NSArray *) fetchFields: (NSArray *) _fields
890 from: (NSCalendarDate *) _startDate
891 to: (NSCalendarDate *) _endDate
892 component: (id) _component
896 if ((folder = [self ocsFolder]) == nil) {
897 [self errorWithFormat:@"(%s): missing folder for fetch!",
898 __PRETTY_FUNCTION__];
902 return [self fetchFields: _fields fromFolder: folder
903 from: _startDate to: _endDate
904 component: _component];
908 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
909 to: (NSCalendarDate *) _endDate
911 static NSArray *infos = nil; // TODO: move to a plist file
914 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
915 @"c_isopaque", @"c_status", nil];
917 return [self fetchFields: infos from: _startDate to: _endDate
918 component: @"vevent"];
921 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
922 to: (NSCalendarDate *) _endDate
923 component: (id) _component
925 static NSArray *infos = nil; // TODO: move to a plist file
928 infos = [[NSArray alloc] initWithObjects:
929 @"c_name", @"c_component",
930 @"c_title", @"c_location", @"c_orgmail",
931 @"c_status", @"c_classification",
932 @"c_isallday", @"c_isopaque",
933 @"c_participants", @"c_partmails",
934 @"c_partstates", @"c_sequence", @"c_priority",
937 return [self fetchFields: infos from: _startDate to: _endDate
938 component: _component];
941 - (void) deleteEntriesWithIds: (NSArray *) ids
944 unsigned int count, max;
949 for (count = 0; count < max; count++)
951 currentId = [ids objectAtIndex: count];
953 = [self objectClassForResourceNamed: currentId];
954 deleteObject = [objectClass objectWithName: currentId
956 [deleteObject delete];
957 [deleteObject primaryDelete];
963 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
966 // TODO: who calls this?
969 if ([_uid length] == 0)
972 url = [self baseURLInContext:_ctx];
973 if (![url hasSuffix: @"/"])
974 url = [url stringByAppendingString: @"/"];
976 // TODO: this should run a query to determine the uid!
977 return [url stringByAppendingString:_uid];
980 /* folder management */
982 - (id) lookupHomeFolderForUID: (NSString *) _uid
985 // TODO: DUP to SOGoGroupFolder
986 NSException *error = nil;
990 if (![_uid isNotNull])
993 /* create subcontext, so that we don't destroy our environment */
995 if ((ctx = [context createSubContext]) == nil) {
996 [self errorWithFormat:@"could not create SOPE subcontext!"];
1002 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1006 result = [[ctx application] traversePathArray:path inContext:ctx
1007 error:&error acquire:NO];
1009 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1014 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1015 _uid, [path componentsJoinedByString:@"=>"], result];
1019 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1021 SOGoFolder *upperContainer;
1022 SOGoUserFolder *userFolder;
1023 SOGoAppointmentFolder *calendarFolder;
1025 upperContainer = [[self container] container];
1026 userFolder = [SOGoUserFolder objectWithName: uid
1027 inContainer: upperContainer];
1028 calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
1029 inContainer: userFolder];
1031 setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]];
1032 [calendarFolder setOwner: uid];
1034 return calendarFolder;
1037 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1040 /* Note: can return NSNull objects in the array! */
1041 NSMutableArray *folders;
1045 if ([_uids count] == 0) return nil;
1046 folders = [NSMutableArray arrayWithCapacity:16];
1047 e = [_uids objectEnumerator];
1048 while ((uid = [e nextObject])) {
1051 folder = [self lookupCalendarFolderForUID: uid];
1052 if (![folder isNotNull])
1053 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1055 /* Note: intentionally add 'null' folders to allow a mapping */
1056 [folders addObject:folder ? folder : [NSNull null]];
1061 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1062 inContext: (id) _ctx
1064 /* Note: can return NSNull objects in the array! */
1065 NSMutableArray *objs;
1069 if ([_uids count] == 0) return nil;
1070 objs = [NSMutableArray arrayWithCapacity:16];
1071 e = [_uids objectEnumerator];
1072 while ((uid = [e nextObject])) {
1075 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1076 if ([obj isNotNull]) {
1077 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1078 if ([obj isKindOfClass:[NSException class]])
1081 if (![obj isNotNull])
1082 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1084 /* Note: intentionally add 'null' folders to allow a mapping */
1085 [objs addObject:obj ? obj : [NSNull null]];
1090 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1092 /* Note: can return NSNull objects in the array! */
1093 NSMutableArray *uids;
1094 LDAPUserManager *um;
1097 if (_persons == nil)
1100 count = [_persons count];
1101 uids = [NSMutableArray arrayWithCapacity:count + 1];
1102 um = [LDAPUserManager sharedUserManager];
1104 for (i = 0; i < count; i++) {
1109 person = [_persons objectAtIndex:i];
1110 email = [person rfc822Email];
1111 if ([email isNotNull]) {
1112 uid = [um getUIDForEmail:email];
1117 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1122 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1123 inContext: (id) _ctx
1125 /* Note: can return NSNull objects in the array! */
1128 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1131 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1134 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1137 SOGoCustomGroupFolder *folder;
1142 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1143 return [folder autorelease];
1146 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1147 inContext: (id) _ctx
1149 SOGoCustomGroupFolder *folder;
1151 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1154 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1155 if (![folder isNotNull])
1157 if ([folder isKindOfClass:[NSException class]]) {
1158 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1168 - (NSArray *) fetchAllSOGoAppointments
1171 Note: very expensive method, do not use unless absolutely required.
1172 returns an array of SOGoAppointment objects.
1174 Note that we can leave out the filenames, supposed to be stored
1175 in the 'uid' field of the iCalendar object!
1177 NSMutableArray *events;
1178 NSDictionary *files;
1179 NSEnumerator *contents;
1182 /* fetch all raw contents */
1184 files = [self fetchContentStringsAndNamesOfAllObjects];
1185 if (![files isNotNull]) return nil;
1186 if ([files isKindOfClass:[NSException class]]) return (id)files;
1188 /* transform to SOGo appointments */
1190 events = [NSMutableArray arrayWithCapacity:[files count]];
1191 contents = [files objectEnumerator];
1192 while ((content = [contents nextObject]) != nil)
1193 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1198 #warning We only support ONE calendar per user at this time
1199 - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1200 toFolderList: (NSMutableArray *) calendarFolders
1203 NSString *currentKey;
1204 NSMutableDictionary *currentCalendar;
1205 BOOL firstShouldBeActive;
1208 firstShouldBeActive = YES;
1210 keys = [[subscribedFolders allKeys] objectEnumerator];
1211 currentKey = [keys nextObject];
1215 currentCalendar = [NSMutableDictionary new];
1216 [currentCalendar autorelease];
1218 setDictionary: [subscribedFolders objectForKey: currentKey]];
1219 [currentCalendar setObject: currentKey forKey: @"folder"];
1220 [calendarFolders addObject: currentCalendar];
1221 if ([[currentCalendar objectForKey: @"active"] boolValue])
1222 firstShouldBeActive = NO;
1224 currentKey = [keys nextObject];
1227 return firstShouldBeActive;
1230 - (NSArray *) calendarFolders
1232 NSMutableDictionary *userCalendar, *calendarDict;
1233 NSMutableArray *calendarFolders;
1234 SOGoUser *calendarUser;
1237 calendarFolders = [NSMutableArray new];
1238 [calendarFolders autorelease];
1240 calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1242 userCalendar = [NSMutableDictionary new];
1243 [userCalendar autorelease];
1244 [userCalendar setObject: @"/" forKey: @"folder"];
1245 [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1246 [calendarFolders addObject: userCalendar];
1248 calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1249 firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1250 firstActive = ([self _appendSubscribedFolders:
1251 [calendarDict objectForKey: @"SubscribedFolders"]
1252 toFolderList: calendarFolders]
1254 [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1257 return calendarFolders;
1260 // - (NSArray *) fetchContentObjectNames
1262 // NSMutableArray *objectNames;
1263 // NSArray *records;
1264 // NSCalendarDate *today, *startDate, *endDate;
1266 // #warning this should be user-configurable
1267 // objectNames = [NSMutableArray array];
1268 // today = [[NSCalendarDate calendarDate] beginOfDay];
1269 // [today setTimeZone: timeZone];
1271 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1272 // hours: 0 minutes: 0 seconds: 0];
1273 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1274 // hours: 0 minutes: 0 seconds: 0];
1275 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1276 // from: startDate to: endDate
1277 // component: @"vevent"];
1278 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1279 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1280 // from: startDate to: endDate
1281 // component: @"vtodo"];
1282 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1284 // return objectNames;
1289 - (NSString *) folderType
1291 return @"Appointment";
1294 - (NSString *) outlookFolderClass
1296 return @"IPF.Appointment";
1299 @end /* SOGoAppointmentFolder */