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 <GDLContentStore/GCSFolder.h>
23 #import <NGCards/NGCards.h>
24 #import <NGObjWeb/WOContext.h>
25 #import <NGObjWeb/WOMessage.h>
26 #import <NGExtensions/NGCalendarDateRange.h>
27 #import <SaxObjC/SaxObjC.h>
28 #import <SaxObjC/XMLNamespaces.h>
30 // #import <NGObjWeb/SoClassSecurityInfo.h>
31 #import <SOGo/SOGoCustomGroupFolder.h>
32 #import <SOGo/LDAPUserManager.h>
33 #import <SOGo/SOGoPermissions.h>
34 #import <SOGo/SOGoUser.h>
38 #import "SOGoAppointmentObject.h"
39 #import "SOGoTaskObject.h"
41 #import "SOGoAppointmentFolder.h"
43 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
44 @interface NSDate(UsedPrivates)
45 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
49 @implementation SOGoAppointmentFolder
51 static NGLogger *logger = nil;
52 static NSNumber *sharedYes = nil;
56 return [super version] + 1 /* v1 */;
62 static BOOL didInit = NO;
63 // SoClassSecurityInfo *securityInfo;
68 NSAssert2([super version] == 0,
69 @"invalid superclass (%@) version %i !",
70 NSStringFromClass([self superclass]), [super version]);
72 lm = [NGLoggerManager defaultLoggerManager];
73 logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
75 // securityInfo = [self soClassSecurityInfo];
76 // [securityInfo declareRole: SOGoRole_Delegate
77 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
78 // [securityInfo declareRole: SOGoRole_Delegate
79 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
80 // [securityInfo declareRoles: [NSArray arrayWithObjects:
82 // SOGoRole_Assistant, nil]
83 // asDefaultForPermission: SoPerm_View];
85 sharedYes = [[NSNumber numberWithBool: YES] retain];
88 - (id) initWithName: (NSString *) name
89 inContainer: (id) newContainer
91 if ((self = [super initWithName: name inContainer: newContainer]))
93 timeZone = [[context activeUser] timeZone];
101 [uidToFilename release];
112 - (BOOL) folderIsMandatory
119 - (NSArray *) calendarUIDs
121 /* this is used for group calendars (this folder just returns itself) */
124 s = [[self container] nameInContainer];
125 // [self logWithFormat:@"CAL UID: %@", s];
126 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
131 - (BOOL) isValidAppointmentName: (NSString *)_key
133 return ([_key length] != 0);
136 - (void) appendObject: (NSDictionary *) object
137 withBaseURL: (NSString *) baseURL
138 toREPORTResponse: (WOResponse *) r
140 SOGoCalendarComponent *component;
141 Class componentClass;
142 NSString *c_name, *etagLine, *calString;
144 c_name = [object objectForKey: @"c_name"];
146 if ([[object objectForKey: @"component"] isEqualToString: @"vevent"])
147 componentClass = [SOGoAppointmentObject class];
149 componentClass = [SOGoTaskObject class];
151 component = [componentClass objectWithName: c_name inContainer: self];
153 [r appendContentString: @" <D:response>\r\n"];
154 [r appendContentString: @" <D:href>"];
155 [r appendContentString: baseURL];
156 if (![baseURL hasSuffix: @"/"])
157 [r appendContentString: @"/"];
158 [r appendContentString: c_name];
159 [r appendContentString: @"</D:href>\r\n"];
161 [r appendContentString: @" <D:propstat>\r\n"];
162 [r appendContentString: @" <D:prop>\r\n"];
163 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
164 [component davEntityTag]];
165 [r appendContentString: etagLine];
166 [r appendContentString: @" </D:prop>\r\n"];
167 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
168 [r appendContentString: @" </D:propstat>\r\n"];
169 [r appendContentString: @" <C:calendar-data>"];
170 calString = [[component contentAsString] stringByEscapingXMLString];
171 [r appendContentString: calString];
172 [r appendContentString: @"</C:calendar-data>\r\n"];
173 [r appendContentString: @" </D:response>\r\n"];
176 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
177 toFilter: (NSMutableDictionary *) filter
179 NSCalendarDate *parsedDate;
181 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
182 [filter setObject: parsedDate forKey: @"start"];
183 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
184 [filter setObject: parsedDate forKey: @"end"];
187 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
189 NSMutableDictionary *filterData;
190 id <DOMNode> parentNode;
191 id <DOMNodeList> ranges;
192 NSString *componentName;
194 parentNode = [filterElement parentNode];
195 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
196 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
198 componentName = [[filterElement attribute: @"name"] lowercaseString];
199 filterData = [NSMutableDictionary new];
200 [filterData autorelease];
201 [filterData setObject: componentName forKey: @"name"];
202 ranges = [filterElement getElementsByTagName: @"time-range"];
204 [self _appendTimeRange: [ranges objectAtIndex: 0]
205 toFilter: filterData];
213 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
215 NSEnumerator *children;
217 NSMutableArray *filters;
218 NSDictionary *filter;
220 filters = [NSMutableArray new];
222 children = [[parentNode getElementsByTagName: @"comp-filter"]
224 node = [children nextObject];
227 filter = [self _parseCalendarFilter: node];
229 [filters addObject: filter];
230 node = [children nextObject];
236 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
237 toResponse: (WOResponse *) response
240 unsigned int count, max;
241 NSDictionary *currentFilter, *appointment;
242 NSEnumerator *appointments;
245 baseURL = [self baseURLInContext: context];
247 max = [filters count];
248 for (count = 0; count < max; count++)
250 currentFilter = [filters objectAtIndex: 0];
251 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
252 to: [currentFilter objectForKey: @"end"]
253 component: [currentFilter objectForKey: @"name"]];
254 appointments = [apts objectEnumerator];
255 appointment = [appointments nextObject];
258 [self appendObject: appointment
260 toREPORTResponse: response];
261 appointment = [appointments nextObject];
266 - (NSArray *) davNamespaces
268 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
271 - (id) davCalendarQuery: (id) queryContext
275 id <DOMDocument> document;
277 r = [context response];
279 [r setContentEncoding: NSUTF8StringEncoding];
280 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
281 [r setHeader: @"no-cache" forKey: @"pragma"];
282 [r setHeader: @"no-cache" forKey: @"cache-control"];
283 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
284 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
285 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
287 document = [[context request] contentAsDOMDocument];
288 filters = [self _parseCalendarFilters: [document documentElement]];
289 [self _appendComponentsMatchingFilters: filters
291 [r appendContentString:@"</D:multistatus>\r\n"];
296 - (Class) objectClassForContent: (NSString *) content
298 iCalCalendar *calendar;
305 calendar = [iCalCalendar parseSingleFromSource: content];
308 elements = [calendar allObjects];
309 if ([elements count])
311 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
312 if ([firstTag isEqualToString: @"VEVENT"])
313 objectClass = [SOGoAppointmentObject class];
314 else if ([firstTag isEqualToString: @"VTODO"])
315 objectClass = [SOGoTaskObject class];
322 - (id) deduceObjectForName: (NSString *)_key
330 request = [_ctx request];
331 method = [request method];
332 if ([method isEqualToString: @"PUT"])
333 objectClass = [self objectClassForContent: [request contentAsString]];
335 objectClass = [self objectClassForResourceNamed: _key];
338 obj = [objectClass objectWithName: _key inContainer: self];
345 - (BOOL) requestNamedIsHandledLater: (NSString *) name
347 return [name isEqualToString: @"OPTIONS"];
350 - (id) lookupName: (NSString *)_key
358 /* first check attributes directly bound to the application */
359 handledLater = [self requestNamedIsHandledLater: _key];
364 obj = [super lookupName:_key inContext:_ctx acquire:NO];
367 if ([self isValidAppointmentName:_key])
369 url = [[[_ctx request] uri] urlWithoutParameters];
370 if ([url hasSuffix: @"AsTask"])
371 obj = [SOGoTaskObject objectWithName: _key
373 else if ([url hasSuffix: @"AsAppointment"])
374 obj = [SOGoAppointmentObject objectWithName: _key
377 obj = [self deduceObjectForName: _key
382 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
388 - (NSArray *) davComplianceClassesInContext: (id)_ctx
390 NSMutableArray *classes;
391 NSArray *primaryClasses;
393 classes = [NSMutableArray new];
394 [classes autorelease];
396 primaryClasses = [super davComplianceClassesInContext: _ctx];
398 [classes addObjectsFromArray: primaryClasses];
399 [classes addObject: @"access-control"];
400 [classes addObject: @"calendar-access"];
405 - (NSArray *) groupDavResourceType
407 return [NSArray arrayWithObjects: @"vevent-collection",
408 @"vtodo-collection", nil];
411 - (NSArray *) davResourceType
413 static NSArray *colType = nil;
414 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
418 gdRT = [self groupDavResourceType];
419 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
420 XMLNS_GROUPDAV, nil];
421 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
422 XMLNS_GROUPDAV, nil];
423 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
424 colType = [NSArray arrayWithObjects: @"collection", cdCol,
425 gdVEventCol, gdVTodoCol, nil];
432 /* vevent UID handling */
434 - (NSString *) resourceNameForEventUID: (NSString *)_u
435 inFolder: (GCSFolder *)_f
437 static NSArray *nameFields = nil;
438 EOQualifier *qualifier;
441 if (![_u isNotNull]) return nil;
443 [self errorWithFormat:@"(%s): missing folder for fetch!",
444 __PRETTY_FUNCTION__];
448 if (nameFields == nil)
449 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
451 qualifier = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _u];
452 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
454 if ([records count] == 1)
455 return [[records objectAtIndex:0] valueForKey:@"c_name"];
456 if ([records count] == 0)
459 [self errorWithFormat:
460 @"The storage contains more than file with the same UID!"];
461 return [[records objectAtIndex:0] valueForKey:@"c_name"];
464 - (NSString *) resourceNameForEventUID: (NSString *) _uid
470 if (![_uid isNotNull])
472 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
473 return [rname isNotNull] ? rname : nil;
475 if ((folder = [self ocsFolder]) == nil) {
476 [self errorWithFormat:@"(%s): missing folder for fetch!",
477 __PRETTY_FUNCTION__];
481 if (uidToFilename == nil)
482 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
484 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
485 [uidToFilename setObject:[NSNull null] forKey:_uid];
487 [uidToFilename setObject:rname forKey:_uid];
492 - (Class) objectClassForResourceNamed: (NSString *) c_name
494 EOQualifier *qualifier;
499 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", c_name];
500 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"component"]
501 matchingQualifier: qualifier];
505 component = [[records objectAtIndex:0] valueForKey: @"component"];
506 if ([component isEqualToString: @"vevent"])
507 objectClass = [SOGoAppointmentObject class];
508 else if ([component isEqualToString: @"vtodo"])
509 objectClass = [SOGoTaskObject class];
521 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
522 fetchRange: (NGCalendarDateRange *) _r
524 NSMutableDictionary *md;
527 md = [[_record mutableCopy] autorelease];
529 if ((tmp = [_record objectForKey:@"startdate"])) {
530 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
531 (NSTimeInterval)[tmp unsignedIntValue]];
532 [tmp setTimeZone: timeZone];
533 if (tmp) [md setObject:tmp forKey:@"startDate"];
537 [self logWithFormat:@"missing 'startdate' in record?"];
539 if ((tmp = [_record objectForKey:@"enddate"])) {
540 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
541 (NSTimeInterval)[tmp unsignedIntValue]];
542 [tmp setTimeZone: timeZone];
543 if (tmp) [md setObject:tmp forKey:@"endDate"];
547 [self logWithFormat:@"missing 'enddate' in record?"];
552 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
553 cycleRange: (NGCalendarDateRange *) _r
555 NSMutableDictionary *md;
558 md = [[_record mutableCopy] autorelease];
561 tmp = [_r startDate];
562 [tmp setTimeZone: timeZone];
563 [md setObject:tmp forKey:@"startDate"];
565 [tmp setTimeZone: timeZone];
566 [md setObject:tmp forKey:@"endDate"];
571 - (NSArray *) fixupRecords: (NSArray *) records
572 fetchRange: (NGCalendarDateRange *) r
574 // TODO: is the result supposed to be sorted by date?
577 id row; // TODO: what is the type of the record?
581 max = [records count];
582 ma = [NSMutableArray arrayWithCapacity: max];
583 for (count = 0; count < max; count++)
585 row = [self fixupRecord: [records objectAtIndex: count]
597 - (void) _flattenCycleRecord: (NSDictionary *) _row
598 forRange: (NGCalendarDateRange *) _r
599 intoArray: (NSMutableArray *) _ma
601 NSMutableDictionary *row;
602 NSDictionary *cycleinfo;
603 NSCalendarDate *startDate, *endDate;
604 NGCalendarDateRange *fir;
605 NSArray *rules, *exRules, *exDates, *ranges;
608 cycleinfo = [[_row objectForKey:@"cycleinfo"] propertyList];
609 if (cycleinfo == nil) {
610 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
614 row = [self fixupRecord:_row fetchRange: _r];
615 [row removeObjectForKey:@"cycleinfo"];
616 [row setObject:sharedYes forKey:@"isRecurrentEvent"];
618 startDate = [row objectForKey:@"startDate"];
619 endDate = [row objectForKey:@"endDate"];
620 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
622 rules = [cycleinfo objectForKey:@"rules"];
623 exRules = [cycleinfo objectForKey:@"exRules"];
624 exDates = [cycleinfo objectForKey:@"exDates"];
626 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
627 firstInstanceCalendarDateRange:fir
628 recurrenceRules:rules
629 exceptionRules:exRules
630 exceptionDates:exDates];
631 count = [ranges count];
632 for (i = 0; i < count; i++) {
633 NGCalendarDateRange *rRange;
636 rRange = [ranges objectAtIndex:i];
637 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
638 if (fixedRow != nil) [_ma addObject:fixedRow];
642 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
643 fetchRange: (NGCalendarDateRange *) _r
645 // TODO: is the result supposed to be sorted by date?
649 if (_records == nil) return nil;
650 if ((count = [_records count]) == 0)
653 ma = [NSMutableArray arrayWithCapacity:count];
654 for (i = 0; i < count; i++) {
655 id row; // TODO: what is the type of the record?
657 row = [_records objectAtIndex:i];
658 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
663 - (NSString *) _sqlStringForComponent: (id) _component
670 if ([_component isKindOfClass: [NSArray class]])
671 components = _component;
673 components = [NSArray arrayWithObject: _component];
676 = [NSString stringWithFormat: @" AND (component = '%@')",
677 [components componentsJoinedByString: @"' OR component = '"]];
685 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
686 to: (NSCalendarDate *) _endDate
688 unsigned int start, end;
690 start = (unsigned int) [_startDate timeIntervalSince1970];
691 end = (unsigned int) [_endDate timeIntervalSince1970];
693 return [NSString stringWithFormat:
694 @" AND (startdate <= %u) AND (enddate >= %u)",
698 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
700 NSMutableString *classificationString;
701 NSString *currentRole;
702 unsigned int counter;
703 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
704 iCalAccessConfidential};
706 classificationString = [NSMutableString string];
707 for (counter = 0; counter < 3; counter++)
709 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
711 if ([currentRole length] > 0)
712 [classificationString appendFormat: @"classification = %d or ",
716 return classificationString;
719 - (NSString *) _privacySqlString
721 NSString *privacySqlString, *login, *email;
722 SOGoUser *activeUser;
724 activeUser = [context activeUser];
725 login = [activeUser login];
727 if ([login isEqualToString: owner])
728 privacySqlString = @"";
729 else if ([login isEqualToString: @"freebusy"])
730 privacySqlString = @"and (isopaque = 1)";
733 email = [activeUser primaryEmail];
736 = [NSString stringWithFormat:
737 @"(%@(orgmail = '%@')"
738 @" or ((partmails caseInsensitiveLike '%@%%'"
739 @" or partmails caseInsensitiveLike '%%\n%@%%')))",
740 [self _privacyClassificationStringsForUID: login],
741 email, email, email];
744 return privacySqlString;
747 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
748 forUser: (NSString *) uid
750 NSString *accessRole, *prefix, *currentRole, *suffix;
755 if (accessClass == iCalAccessPublic)
757 else if (accessClass == iCalAccessPrivate)
760 prefix = @"Confidential";
762 acls = [[self aclsForUser: uid] objectEnumerator];
763 currentRole = [acls nextObject];
764 while (currentRole && !accessRole)
765 if ([currentRole hasPrefix: prefix])
767 suffix = [currentRole substringFromIndex: [prefix length]];
768 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
771 currentRole = [acls nextObject];
776 - (NSArray *) fetchFields: (NSArray *) _fields
777 fromFolder: (GCSFolder *) _folder
778 from: (NSCalendarDate *) _startDate
779 to: (NSCalendarDate *) _endDate
780 component: (id) _component
782 EOQualifier *qualifier;
783 NSMutableArray *fields, *ma = nil;
785 NSString *sql, *dateSqlString, *componentSqlString, *privacySqlString;
786 NGCalendarDateRange *r;
788 if (_folder == nil) {
789 [self errorWithFormat:@"(%s): missing folder for fetch!",
790 __PRETTY_FUNCTION__];
794 if (_startDate && _endDate)
796 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
798 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
806 componentSqlString = [self _sqlStringForComponent: _component];
807 privacySqlString = [self _privacySqlString];
809 /* prepare mandatory fields */
811 fields = [NSMutableArray arrayWithArray: _fields];
812 [fields addObject: @"uid"];
813 [fields addObject: @"startdate"];
814 [fields addObject: @"enddate"];
817 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
819 sql = [NSString stringWithFormat: @"(iscycle = 0)%@%@%@",
820 dateSqlString, componentSqlString, privacySqlString];
822 /* fetch non-recurrent apts first */
823 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
825 records = [_folder fetchFields: fields matchingQualifier: qualifier];
829 records = [self fixupRecords: records fetchRange: r];
831 [self debugWithFormat: @"fetched %i records: %@",
832 [records count], records];
833 ma = [NSMutableArray arrayWithArray: records];
836 /* fetch recurrent apts now */
837 sql = [NSString stringWithFormat: @"(iscycle = 1)%@%@%@",
838 dateSqlString, componentSqlString, privacySqlString];
839 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
841 [fields addObject: @"cycleinfo"];
843 records = [_folder fetchFields: fields matchingQualifier: qualifier];
847 [self debugWithFormat: @"fetched %i cyclic records: %@",
848 [records count], records];
850 records = [self fixupCyclicRecords: records fetchRange: r];
852 ma = [NSMutableArray arrayWithCapacity: [records count]];
854 [ma addObjectsFromArray: records];
858 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
863 [self debugWithFormat:@"returning %i records", [ma count]];
865 // [ma makeObjectsPerform: @selector (setObject:forKey:)
867 // withObject: @"owner"];
872 /* override this in subclasses */
873 - (NSArray *) fetchFields: (NSArray *) _fields
874 from: (NSCalendarDate *) _startDate
875 to: (NSCalendarDate *) _endDate
876 component: (id) _component
880 if ((folder = [self ocsFolder]) == nil) {
881 [self errorWithFormat:@"(%s): missing folder for fetch!",
882 __PRETTY_FUNCTION__];
886 return [self fetchFields: _fields fromFolder: folder
887 from: _startDate to: _endDate
888 component: _component];
892 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
893 to: (NSCalendarDate *) _endDate
895 static NSArray *infos = nil; // TODO: move to a plist file
898 infos = [[NSArray alloc] initWithObjects: @"partmails", @"partstates",
899 @"isopaque", @"status", nil];
901 return [self fetchFields: infos from: _startDate to: _endDate
902 component: @"vevent"];
905 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
906 to: (NSCalendarDate *) _endDate
907 component: (id) _component
909 static NSArray *infos = nil; // TODO: move to a plist file
912 infos = [[NSArray alloc] initWithObjects:
913 @"c_name", @"component",
914 @"title", @"location", @"orgmail",
915 @"status", @"classification",
916 @"isallday", @"isopaque",
917 @"participants", @"partmails",
918 @"partstates", @"sequence", @"priority", nil];
920 return [self fetchFields: infos from: _startDate to: _endDate
921 component: _component];
924 - (void) deleteEntriesWithIds: (NSArray *) ids
927 unsigned int count, max;
932 for (count = 0; count < max; count++)
934 currentId = [ids objectAtIndex: count];
936 = [self objectClassForResourceNamed: currentId];
937 deleteObject = [objectClass objectWithName: currentId
939 [deleteObject delete];
940 [deleteObject primaryDelete];
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 (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", 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;
1028 if ([_uids count] == 0) return nil;
1029 folders = [NSMutableArray arrayWithCapacity:16];
1030 e = [_uids objectEnumerator];
1031 while ((uid = [e nextObject])) {
1034 folder = [self lookupCalendarFolderForUID: uid];
1035 if (![folder isNotNull])
1036 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1038 /* Note: intentionally add 'null' folders to allow a mapping */
1039 [folders addObject:folder ? folder : [NSNull null]];
1044 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1045 inContext: (id) _ctx
1047 /* Note: can return NSNull objects in the array! */
1048 NSMutableArray *objs;
1052 if ([_uids count] == 0) return nil;
1053 objs = [NSMutableArray arrayWithCapacity:16];
1054 e = [_uids objectEnumerator];
1055 while ((uid = [e nextObject])) {
1058 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1059 if ([obj isNotNull]) {
1060 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1061 if ([obj isKindOfClass:[NSException class]])
1064 if (![obj isNotNull])
1065 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1067 /* Note: intentionally add 'null' folders to allow a mapping */
1068 [objs addObject:obj ? obj : [NSNull null]];
1073 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1075 /* Note: can return NSNull objects in the array! */
1076 NSMutableArray *uids;
1077 LDAPUserManager *um;
1080 if (_persons == nil)
1083 count = [_persons count];
1084 uids = [NSMutableArray arrayWithCapacity:count + 1];
1085 um = [LDAPUserManager sharedUserManager];
1087 for (i = 0; i < count; i++) {
1092 person = [_persons objectAtIndex:i];
1093 email = [person rfc822Email];
1094 if ([email isNotNull]) {
1095 uid = [um getUIDForEmail:email];
1100 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1105 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1106 inContext: (id) _ctx
1108 /* Note: can return NSNull objects in the array! */
1111 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1114 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1117 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1120 SOGoCustomGroupFolder *folder;
1125 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1126 return [folder autorelease];
1129 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1130 inContext: (id) _ctx
1132 SOGoCustomGroupFolder *folder;
1134 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1137 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1138 if (![folder isNotNull])
1140 if ([folder isKindOfClass:[NSException class]]) {
1141 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1151 - (NSArray *) fetchAllSOGoAppointments
1154 Note: very expensive method, do not use unless absolutely required.
1155 returns an array of SOGoAppointment objects.
1157 Note that we can leave out the filenames, supposed to be stored
1158 in the 'uid' field of the iCalendar object!
1160 NSMutableArray *events;
1161 NSDictionary *files;
1162 NSEnumerator *contents;
1165 /* fetch all raw contents */
1167 files = [self fetchContentStringsAndNamesOfAllObjects];
1168 if (![files isNotNull]) return nil;
1169 if ([files isKindOfClass:[NSException class]]) return (id)files;
1171 /* transform to SOGo appointments */
1173 events = [NSMutableArray arrayWithCapacity:[files count]];
1174 contents = [files objectEnumerator];
1175 while ((content = [contents nextObject]) != nil)
1176 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1181 #warning We only support ONE calendar per user at this time
1182 - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1183 toFolderList: (NSMutableArray *) calendarFolders
1186 NSString *currentKey;
1187 NSMutableDictionary *currentCalendar;
1188 BOOL firstShouldBeActive;
1191 firstShouldBeActive = YES;
1193 keys = [[subscribedFolders allKeys] objectEnumerator];
1194 currentKey = [keys nextObject];
1198 currentCalendar = [NSMutableDictionary new];
1199 [currentCalendar autorelease];
1201 setDictionary: [subscribedFolders objectForKey: currentKey]];
1202 [currentCalendar setObject: currentKey forKey: @"folder"];
1203 [calendarFolders addObject: currentCalendar];
1204 if ([[currentCalendar objectForKey: @"active"] boolValue])
1205 firstShouldBeActive = NO;
1207 currentKey = [keys nextObject];
1210 return firstShouldBeActive;
1213 - (NSArray *) calendarFolders
1215 NSMutableDictionary *userCalendar, *calendarDict;
1216 NSMutableArray *calendarFolders;
1217 SOGoUser *calendarUser;
1220 calendarFolders = [NSMutableArray new];
1221 [calendarFolders autorelease];
1223 calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1225 userCalendar = [NSMutableDictionary new];
1226 [userCalendar autorelease];
1227 [userCalendar setObject: @"/" forKey: @"folder"];
1228 [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1229 [calendarFolders addObject: userCalendar];
1231 calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1232 firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1233 firstActive = ([self _appendSubscribedFolders:
1234 [calendarDict objectForKey: @"SubscribedFolders"]
1235 toFolderList: calendarFolders]
1237 [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1240 return calendarFolders;
1243 // - (NSArray *) fetchContentObjectNames
1245 // NSMutableArray *objectNames;
1246 // NSArray *records;
1247 // NSCalendarDate *today, *startDate, *endDate;
1249 // #warning this should be user-configurable
1250 // objectNames = [NSMutableArray array];
1251 // today = [[NSCalendarDate calendarDate] beginOfDay];
1252 // [today setTimeZone: timeZone];
1254 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1255 // hours: 0 minutes: 0 seconds: 0];
1256 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1257 // hours: 0 minutes: 0 seconds: 0];
1258 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1259 // from: startDate to: endDate
1260 // component: @"vevent"];
1261 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1262 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1263 // from: startDate to: endDate
1264 // component: @"vtodo"];
1265 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1267 // return objectNames;
1272 - (NSString *) folderType
1274 return @"Appointment";
1277 - (NSString *) outlookFolderClass
1279 return @"IPF.Appointment";
1282 @end /* SOGoAppointmentFolder */