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];
130 - (NSArray *) calendarUIDs
132 /* this is used for group calendars (this folder just returns itself) */
135 s = [[self container] nameInContainer];
136 // [self logWithFormat:@"CAL UID: %@", s];
137 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
142 - (BOOL) isValidAppointmentName: (NSString *)_key
144 return ([_key length] != 0);
147 - (void) appendObject: (NSDictionary *) object
148 withBaseURL: (NSString *) baseURL
149 toREPORTResponse: (WOResponse *) r
151 SOGoCalendarComponent *component;
152 Class componentClass;
153 NSString *name, *etagLine, *calString;
155 name = [object objectForKey: @"c_name"];
157 if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
158 componentClass = [SOGoAppointmentObject class];
160 componentClass = [SOGoTaskObject class];
162 component = [componentClass objectWithName: name inContainer: self];
164 [r appendContentString: @" <D:response>\r\n"];
165 [r appendContentString: @" <D:href>"];
166 [r appendContentString: baseURL];
167 if (![baseURL hasSuffix: @"/"])
168 [r appendContentString: @"/"];
169 [r appendContentString: name];
170 [r appendContentString: @"</D:href>\r\n"];
172 [r appendContentString: @" <D:propstat>\r\n"];
173 [r appendContentString: @" <D:prop>\r\n"];
174 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
175 [component davEntityTag]];
176 [r appendContentString: etagLine];
177 [r appendContentString: @" </D:prop>\r\n"];
178 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
179 [r appendContentString: @" </D:propstat>\r\n"];
180 [r appendContentString: @" <C:calendar-data>"];
181 calString = [[component contentAsString] stringByEscapingXMLString];
182 [r appendContentString: calString];
183 [r appendContentString: @"</C:calendar-data>\r\n"];
184 [r appendContentString: @" </D:response>\r\n"];
187 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
188 toFilter: (NSMutableDictionary *) filter
190 NSCalendarDate *parsedDate;
192 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
193 [filter setObject: parsedDate forKey: @"start"];
194 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
195 [filter setObject: parsedDate forKey: @"end"];
198 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
200 NSMutableDictionary *filterData;
201 id <DOMNode> parentNode;
202 id <DOMNodeList> ranges;
203 NSString *componentName;
205 parentNode = [filterElement parentNode];
206 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
207 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
209 componentName = [[filterElement attribute: @"name"] lowercaseString];
210 filterData = [NSMutableDictionary new];
211 [filterData autorelease];
212 [filterData setObject: componentName forKey: @"name"];
213 ranges = [filterElement getElementsByTagName: @"time-range"];
215 [self _appendTimeRange: [ranges objectAtIndex: 0]
216 toFilter: filterData];
224 #warning filters is leaked here
225 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
227 NSEnumerator *children;
229 NSMutableArray *filters;
230 NSDictionary *filter;
232 filters = [NSMutableArray new];
234 children = [[parentNode getElementsByTagName: @"comp-filter"]
236 node = [children nextObject];
239 filter = [self _parseCalendarFilter: node];
241 [filters addObject: filter];
242 node = [children nextObject];
248 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
249 toResponse: (WOResponse *) response
252 unsigned int count, max;
253 NSDictionary *currentFilter, *appointment;
254 NSEnumerator *appointments;
257 baseURL = [self baseURLInContext: context];
259 max = [filters count];
260 for (count = 0; count < max; count++)
262 #warning huh? why not objectAtIndex: count?
263 currentFilter = [filters objectAtIndex: 0];
264 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
265 to: [currentFilter objectForKey: @"end"]
266 component: [currentFilter objectForKey: @"name"]];
267 appointments = [apts objectEnumerator];
268 appointment = [appointments nextObject];
271 [self appendObject: appointment
273 toREPORTResponse: response];
274 appointment = [appointments nextObject];
279 - (NSArray *) davNamespaces
281 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
284 - (id) davCalendarQuery: (id) queryContext
288 id <DOMDocument> document;
290 r = [context response];
292 [r setContentEncoding: NSUTF8StringEncoding];
293 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
294 [r setHeader: @"no-cache" forKey: @"pragma"];
295 [r setHeader: @"no-cache" forKey: @"cache-control"];
296 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
297 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
298 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
300 document = [[context request] contentAsDOMDocument];
301 filters = [self _parseCalendarFilters: [document documentElement]];
302 [self _appendComponentsMatchingFilters: filters
304 [r appendContentString:@"</D:multistatus>\r\n"];
309 - (Class) objectClassForContent: (NSString *) content
311 iCalCalendar *calendar;
318 calendar = [iCalCalendar parseSingleFromSource: content];
321 elements = [calendar allObjects];
322 if ([elements count])
324 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
325 if ([firstTag isEqualToString: @"VEVENT"])
326 objectClass = [SOGoAppointmentObject class];
327 else if ([firstTag isEqualToString: @"VTODO"])
328 objectClass = [SOGoTaskObject class];
335 - (id) deduceObjectForName: (NSString *)_key
343 request = [_ctx request];
344 method = [request method];
345 if ([method isEqualToString: @"PUT"])
346 objectClass = [self objectClassForContent: [request contentAsString]];
348 objectClass = [self objectClassForResourceNamed: _key];
351 obj = [objectClass objectWithName: _key inContainer: self];
358 - (BOOL) requestNamedIsHandledLater: (NSString *) name
360 return [name isEqualToString: @"OPTIONS"];
363 - (id) lookupName: (NSString *)_key
371 /* first check attributes directly bound to the application */
372 handledLater = [self requestNamedIsHandledLater: _key];
377 obj = [super lookupName:_key inContext:_ctx acquire:NO];
380 if ([self isValidAppointmentName:_key])
382 url = [[[_ctx request] uri] urlWithoutParameters];
383 if ([url hasSuffix: @"AsTask"])
384 obj = [SOGoTaskObject objectWithName: _key
386 else if ([url hasSuffix: @"AsAppointment"])
387 obj = [SOGoAppointmentObject objectWithName: _key
390 obj = [self deduceObjectForName: _key
395 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
401 - (NSArray *) davComplianceClassesInContext: (id)_ctx
403 NSMutableArray *classes;
404 NSArray *primaryClasses;
406 classes = [NSMutableArray new];
407 [classes autorelease];
409 primaryClasses = [super davComplianceClassesInContext: _ctx];
411 [classes addObjectsFromArray: primaryClasses];
412 [classes addObject: @"access-control"];
413 [classes addObject: @"calendar-access"];
418 - (NSArray *) groupDavResourceType
420 return [NSArray arrayWithObjects: @"vevent-collection",
421 @"vtodo-collection", nil];
424 - (NSArray *) davResourceType
426 static NSArray *colType = nil;
427 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
431 gdRT = [self groupDavResourceType];
432 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
433 XMLNS_GROUPDAV, nil];
434 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
435 XMLNS_GROUPDAV, nil];
436 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
437 colType = [NSArray arrayWithObjects: @"collection", cdCol,
438 gdVEventCol, gdVTodoCol, nil];
445 /* vevent UID handling */
447 - (NSString *) resourceNameForEventUID: (NSString *)_u
448 inFolder: (GCSFolder *)_f
450 static NSArray *nameFields = nil;
451 EOQualifier *qualifier;
454 if (![_u isNotNull]) return nil;
456 [self errorWithFormat:@"(%s): missing folder for fetch!",
457 __PRETTY_FUNCTION__];
461 if (nameFields == nil)
462 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
464 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
465 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
467 if ([records count] == 1)
468 return [[records objectAtIndex:0] valueForKey:@"c_name"];
469 if ([records count] == 0)
472 [self errorWithFormat:
473 @"The storage contains more than file with the same UID!"];
474 return [[records objectAtIndex:0] valueForKey:@"c_name"];
477 - (NSString *) resourceNameForEventUID: (NSString *) _uid
483 if (![_uid isNotNull])
485 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
486 return [rname isNotNull] ? rname : nil;
488 if ((folder = [self ocsFolder]) == nil) {
489 [self errorWithFormat:@"(%s): missing folder for fetch!",
490 __PRETTY_FUNCTION__];
494 if (uidToFilename == nil)
495 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
497 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
498 [uidToFilename setObject:[NSNull null] forKey:_uid];
500 [uidToFilename setObject:rname forKey:_uid];
505 - (Class) objectClassForResourceNamed: (NSString *) name
507 EOQualifier *qualifier;
512 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
513 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
514 matchingQualifier: qualifier];
518 component = [[records objectAtIndex:0] valueForKey: @"c_component"];
519 if ([component isEqualToString: @"vevent"])
520 objectClass = [SOGoAppointmentObject class];
521 else if ([component isEqualToString: @"vtodo"])
522 objectClass = [SOGoTaskObject class];
534 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
535 fetchRange: (NGCalendarDateRange *) _r
537 NSMutableDictionary *md;
540 md = [[_record mutableCopy] autorelease];
542 if ((tmp = [_record objectForKey:@"c_startdate"])) {
543 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
544 (NSTimeInterval)[tmp unsignedIntValue]];
545 [tmp setTimeZone: timeZone];
546 if (tmp) [md setObject:tmp forKey:@"startDate"];
550 [self logWithFormat:@"missing 'startdate' in record?"];
552 if ((tmp = [_record objectForKey:@"c_enddate"])) {
553 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
554 (NSTimeInterval)[tmp unsignedIntValue]];
555 [tmp setTimeZone: timeZone];
556 if (tmp) [md setObject:tmp forKey:@"endDate"];
560 [self logWithFormat:@"missing 'enddate' in record?"];
565 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
566 cycleRange: (NGCalendarDateRange *) _r
568 NSMutableDictionary *md;
571 md = [[_record mutableCopy] autorelease];
573 /* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
574 the reccurence since we use those when displaying events in SOGo Web */
575 tmp = [_r startDate];
576 [tmp setTimeZone: timeZone];
577 [md setObject:tmp forKey:@"startDate"];
578 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"];
580 [tmp setTimeZone: timeZone];
581 [md setObject:tmp forKey:@"endDate"];
582 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_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];
649 for (i = 0; i < count; i++) {
650 NGCalendarDateRange *rRange;
653 rRange = [ranges objectAtIndex:i];
654 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
657 [_ma addObject:fixedRow];
662 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
663 fetchRange: (NGCalendarDateRange *) _r
665 // TODO: is the result supposed to be sorted by date?
669 if (_records == nil) return nil;
670 if ((count = [_records count]) == 0)
673 ma = [NSMutableArray arrayWithCapacity:count];
674 for (i = 0; i < count; i++) {
675 id row; // TODO: what is the type of the record?
677 row = [_records objectAtIndex:i];
678 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
683 - (NSString *) _sqlStringForComponent: (id) _component
690 if ([_component isKindOfClass: [NSArray class]])
691 components = _component;
693 components = [NSArray arrayWithObject: _component];
696 = [NSString stringWithFormat: @" AND (c_component = '%@')",
697 [components componentsJoinedByString: @"' OR c_component = '"]];
705 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
706 to: (NSCalendarDate *) _endDate
708 unsigned int start, end;
710 start = (unsigned int) [_startDate timeIntervalSince1970];
711 end = (unsigned int) [_endDate timeIntervalSince1970];
713 return [NSString stringWithFormat:
714 @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
718 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
720 NSMutableString *classificationString;
721 NSString *currentRole;
722 unsigned int counter;
723 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
724 iCalAccessConfidential};
726 classificationString = [NSMutableString string];
727 for (counter = 0; counter < 3; counter++)
729 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
731 if ([currentRole length] > 0)
732 [classificationString appendFormat: @"c_classification = %d or ",
736 return classificationString;
739 - (NSString *) _privacySqlString
741 NSString *privacySqlString, *login, *email;
742 SOGoUser *activeUser;
744 activeUser = [context activeUser];
745 login = [activeUser login];
747 if ([login isEqualToString: owner])
748 privacySqlString = @"";
749 else if ([login isEqualToString: @"freebusy"])
750 privacySqlString = @"and (c_isopaque = 1)";
753 #warning we do not manage all the possible user emails
754 email = [[activeUser primaryIdentity] objectForKey: @"email"];
757 = [NSString stringWithFormat:
758 @"(%@(c_orgmail = '%@')"
759 @" or ((c_partmails caseInsensitiveLike '%@%%'"
760 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
761 [self _privacyClassificationStringsForUID: login],
762 email, email, email];
765 return privacySqlString;
768 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
769 forUser: (NSString *) uid
771 NSString *accessRole, *prefix, *currentRole, *suffix;
776 if (accessClass == iCalAccessPublic)
778 else if (accessClass == iCalAccessPrivate)
781 prefix = @"Confidential";
783 acls = [[self aclsForUser: uid] objectEnumerator];
784 currentRole = [acls nextObject];
785 while (currentRole && !accessRole)
786 if ([currentRole hasPrefix: prefix])
788 suffix = [currentRole substringFromIndex: [prefix length]];
789 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
792 currentRole = [acls nextObject];
797 - (NSArray *) fetchFields: (NSArray *) _fields
798 fromFolder: (GCSFolder *) _folder
799 from: (NSCalendarDate *) _startDate
800 to: (NSCalendarDate *) _endDate
801 component: (id) _component
803 EOQualifier *qualifier;
804 NSMutableArray *fields, *ma = nil;
806 NSString *sql, *dateSqlString, *componentSqlString, *privacySqlString;
807 NGCalendarDateRange *r;
809 if (_folder == nil) {
810 [self errorWithFormat:@"(%s): missing folder for fetch!",
811 __PRETTY_FUNCTION__];
815 if (_startDate && _endDate)
817 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
819 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
827 componentSqlString = [self _sqlStringForComponent: _component];
828 privacySqlString = [self _privacySqlString];
830 /* prepare mandatory fields */
832 fields = [NSMutableArray arrayWithArray: _fields];
833 [fields addObject: @"c_uid"];
834 [fields addObject: @"c_startdate"];
835 [fields addObject: @"c_enddate"];
838 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
840 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@",
841 dateSqlString, componentSqlString, privacySqlString];
843 /* fetch non-recurrent apts first */
844 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
846 records = [_folder fetchFields: fields matchingQualifier: qualifier];
850 records = [self fixupRecords: records fetchRange: r];
852 [self debugWithFormat: @"fetched %i records: %@",
853 [records count], records];
854 ma = [NSMutableArray arrayWithArray: records];
857 /* fetch recurrent apts now. we do NOT consider the date range when doing that
858 as the c_startdate/c_enddate of a recurring event is always set to the first
859 recurrence - others are generated on the fly */
860 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@", componentSqlString, privacySqlString];
862 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
864 records = [_folder fetchFields: fields matchingQualifier: qualifier];
869 records = [self fixupCyclicRecords: records fetchRange: r];
872 ma = [NSMutableArray arrayWithCapacity: [records count]];
874 [ma addObjectsFromArray: records];
878 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
883 [self debugWithFormat:@"returning %i records", [ma count]];
885 // [ma makeObjectsPerform: @selector (setObject:forKey:)
887 // withObject: @"owner"];
892 /* override this in subclasses */
893 - (NSArray *) fetchFields: (NSArray *) _fields
894 from: (NSCalendarDate *) _startDate
895 to: (NSCalendarDate *) _endDate
896 component: (id) _component
900 if ((folder = [self ocsFolder]) == nil) {
901 [self errorWithFormat:@"(%s): missing folder for fetch!",
902 __PRETTY_FUNCTION__];
906 return [self fetchFields: _fields fromFolder: folder
907 from: _startDate to: _endDate
908 component: _component];
911 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
912 to: (NSCalendarDate *) _endDate
914 static NSArray *infos = nil; // TODO: move to a plist file
917 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
918 @"c_isopaque", @"c_status", nil];
920 return [self fetchFields: infos from: _startDate to: _endDate
921 component: @"vevent"];
924 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
925 to: (NSCalendarDate *) _endDate
926 component: (id) _component
928 static NSArray *infos = nil; // TODO: move to a plist file
931 infos = [[NSArray alloc] initWithObjects:
932 @"c_name", @"c_component",
933 @"c_title", @"c_location", @"c_orgmail",
934 @"c_status", @"c_classification",
935 @"c_isallday", @"c_isopaque",
936 @"c_participants", @"c_partmails",
937 @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo",
940 return [self fetchFields: infos from: _startDate to: _endDate
941 component: _component];
946 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
949 // TODO: who calls this?
952 if ([_uid length] == 0)
955 url = [self baseURLInContext:_ctx];
956 if (![url hasSuffix: @"/"])
957 url = [url stringByAppendingString: @"/"];
959 // TODO: this should run a query to determine the uid!
960 return [url stringByAppendingString:_uid];
963 /* folder management */
965 - (id) lookupHomeFolderForUID: (NSString *) _uid
968 // TODO: DUP to SOGoGroupFolder
969 NSException *error = nil;
973 if (![_uid isNotNull])
976 /* create subcontext, so that we don't destroy our environment */
978 if ((ctx = [context createSubContext]) == nil) {
979 [self errorWithFormat:@"could not create SOPE subcontext!"];
985 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
989 result = [[ctx application] traversePathArray:path inContext:ctx
990 error:&error acquire:NO];
992 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
997 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
998 _uid, [path componentsJoinedByString:@"=>"], result];
1002 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1004 SOGoFolder *upperContainer;
1005 SOGoUserFolder *userFolder;
1006 SOGoAppointmentFolder *calendarFolder;
1008 upperContainer = [[self container] container];
1009 userFolder = [SOGoUserFolder objectWithName: uid
1010 inContainer: upperContainer];
1011 calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
1012 inContainer: userFolder];
1014 setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar/personal", uid]];
1015 [calendarFolder setOwner: uid];
1017 return calendarFolder;
1020 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1023 /* Note: can return NSNull objects in the array! */
1024 NSMutableArray *folders;
1026 NSString *uid, *ownerLogin;
1029 ownerLogin = [self ownerInContext: context];
1031 if ([_uids count] == 0) return nil;
1032 folders = [NSMutableArray arrayWithCapacity:16];
1033 e = [_uids objectEnumerator];
1034 while ((uid = [e nextObject]))
1036 if ([uid isEqualToString: ownerLogin])
1040 folder = [self lookupCalendarFolderForUID: uid];
1041 if (![folder isNotNull])
1042 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1045 [folders addObject: folder];
1051 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1052 inContext: (id) _ctx
1054 /* Note: can return NSNull objects in the array! */
1055 NSMutableArray *objs;
1059 if ([_uids count] == 0) return nil;
1060 objs = [NSMutableArray arrayWithCapacity:16];
1061 e = [_uids objectEnumerator];
1062 while ((uid = [e nextObject])) {
1065 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1066 if ([obj isNotNull]) {
1067 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1068 if ([obj isKindOfClass:[NSException class]])
1071 if (![obj isNotNull])
1072 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1074 /* Note: intentionally add 'null' folders to allow a mapping */
1075 [objs addObject:obj ? obj : [NSNull null]];
1080 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1082 /* Note: can return NSNull objects in the array! */
1083 NSMutableArray *uids;
1084 LDAPUserManager *um;
1087 if (_persons == nil)
1090 count = [_persons count];
1091 uids = [NSMutableArray arrayWithCapacity:count + 1];
1092 um = [LDAPUserManager sharedUserManager];
1094 for (i = 0; i < count; i++) {
1099 person = [_persons objectAtIndex:i];
1100 email = [person rfc822Email];
1101 if ([email isNotNull]) {
1102 uid = [um getUIDForEmail:email];
1107 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1112 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1113 inContext: (id) _ctx
1115 /* Note: can return NSNull objects in the array! */
1118 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1121 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1124 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1127 SOGoCustomGroupFolder *folder;
1132 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1133 return [folder autorelease];
1136 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1137 inContext: (id) _ctx
1139 SOGoCustomGroupFolder *folder;
1141 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1144 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1145 if (![folder isNotNull])
1147 if ([folder isKindOfClass:[NSException class]]) {
1148 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1158 - (NSArray *) fetchAllSOGoAppointments
1161 Note: very expensive method, do not use unless absolutely required.
1162 returns an array of SOGoAppointment objects.
1164 Note that we can leave out the filenames, supposed to be stored
1165 in the 'uid' field of the iCalendar object!
1167 NSMutableArray *events;
1168 NSDictionary *files;
1169 NSEnumerator *contents;
1172 /* fetch all raw contents */
1174 files = [self fetchContentStringsAndNamesOfAllObjects];
1175 if (![files isNotNull]) return nil;
1176 if ([files isKindOfClass:[NSException class]]) return (id)files;
1178 /* transform to SOGo appointments */
1180 events = [NSMutableArray arrayWithCapacity:[files count]];
1181 contents = [files objectEnumerator];
1182 while ((content = [contents nextObject]) != nil)
1183 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1188 // #warning We only support ONE calendar per user at this time
1189 // - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1190 // toFolderList: (NSMutableArray *) calendarFolders
1192 // NSEnumerator *keys;
1193 // NSString *currentKey;
1194 // NSMutableDictionary *currentCalendar;
1195 // BOOL firstShouldBeActive;
1196 // unsigned int count;
1198 // firstShouldBeActive = YES;
1200 // keys = [[subscribedFolders allKeys] objectEnumerator];
1201 // currentKey = [keys nextObject];
1203 // while (currentKey)
1205 // currentCalendar = [NSMutableDictionary new];
1206 // [currentCalendar autorelease];
1208 // setDictionary: [subscribedFolders objectForKey: currentKey]];
1209 // [currentCalendar setObject: currentKey forKey: @"folder"];
1210 // [calendarFolders addObject: currentCalendar];
1211 // if ([[currentCalendar objectForKey: @"active"] boolValue])
1212 // firstShouldBeActive = NO;
1214 // currentKey = [keys nextObject];
1217 // return firstShouldBeActive;
1220 // - (NSArray *) calendarFolders
1222 // NSMutableDictionary *userCalendar, *calendarDict;
1223 // NSMutableArray *calendarFolders;
1224 // SOGoUser *calendarUser;
1225 // BOOL firstActive;
1227 // calendarFolders = [NSMutableArray new];
1228 // [calendarFolders autorelease];
1230 // calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1232 // userCalendar = [NSMutableDictionary new];
1233 // [userCalendar autorelease];
1234 // [userCalendar setObject: @"/" forKey: @"folder"];
1235 // [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1236 // [calendarFolders addObject: userCalendar];
1238 // calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1239 // firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1240 // firstActive = ([self _appendSubscribedFolders:
1241 // [calendarDict objectForKey: @"SubscribedFolders"]
1242 // toFolderList: calendarFolders]
1244 // [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1245 // forKey: @"active"];
1247 // return calendarFolders;
1250 // - (NSArray *) fetchContentObjectNames
1252 // NSMutableArray *objectNames;
1253 // NSArray *records;
1254 // NSCalendarDate *today, *startDate, *endDate;
1256 // #warning this should be user-configurable
1257 // objectNames = [NSMutableArray array];
1258 // today = [[NSCalendarDate calendarDate] beginOfDay];
1259 // [today setTimeZone: timeZone];
1261 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1262 // hours: 0 minutes: 0 seconds: 0];
1263 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1264 // hours: 0 minutes: 0 seconds: 0];
1265 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1266 // from: startDate to: endDate
1267 // component: @"vevent"];
1268 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1269 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1270 // from: startDate to: endDate
1271 // component: @"vtodo"];
1272 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1274 // return objectNames;
1279 - (NSString *) folderType
1281 return @"Appointment";
1284 - (NSString *) outlookFolderClass
1286 return @"IPF.Appointment";
1291 NSUserDefaults *settings;
1292 NSArray *activeFolders;
1294 settings = [[context activeUser] userSettings];
1296 = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
1298 return [activeFolders containsObject: nameInContainer];
1301 @end /* SOGoAppointmentFolder */