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/NSArray+Utilities.h>
52 #import <SOGo/NSString+Utilities.h>
53 #import <SOGo/SOGoUser.h>
55 #import "SOGoAppointmentObject.h"
56 #import "SOGoAppointmentFolders.h"
57 #import "SOGoTaskObject.h"
59 #import "SOGoAppointmentFolder.h"
61 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
62 @interface NSDate(UsedPrivates)
63 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
67 @implementation SOGoAppointmentFolder
69 static NGLogger *logger = nil;
70 static NSNumber *sharedYes = nil;
74 return [super version] + 1 /* v1 */;
80 static BOOL didInit = NO;
81 // SoClassSecurityInfo *securityInfo;
86 NSAssert2([super version] == 0,
87 @"invalid superclass (%@) version %i !",
88 NSStringFromClass([self superclass]), [super version]);
90 lm = [NGLoggerManager defaultLoggerManager];
91 logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
93 // securityInfo = [self soClassSecurityInfo];
94 // [securityInfo declareRole: SOGoRole_Delegate
95 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
96 // [securityInfo declareRole: SOGoRole_Delegate
97 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
98 // [securityInfo declareRoles: [NSArray arrayWithObjects:
100 // SOGoRole_Assistant, nil]
101 // asDefaultForPermission: SoPerm_View];
103 sharedYes = [[NSNumber numberWithBool: YES] retain];
106 - (id) initWithName: (NSString *) name
107 inContainer: (id) newContainer
109 if ((self = [super initWithName: name inContainer: newContainer]))
111 timeZone = [[context activeUser] timeZone];
119 [uidToFilename release];
132 - (NSArray *) calendarUIDs
134 /* this is used for group calendars (this folder just returns itself) */
137 s = [[self container] nameInContainer];
138 // [self logWithFormat:@"CAL UID: %@", s];
139 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
144 - (BOOL) isValidAppointmentName: (NSString *)_key
146 return ([_key length] != 0);
149 - (void) appendObject: (NSDictionary *) object
150 withBaseURL: (NSString *) baseURL
151 toREPORTResponse: (WOResponse *) r
153 SOGoCalendarComponent *component;
154 Class componentClass;
155 NSString *name, *etagLine, *calString;
157 name = [object objectForKey: @"c_name"];
159 if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
160 componentClass = [SOGoAppointmentObject class];
162 componentClass = [SOGoTaskObject class];
164 component = [componentClass objectWithName: name inContainer: self];
166 [r appendContentString: @" <D:response>\r\n"];
167 [r appendContentString: @" <D:href>"];
168 [r appendContentString: baseURL];
169 if (![baseURL hasSuffix: @"/"])
170 [r appendContentString: @"/"];
171 [r appendContentString: name];
172 [r appendContentString: @"</D:href>\r\n"];
174 [r appendContentString: @" <D:propstat>\r\n"];
175 [r appendContentString: @" <D:prop>\r\n"];
176 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
177 [component davEntityTag]];
178 [r appendContentString: etagLine];
179 [r appendContentString: @" </D:prop>\r\n"];
180 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
181 [r appendContentString: @" </D:propstat>\r\n"];
182 [r appendContentString: @" <C:calendar-data>"];
183 calString = [[component contentAsString] stringByEscapingXMLString];
184 [r appendContentString: calString];
185 [r appendContentString: @"</C:calendar-data>\r\n"];
186 [r appendContentString: @" </D:response>\r\n"];
189 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
190 toFilter: (NSMutableDictionary *) filter
192 NSCalendarDate *parsedDate;
194 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
195 [filter setObject: parsedDate forKey: @"start"];
196 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
197 [filter setObject: parsedDate forKey: @"end"];
200 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
202 NSMutableDictionary *filterData;
203 id <DOMNode> parentNode;
204 id <DOMNodeList> ranges;
205 NSString *componentName;
207 parentNode = [filterElement parentNode];
208 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
209 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
211 componentName = [[filterElement attribute: @"name"] lowercaseString];
212 filterData = [NSMutableDictionary new];
213 [filterData autorelease];
214 [filterData setObject: componentName forKey: @"name"];
215 ranges = [filterElement getElementsByTagName: @"time-range"];
217 [self _appendTimeRange: [ranges objectAtIndex: 0]
218 toFilter: filterData];
226 #warning filters is leaked here
227 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
229 NSEnumerator *children;
231 NSMutableArray *filters;
232 NSDictionary *filter;
234 filters = [NSMutableArray new];
236 children = [[parentNode getElementsByTagName: @"comp-filter"]
238 node = [children nextObject];
241 filter = [self _parseCalendarFilter: node];
243 [filters addObject: filter];
244 node = [children nextObject];
250 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
251 toResponse: (WOResponse *) response
254 unsigned int count, max;
255 NSDictionary *currentFilter, *appointment;
256 NSEnumerator *appointments;
259 baseURL = [self baseURLInContext: context];
261 max = [filters count];
262 for (count = 0; count < max; count++)
264 #warning huh? why not objectAtIndex: count?
265 currentFilter = [filters objectAtIndex: 0];
266 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
267 to: [currentFilter objectForKey: @"end"]
268 component: [currentFilter objectForKey: @"name"]];
269 appointments = [apts objectEnumerator];
270 appointment = [appointments nextObject];
273 [self appendObject: appointment
275 toREPORTResponse: response];
276 appointment = [appointments nextObject];
281 - (NSArray *) davNamespaces
283 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
286 - (id) davCalendarQuery: (id) queryContext
290 id <DOMDocument> document;
292 r = [context response];
294 [r setContentEncoding: NSUTF8StringEncoding];
295 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
296 [r setHeader: @"no-cache" forKey: @"pragma"];
297 [r setHeader: @"no-cache" forKey: @"cache-control"];
298 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
299 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
300 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
302 document = [[context request] contentAsDOMDocument];
303 filters = [self _parseCalendarFilters: [document documentElement]];
304 [self _appendComponentsMatchingFilters: filters
306 [r appendContentString:@"</D:multistatus>\r\n"];
311 - (Class) objectClassForContent: (NSString *) content
313 iCalCalendar *calendar;
320 calendar = [iCalCalendar parseSingleFromSource: content];
323 elements = [calendar allObjects];
324 if ([elements count])
326 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
327 if ([firstTag isEqualToString: @"VEVENT"])
328 objectClass = [SOGoAppointmentObject class];
329 else if ([firstTag isEqualToString: @"VTODO"])
330 objectClass = [SOGoTaskObject class];
337 - (id) deduceObjectForName: (NSString *)_key
345 request = [_ctx request];
346 method = [request method];
347 if ([method isEqualToString: @"PUT"])
348 objectClass = [self objectClassForContent: [request contentAsString]];
350 objectClass = [self objectClassForResourceNamed: _key];
353 obj = [objectClass objectWithName: _key inContainer: self];
360 - (BOOL) requestNamedIsHandledLater: (NSString *) name
362 return [name isEqualToString: @"OPTIONS"];
365 - (id) lookupName: (NSString *)_key
373 /* first check attributes directly bound to the application */
374 handledLater = [self requestNamedIsHandledLater: _key];
379 obj = [super lookupName:_key inContext:_ctx acquire:NO];
382 if ([self isValidAppointmentName:_key])
384 url = [[[_ctx request] uri] urlWithoutParameters];
385 if ([url hasSuffix: @"AsTask"])
386 obj = [SOGoTaskObject objectWithName: _key
388 else if ([url hasSuffix: @"AsAppointment"])
389 obj = [SOGoAppointmentObject objectWithName: _key
392 obj = [self deduceObjectForName: _key
397 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
403 - (NSArray *) davComplianceClassesInContext: (id)_ctx
405 NSMutableArray *classes;
406 NSArray *primaryClasses;
408 classes = [NSMutableArray new];
409 [classes autorelease];
411 primaryClasses = [super davComplianceClassesInContext: _ctx];
413 [classes addObjectsFromArray: primaryClasses];
414 [classes addObject: @"access-control"];
415 [classes addObject: @"calendar-access"];
420 - (NSArray *) groupDavResourceType
422 return [NSArray arrayWithObjects: @"vevent-collection",
423 @"vtodo-collection", nil];
426 - (NSArray *) davResourceType
428 static NSArray *colType = nil;
429 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
433 gdRT = [self groupDavResourceType];
434 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
435 XMLNS_GROUPDAV, nil];
436 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
437 XMLNS_GROUPDAV, nil];
438 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
439 colType = [NSArray arrayWithObjects: @"collection", cdCol,
440 gdVEventCol, gdVTodoCol, nil];
447 /* vevent UID handling */
449 - (NSString *) resourceNameForEventUID: (NSString *)_u
450 inFolder: (GCSFolder *)_f
452 static NSArray *nameFields = nil;
453 EOQualifier *qualifier;
456 if (![_u isNotNull]) return nil;
458 [self errorWithFormat:@"(%s): missing folder for fetch!",
459 __PRETTY_FUNCTION__];
463 if (nameFields == nil)
464 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
466 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
467 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
469 if ([records count] == 1)
470 return [[records objectAtIndex:0] valueForKey:@"c_name"];
471 if ([records count] == 0)
474 [self errorWithFormat:
475 @"The storage contains more than file with the same UID!"];
476 return [[records objectAtIndex:0] valueForKey:@"c_name"];
479 - (NSString *) resourceNameForEventUID: (NSString *) _uid
485 if (![_uid isNotNull])
487 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
488 return [rname isNotNull] ? rname : nil;
490 if ((folder = [self ocsFolder]) == nil) {
491 [self errorWithFormat:@"(%s): missing folder for fetch!",
492 __PRETTY_FUNCTION__];
496 if (uidToFilename == nil)
497 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
499 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
500 [uidToFilename setObject:[NSNull null] forKey:_uid];
502 [uidToFilename setObject:rname forKey:_uid];
507 - (Class) objectClassForResourceNamed: (NSString *) name
509 EOQualifier *qualifier;
514 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
515 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
516 matchingQualifier: qualifier];
520 component = [[records objectAtIndex:0] valueForKey: @"c_component"];
521 if ([component isEqualToString: @"vevent"])
522 objectClass = [SOGoAppointmentObject class];
523 else if ([component isEqualToString: @"vtodo"])
524 objectClass = [SOGoTaskObject class];
536 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
537 fetchRange: (NGCalendarDateRange *) _r
539 NSMutableDictionary *md;
542 md = [[_record mutableCopy] autorelease];
544 if ((tmp = [_record objectForKey:@"c_startdate"])) {
545 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
546 (NSTimeInterval)[tmp unsignedIntValue]];
547 [tmp setTimeZone: timeZone];
548 if (tmp) [md setObject:tmp forKey:@"startDate"];
552 [self logWithFormat:@"missing 'startdate' in record?"];
554 if ((tmp = [_record objectForKey:@"c_enddate"])) {
555 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
556 (NSTimeInterval)[tmp unsignedIntValue]];
557 [tmp setTimeZone: timeZone];
558 if (tmp) [md setObject:tmp forKey:@"endDate"];
562 [self logWithFormat:@"missing 'enddate' in record?"];
567 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
568 cycleRange: (NGCalendarDateRange *) _r
570 NSMutableDictionary *md;
573 md = [[_record mutableCopy] autorelease];
575 /* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
576 the reccurence since we use those when displaying events in SOGo Web */
577 tmp = [_r startDate];
578 [tmp setTimeZone: timeZone];
579 [md setObject:tmp forKey:@"startDate"];
580 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"];
582 [tmp setTimeZone: timeZone];
583 [md setObject:tmp forKey:@"endDate"];
584 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"];
589 - (NSArray *) fixupRecords: (NSArray *) records
590 fetchRange: (NGCalendarDateRange *) r
592 // TODO: is the result supposed to be sorted by date?
595 id row; // TODO: what is the type of the record?
599 max = [records count];
600 ma = [NSMutableArray arrayWithCapacity: max];
601 for (count = 0; count < max; count++)
603 row = [self fixupRecord: [records objectAtIndex: count]
615 - (void) _flattenCycleRecord: (NSDictionary *) _row
616 forRange: (NGCalendarDateRange *) _r
617 intoArray: (NSMutableArray *) _ma
619 NSMutableDictionary *row;
620 NSDictionary *cycleinfo;
621 NSCalendarDate *startDate, *endDate;
622 NGCalendarDateRange *fir;
623 NSArray *rules, *exRules, *exDates, *ranges;
626 cycleinfo = [[_row objectForKey:@"c_cycleinfo"] propertyList];
627 if (cycleinfo == nil) {
628 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
632 row = [self fixupRecord:_row fetchRange: _r];
633 [row removeObjectForKey: @"c_cycleinfo"];
634 [row setObject: sharedYes forKey:@"isRecurrentEvent"];
636 startDate = [row objectForKey:@"startDate"];
637 endDate = [row objectForKey:@"endDate"];
638 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
640 rules = [cycleinfo objectForKey:@"rules"];
641 exRules = [cycleinfo objectForKey:@"exRules"];
642 exDates = [cycleinfo objectForKey:@"exDates"];
644 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
645 firstInstanceCalendarDateRange:fir
646 recurrenceRules:rules
647 exceptionRules:exRules
648 exceptionDates:exDates];
649 count = [ranges count];
651 for (i = 0; i < count; i++) {
652 NGCalendarDateRange *rRange;
655 rRange = [ranges objectAtIndex:i];
656 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
659 [_ma addObject:fixedRow];
664 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
665 fetchRange: (NGCalendarDateRange *) _r
667 // TODO: is the result supposed to be sorted by date?
671 if (_records == nil) return nil;
672 if ((count = [_records count]) == 0)
675 ma = [NSMutableArray arrayWithCapacity:count];
676 for (i = 0; i < count; i++) {
677 id row; // TODO: what is the type of the record?
679 row = [_records objectAtIndex:i];
680 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
685 - (NSString *) _sqlStringForComponent: (id) _component
692 if ([_component isKindOfClass: [NSArray class]])
693 components = _component;
695 components = [NSArray arrayWithObject: _component];
698 = [NSString stringWithFormat: @" AND (c_component = '%@')",
699 [components componentsJoinedByString: @"' OR c_component = '"]];
707 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
708 to: (NSCalendarDate *) _endDate
710 unsigned int start, end;
712 start = (unsigned int) [_startDate timeIntervalSince1970];
713 end = (unsigned int) [_endDate timeIntervalSince1970];
715 return [NSString stringWithFormat:
716 @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
720 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
722 NSMutableString *classificationString;
723 NSString *currentRole;
724 unsigned int counter;
725 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
726 iCalAccessConfidential};
728 classificationString = [NSMutableString string];
729 for (counter = 0; counter < 3; counter++)
731 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
733 if ([currentRole length] > 0)
734 [classificationString appendFormat: @"c_classification = %d or ",
738 return classificationString;
741 - (NSString *) _privacySqlString
743 NSString *privacySqlString, *login, *email;
744 SOGoUser *activeUser;
746 activeUser = [context activeUser];
747 login = [activeUser login];
749 if ([login isEqualToString: owner])
750 privacySqlString = @"";
751 else if ([login isEqualToString: @"freebusy"])
752 privacySqlString = @"and (c_isopaque = 1)";
755 #warning we do not manage all the possible user emails
756 email = [[activeUser primaryIdentity] objectForKey: @"email"];
759 = [NSString stringWithFormat:
760 @"(%@(c_orgmail = '%@')"
761 @" or ((c_partmails caseInsensitiveLike '%@%%'"
762 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
763 [self _privacyClassificationStringsForUID: login],
764 email, email, email];
767 return privacySqlString;
770 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
771 forUser: (NSString *) uid
773 NSString *accessRole, *prefix, *currentRole, *suffix;
778 if (accessClass == iCalAccessPublic)
780 else if (accessClass == iCalAccessPrivate)
783 prefix = @"Confidential";
785 acls = [[self aclsForUser: uid] objectEnumerator];
786 currentRole = [acls nextObject];
787 while (currentRole && !accessRole)
788 if ([currentRole hasPrefix: prefix])
790 suffix = [currentRole substringFromIndex: [prefix length]];
791 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
794 currentRole = [acls nextObject];
799 - (NSArray *) fetchFields: (NSArray *) _fields
800 fromFolder: (GCSFolder *) _folder
801 from: (NSCalendarDate *) _startDate
802 to: (NSCalendarDate *) _endDate
803 component: (id) _component
805 EOQualifier *qualifier;
806 NSMutableArray *fields, *ma = nil;
808 NSString *sql, *dateSqlString, *componentSqlString, *privacySqlString;
809 NGCalendarDateRange *r;
811 if (_folder == nil) {
812 [self errorWithFormat:@"(%s): missing folder for fetch!",
813 __PRETTY_FUNCTION__];
817 if (_startDate && _endDate)
819 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
821 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
829 componentSqlString = [self _sqlStringForComponent: _component];
830 privacySqlString = [self _privacySqlString];
832 /* prepare mandatory fields */
834 fields = [NSMutableArray arrayWithArray: _fields];
835 [fields addObject: @"c_uid"];
836 [fields addObject: @"c_startdate"];
837 [fields addObject: @"c_enddate"];
840 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
842 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@",
843 dateSqlString, componentSqlString, privacySqlString];
845 /* fetch non-recurrent apts first */
846 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
848 records = [_folder fetchFields: fields matchingQualifier: qualifier];
852 records = [self fixupRecords: records fetchRange: r];
854 [self debugWithFormat: @"fetched %i records: %@",
855 [records count], records];
856 ma = [NSMutableArray arrayWithArray: records];
859 /* fetch recurrent apts now. we do NOT consider the date range when doing that
860 as the c_startdate/c_enddate of a recurring event is always set to the first
861 recurrence - others are generated on the fly */
862 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@", componentSqlString, privacySqlString];
864 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
866 records = [_folder fetchFields: fields matchingQualifier: qualifier];
871 records = [self fixupCyclicRecords: records fetchRange: r];
874 ma = [NSMutableArray arrayWithCapacity: [records count]];
876 [ma addObjectsFromArray: records];
880 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
885 [self debugWithFormat:@"returning %i records", [ma count]];
887 // [ma makeObjectsPerform: @selector (setObject:forKey:)
889 // withObject: @"owner"];
894 /* override this in subclasses */
895 - (NSArray *) fetchFields: (NSArray *) _fields
896 from: (NSCalendarDate *) _startDate
897 to: (NSCalendarDate *) _endDate
898 component: (id) _component
902 if ((folder = [self ocsFolder]) == nil) {
903 [self errorWithFormat:@"(%s): missing folder for fetch!",
904 __PRETTY_FUNCTION__];
908 return [self fetchFields: _fields fromFolder: folder
909 from: _startDate to: _endDate
910 component: _component];
913 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
914 to: (NSCalendarDate *) _endDate
916 static NSArray *infos = nil; // TODO: move to a plist file
919 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
920 @"c_isopaque", @"c_status", nil];
922 return [self fetchFields: infos from: _startDate to: _endDate
923 component: @"vevent"];
926 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
927 to: (NSCalendarDate *) _endDate
928 component: (id) _component
930 static NSArray *infos = nil; // TODO: move to a plist file
933 infos = [[NSArray alloc] initWithObjects:
934 @"c_name", @"c_component",
935 @"c_title", @"c_location", @"c_orgmail",
936 @"c_status", @"c_classification",
937 @"c_isallday", @"c_isopaque",
938 @"c_participants", @"c_partmails",
939 @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo",
942 return [self fetchFields: infos from: _startDate to: _endDate
943 component: _component];
948 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
951 // TODO: who calls this?
954 if ([_uid length] == 0)
957 url = [self baseURLInContext:_ctx];
958 if (![url hasSuffix: @"/"])
959 url = [url stringByAppendingString: @"/"];
961 // TODO: this should run a query to determine the uid!
962 return [url stringByAppendingString:_uid];
965 /* folder management */
969 NSMutableArray *folderSubscription;
970 NSUserDefaults *userSettings;
971 NSMutableDictionary *calendarSettings;
977 ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]
979 userSettings = [ownerUser userSettings];
980 calendarSettings = [userSettings objectForKey: @"Calendar"];
981 if (!calendarSettings)
983 calendarSettings = [NSMutableDictionary dictionary];
984 [userSettings setObject: calendarSettings forKey: @"Calendar"];
987 = [calendarSettings objectForKey: @"ActiveFolders"];
988 if (!folderSubscription)
990 folderSubscription = [NSMutableArray array];
991 [calendarSettings setObject: folderSubscription
992 forKey: @"ActiveFolders"];
994 [folderSubscription addObjectUniquely: nameInContainer];
995 [userSettings synchronize];
1001 - (id) lookupHomeFolderForUID: (NSString *) _uid
1004 // TODO: DUP to SOGoGroupFolder
1005 NSException *error = nil;
1009 if (![_uid isNotNull])
1012 /* create subcontext, so that we don't destroy our environment */
1014 if ((ctx = [context createSubContext]) == nil) {
1015 [self errorWithFormat:@"could not create SOPE subcontext!"];
1021 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1025 result = [[ctx application] traversePathArray:path inContext:ctx
1026 error:&error acquire:NO];
1028 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1033 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1034 _uid, [path componentsJoinedByString:@"=>"], result];
1038 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1040 SOGoFolder *currentContainer;
1041 SOGoAppointmentFolders *parent;
1044 currentContainer = [[container container] container];
1045 currentContainer = [currentContainer lookupName: uid
1048 parent = [currentContainer lookupName: @"Calendar" inContext: context
1050 currentContainer = [parent lookupName: @"personal" inContext: context
1052 if (!currentContainer)
1054 error = [parent newFolderWithName: [parent defaultFolderName]
1055 andNameInContainer: @"personal"];
1057 currentContainer = [parent lookupName: @"personal"
1062 return (SOGoAppointmentFolder *) currentContainer;
1065 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1068 /* Note: can return NSNull objects in the array! */
1069 NSMutableArray *folders;
1071 NSString *uid, *ownerLogin;
1074 ownerLogin = [self ownerInContext: context];
1076 if ([_uids count] == 0) return nil;
1077 folders = [NSMutableArray arrayWithCapacity:16];
1078 e = [_uids objectEnumerator];
1079 while ((uid = [e nextObject]))
1081 if ([uid isEqualToString: ownerLogin])
1085 folder = [self lookupCalendarFolderForUID: uid];
1086 if (![folder isNotNull])
1087 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1091 [folders addObject: folder];
1097 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1098 inContext: (id) _ctx
1100 /* Note: can return NSNull objects in the array! */
1101 NSMutableArray *objs;
1105 if ([_uids count] == 0) return nil;
1106 objs = [NSMutableArray arrayWithCapacity:16];
1107 e = [_uids objectEnumerator];
1108 while ((uid = [e nextObject])) {
1111 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1112 if ([obj isNotNull]) {
1113 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1114 if ([obj isKindOfClass:[NSException class]])
1117 if (![obj isNotNull])
1118 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1120 /* Note: intentionally add 'null' folders to allow a mapping */
1121 [objs addObject:obj ? obj : [NSNull null]];
1126 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1128 /* Note: can return NSNull objects in the array! */
1129 NSMutableArray *uids;
1130 LDAPUserManager *um;
1133 if (_persons == nil)
1136 count = [_persons count];
1137 uids = [NSMutableArray arrayWithCapacity:count + 1];
1138 um = [LDAPUserManager sharedUserManager];
1140 for (i = 0; i < count; i++) {
1145 person = [_persons objectAtIndex:i];
1146 email = [person rfc822Email];
1147 if ([email isNotNull]) {
1148 uid = [um getUIDForEmail:email];
1153 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1158 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1159 inContext: (id) _ctx
1161 /* Note: can return NSNull objects in the array! */
1164 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1167 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1170 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1173 SOGoCustomGroupFolder *folder;
1178 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1179 return [folder autorelease];
1182 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1183 inContext: (id) _ctx
1185 SOGoCustomGroupFolder *folder;
1187 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1190 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1191 if (![folder isNotNull])
1193 if ([folder isKindOfClass:[NSException class]]) {
1194 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1204 - (NSArray *) fetchAllSOGoAppointments
1207 Note: very expensive method, do not use unless absolutely required.
1208 returns an array of SOGoAppointment objects.
1210 Note that we can leave out the filenames, supposed to be stored
1211 in the 'uid' field of the iCalendar object!
1213 NSMutableArray *events;
1214 NSDictionary *files;
1215 NSEnumerator *contents;
1218 /* fetch all raw contents */
1220 files = [self fetchContentStringsAndNamesOfAllObjects];
1221 if (![files isNotNull]) return nil;
1222 if ([files isKindOfClass:[NSException class]]) return (id)files;
1224 /* transform to SOGo appointments */
1226 events = [NSMutableArray arrayWithCapacity:[files count]];
1227 contents = [files objectEnumerator];
1228 while ((content = [contents nextObject]) != nil)
1229 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1234 // #warning We only support ONE calendar per user at this time
1235 // - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1236 // toFolderList: (NSMutableArray *) calendarFolders
1238 // NSEnumerator *keys;
1239 // NSString *currentKey;
1240 // NSMutableDictionary *currentCalendar;
1241 // BOOL firstShouldBeActive;
1242 // unsigned int count;
1244 // firstShouldBeActive = YES;
1246 // keys = [[subscribedFolders allKeys] objectEnumerator];
1247 // currentKey = [keys nextObject];
1249 // while (currentKey)
1251 // currentCalendar = [NSMutableDictionary new];
1252 // [currentCalendar autorelease];
1254 // setDictionary: [subscribedFolders objectForKey: currentKey]];
1255 // [currentCalendar setObject: currentKey forKey: @"folder"];
1256 // [calendarFolders addObject: currentCalendar];
1257 // if ([[currentCalendar objectForKey: @"active"] boolValue])
1258 // firstShouldBeActive = NO;
1260 // currentKey = [keys nextObject];
1263 // return firstShouldBeActive;
1266 // - (NSArray *) calendarFolders
1268 // NSMutableDictionary *userCalendar, *calendarDict;
1269 // NSMutableArray *calendarFolders;
1270 // SOGoUser *calendarUser;
1271 // BOOL firstActive;
1273 // calendarFolders = [NSMutableArray new];
1274 // [calendarFolders autorelease];
1276 // calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1278 // userCalendar = [NSMutableDictionary new];
1279 // [userCalendar autorelease];
1280 // [userCalendar setObject: @"/" forKey: @"folder"];
1281 // [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1282 // [calendarFolders addObject: userCalendar];
1284 // calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1285 // firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1286 // firstActive = ([self _appendSubscribedFolders:
1287 // [calendarDict objectForKey: @"SubscribedFolders"]
1288 // toFolderList: calendarFolders]
1290 // [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1291 // forKey: @"active"];
1293 // return calendarFolders;
1296 // - (NSArray *) fetchContentObjectNames
1298 // NSMutableArray *objectNames;
1299 // NSArray *records;
1300 // NSCalendarDate *today, *startDate, *endDate;
1302 // #warning this should be user-configurable
1303 // objectNames = [NSMutableArray array];
1304 // today = [[NSCalendarDate calendarDate] beginOfDay];
1305 // [today setTimeZone: timeZone];
1307 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1308 // hours: 0 minutes: 0 seconds: 0];
1309 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1310 // hours: 0 minutes: 0 seconds: 0];
1311 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1312 // from: startDate to: endDate
1313 // component: @"vevent"];
1314 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1315 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1316 // from: startDate to: endDate
1317 // component: @"vtodo"];
1318 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1320 // return objectNames;
1325 - (NSString *) folderType
1327 return @"Appointment";
1330 - (NSString *) outlookFolderClass
1332 return @"IPF.Appointment";
1337 NSUserDefaults *settings;
1338 NSArray *activeFolders;
1340 settings = [[context activeUser] userSettings];
1342 = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
1344 return [activeFolders containsObject: nameInContainer];
1347 @end /* SOGoAppointmentFolder */