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/NSUserDefaults.h>
25 #import <Foundation/NSValue.h>
27 #import <NGObjWeb/NSException+HTTP.h>
28 #import <NGObjWeb/SoObject+SoDAV.h>
29 #import <NGObjWeb/SoSecurityManager.h>
30 #import <NGObjWeb/WOContext+SoObjects.h>
31 #import <NGObjWeb/WOMessage.h>
32 #import <NGObjWeb/WORequest.h>
33 #import <NGObjWeb/WOResponse.h>
34 #import <NGExtensions/NGLoggerManager.h>
35 #import <NGExtensions/NSString+misc.h>
36 #import <GDLContentStore/GCSFolder.h>
37 #import <DOM/DOMProtocols.h>
38 #import <EOControl/EOQualifier.h>
39 #import <NGCards/iCalCalendar.h>
40 #import <NGCards/iCalDateTime.h>
41 #import <NGCards/iCalPerson.h>
42 #import <NGCards/iCalRecurrenceCalculator.h>
43 #import <NGCards/NSString+NGCards.h>
44 #import <NGExtensions/NGCalendarDateRange.h>
45 #import <NGExtensions/NSNull+misc.h>
46 #import <NGExtensions/NSObject+Logs.h>
47 #import <SaxObjC/SaxObjC.h>
48 #import <SaxObjC/XMLNamespaces.h>
50 // #import <NGObjWeb/SoClassSecurityInfo.h>
51 #import <SOGo/SOGoCustomGroupFolder.h>
52 #import <SOGo/LDAPUserManager.h>
53 #import <SOGo/SOGoPermissions.h>
54 #import <SOGo/NSArray+Utilities.h>
55 #import <SOGo/NSString+Utilities.h>
56 #import <SOGo/SOGoUser.h>
58 #import "SOGoAppointmentObject.h"
59 #import "SOGoAppointmentFolders.h"
60 #import "SOGoTaskObject.h"
62 #import "SOGoAppointmentFolder.h"
64 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
65 @interface NSDate(UsedPrivates)
66 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
70 @implementation SOGoAppointmentFolder
72 static NGLogger *logger = nil;
73 static NSNumber *sharedYes = nil;
77 return [super version] + 1 /* v1 */;
83 static BOOL didInit = NO;
84 // SoClassSecurityInfo *securityInfo;
89 NSAssert2([super version] == 0,
90 @"invalid superclass (%@) version %i !",
91 NSStringFromClass([self superclass]), [super version]);
93 lm = [NGLoggerManager defaultLoggerManager];
94 logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
96 // securityInfo = [self soClassSecurityInfo];
97 // [securityInfo declareRole: SOGoRole_Delegate
98 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
99 // [securityInfo declareRole: SOGoRole_Delegate
100 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
101 // [securityInfo declareRoles: [NSArray arrayWithObjects:
102 // SOGoRole_Delegate,
103 // SOGoRole_Assistant, nil]
104 // asDefaultForPermission: SoPerm_View];
106 sharedYes = [[NSNumber numberWithBool: YES] retain];
109 - (id) initWithName: (NSString *) name
110 inContainer: (id) newContainer
112 if ((self = [super initWithName: name inContainer: newContainer]))
114 timeZone = [[context activeUser] timeZone];
115 aclMatrix = [NSMutableDictionary new];
126 [stripFields release];
127 [uidToFilename release];
140 - (NSArray *) calendarUIDs
142 /* this is used for group calendars (this folder just returns itself) */
145 s = [[self container] nameInContainer];
146 // [self logWithFormat:@"CAL UID: %@", s];
147 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
152 - (BOOL) isValidAppointmentName: (NSString *)_key
154 return ([_key length] != 0);
157 - (void) appendObject: (NSDictionary *) object
158 withBaseURL: (NSString *) baseURL
159 toREPORTResponse: (WOResponse *) r
161 SOGoCalendarComponent *component;
162 Class componentClass;
163 NSString *name, *etagLine, *calString;
165 name = [object objectForKey: @"c_name"];
167 if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
168 componentClass = [SOGoAppointmentObject class];
170 componentClass = [SOGoTaskObject class];
172 component = [componentClass objectWithName: name inContainer: self];
174 [r appendContentString: @" <D:response>\r\n"];
175 [r appendContentString: @" <D:href>"];
176 [r appendContentString: baseURL];
177 if (![baseURL hasSuffix: @"/"])
178 [r appendContentString: @"/"];
179 [r appendContentString: name];
180 [r appendContentString: @"</D:href>\r\n"];
182 [r appendContentString: @" <D:propstat>\r\n"];
183 [r appendContentString: @" <D:prop>\r\n"];
184 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
185 [component davEntityTag]];
186 [r appendContentString: etagLine];
187 [r appendContentString: @" </D:prop>\r\n"];
188 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
189 [r appendContentString: @" </D:propstat>\r\n"];
190 [r appendContentString: @" <C:calendar-data>"];
191 calString = [[component contentAsString] stringByEscapingXMLString];
192 [r appendContentString: calString];
193 [r appendContentString: @"</C:calendar-data>\r\n"];
194 [r appendContentString: @" </D:response>\r\n"];
197 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
198 toFilter: (NSMutableDictionary *) filter
200 NSCalendarDate *parsedDate;
202 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
203 [filter setObject: parsedDate forKey: @"start"];
204 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
205 [filter setObject: parsedDate forKey: @"end"];
208 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
210 NSMutableDictionary *filterData;
211 id <DOMNode> parentNode;
212 id <DOMNodeList> ranges;
213 NSString *componentName;
215 parentNode = [filterElement parentNode];
216 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
217 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
219 componentName = [[filterElement attribute: @"name"] lowercaseString];
220 filterData = [NSMutableDictionary new];
221 [filterData autorelease];
222 [filterData setObject: componentName forKey: @"name"];
223 ranges = [filterElement getElementsByTagName: @"time-range"];
225 [self _appendTimeRange: [ranges objectAtIndex: 0]
226 toFilter: filterData];
234 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
236 NSEnumerator *children;
238 NSMutableArray *filters;
239 NSDictionary *filter;
241 filters = [NSMutableArray array];
242 children = [[parentNode getElementsByTagName: @"comp-filter"]
244 node = [children nextObject];
247 filter = [self _parseCalendarFilter: node];
249 [filters addObject: filter];
250 node = [children nextObject];
256 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
257 toResponse: (WOResponse *) response
260 unsigned int count, max;
261 NSDictionary *currentFilter, *appointment;
262 NSEnumerator *appointments;
265 baseURL = [self baseURLInContext: context];
267 max = [filters count];
268 for (count = 0; count < max; count++)
270 currentFilter = [filters objectAtIndex: count];
271 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
272 to: [currentFilter objectForKey: @"end"]
273 title: [currentFilter objectForKey: @"title"]
274 component: [currentFilter objectForKey: @"name"]];
275 appointments = [apts objectEnumerator];
276 appointment = [appointments nextObject];
279 [self appendObject: appointment
281 toREPORTResponse: response];
282 appointment = [appointments nextObject];
287 - (NSArray *) davNamespaces
289 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
292 - (id) davCalendarQuery: (id) queryContext
296 id <DOMDocument> document;
298 r = [context response];
300 [r setContentEncoding: NSUTF8StringEncoding];
301 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
302 [r setHeader: @"no-cache" forKey: @"pragma"];
303 [r setHeader: @"no-cache" forKey: @"cache-control"];
304 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
305 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
306 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
308 document = [[context request] contentAsDOMDocument];
309 filters = [self _parseCalendarFilters: [document documentElement]];
310 [self _appendComponentsMatchingFilters: filters
312 [r appendContentString:@"</D:multistatus>\r\n"];
317 - (Class) objectClassForContent: (NSString *) content
319 iCalCalendar *calendar;
326 calendar = [iCalCalendar parseSingleFromSource: content];
329 elements = [calendar allObjects];
330 if ([elements count])
332 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
333 if ([firstTag isEqualToString: @"VEVENT"])
334 objectClass = [SOGoAppointmentObject class];
335 else if ([firstTag isEqualToString: @"VTODO"])
336 objectClass = [SOGoTaskObject class];
343 - (id) deduceObjectForName: (NSString *)_key
351 request = [_ctx request];
352 method = [request method];
353 if ([method isEqualToString: @"PUT"])
354 objectClass = [self objectClassForContent: [request contentAsString]];
356 objectClass = [self objectClassForResourceNamed: _key];
359 obj = [objectClass objectWithName: _key inContainer: self];
366 - (BOOL) requestNamedIsHandledLater: (NSString *) name
368 return [name isEqualToString: @"OPTIONS"];
371 - (id) lookupName: (NSString *)_key
379 /* first check attributes directly bound to the application */
380 handledLater = [self requestNamedIsHandledLater: _key];
385 obj = [super lookupName:_key inContext:_ctx acquire:NO];
388 if ([self isValidAppointmentName:_key])
390 url = [[[_ctx request] uri] urlWithoutParameters];
391 if ([url hasSuffix: @"AsTask"])
392 obj = [SOGoTaskObject objectWithName: _key
394 else if ([url hasSuffix: @"AsAppointment"])
395 obj = [SOGoAppointmentObject objectWithName: _key
398 obj = [self deduceObjectForName: _key
403 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
409 - (NSArray *) davComplianceClassesInContext: (id)_ctx
411 NSMutableArray *classes;
412 NSArray *primaryClasses;
414 classes = [NSMutableArray new];
415 [classes autorelease];
417 primaryClasses = [super davComplianceClassesInContext: _ctx];
419 [classes addObjectsFromArray: primaryClasses];
420 [classes addObject: @"access-control"];
421 [classes addObject: @"calendar-access"];
426 - (NSArray *) groupDavResourceType
428 return [NSArray arrayWithObjects: @"vevent-collection",
429 @"vtodo-collection", nil];
432 - (NSArray *) davResourceType
434 static NSArray *colType = nil;
435 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
439 gdRT = [self groupDavResourceType];
440 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
441 XMLNS_GROUPDAV, nil];
442 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
443 XMLNS_GROUPDAV, nil];
444 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
445 colType = [NSArray arrayWithObjects: @"collection", cdCol,
446 gdVEventCol, gdVTodoCol, nil];
453 /* vevent UID handling */
455 - (NSString *) resourceNameForEventUID: (NSString *)_u
456 inFolder: (GCSFolder *)_f
458 static NSArray *nameFields = nil;
459 EOQualifier *qualifier;
462 if (![_u isNotNull]) return nil;
464 [self errorWithFormat:@"(%s): missing folder for fetch!",
465 __PRETTY_FUNCTION__];
469 if (nameFields == nil)
470 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
472 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
473 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
475 if ([records count] == 1)
476 return [[records objectAtIndex:0] valueForKey:@"c_name"];
477 if ([records count] == 0)
480 [self errorWithFormat:
481 @"The storage contains more than file with the same UID!"];
482 return [[records objectAtIndex:0] valueForKey:@"c_name"];
485 - (NSString *) resourceNameForEventUID: (NSString *) _uid
491 if (![_uid isNotNull])
493 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
494 return [rname isNotNull] ? rname : nil;
496 if ((folder = [self ocsFolder]) == nil) {
497 [self errorWithFormat:@"(%s): missing folder for fetch!",
498 __PRETTY_FUNCTION__];
502 if (uidToFilename == nil)
503 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
505 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
506 [uidToFilename setObject:[NSNull null] forKey:_uid];
508 [uidToFilename setObject:rname forKey:_uid];
513 - (Class) objectClassForResourceNamed: (NSString *) name
515 EOQualifier *qualifier;
520 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
521 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
522 matchingQualifier: qualifier];
526 component = [[records objectAtIndex:0] valueForKey: @"c_component"];
527 if ([component isEqualToString: @"vevent"])
528 objectClass = [SOGoAppointmentObject class];
529 else if ([component isEqualToString: @"vtodo"])
530 objectClass = [SOGoTaskObject class];
542 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
543 fetchRange: (NGCalendarDateRange *) _r
545 NSMutableDictionary *md;
548 md = [[_record mutableCopy] autorelease];
550 if ((tmp = [_record objectForKey:@"c_startdate"])) {
551 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
552 (NSTimeInterval)[tmp unsignedIntValue]];
553 [tmp setTimeZone: timeZone];
554 if (tmp) [md setObject:tmp forKey:@"startDate"];
558 [self logWithFormat:@"missing 'startdate' in record?"];
560 if ((tmp = [_record objectForKey:@"c_enddate"])) {
561 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
562 (NSTimeInterval)[tmp unsignedIntValue]];
563 [tmp setTimeZone: timeZone];
564 if (tmp) [md setObject:tmp forKey:@"endDate"];
568 [self logWithFormat:@"missing 'enddate' in record?"];
573 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
574 cycleRange: (NGCalendarDateRange *) _r
576 NSMutableDictionary *md;
579 md = [[_record mutableCopy] autorelease];
581 /* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
582 the reccurence since we use those when displaying events in SOGo Web */
583 tmp = [_r startDate];
584 [tmp setTimeZone: timeZone];
585 [md setObject:tmp forKey:@"startDate"];
586 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"];
588 [tmp setTimeZone: timeZone];
589 [md setObject:tmp forKey:@"endDate"];
590 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"];
595 - (NSArray *) fixupRecords: (NSArray *) records
596 fetchRange: (NGCalendarDateRange *) r
598 // TODO: is the result supposed to be sorted by date?
601 id row; // TODO: what is the type of the record?
605 max = [records count];
606 ma = [NSMutableArray arrayWithCapacity: max];
607 for (count = 0; count < max; count++)
609 row = [self fixupRecord: [records objectAtIndex: count]
621 - (void) _flattenCycleRecord: (NSDictionary *) _row
622 forRange: (NGCalendarDateRange *) _r
623 intoArray: (NSMutableArray *) _ma
625 NSMutableDictionary *row;
626 NSDictionary *cycleinfo;
627 NSCalendarDate *startDate, *endDate;
628 NGCalendarDateRange *fir;
629 NSArray *rules, *exRules, *exDates, *ranges;
632 cycleinfo = [[_row objectForKey:@"c_cycleinfo"] propertyList];
633 if (cycleinfo == nil) {
634 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
638 row = [self fixupRecord:_row fetchRange: _r];
639 [row removeObjectForKey: @"c_cycleinfo"];
640 [row setObject: sharedYes forKey:@"isRecurrentEvent"];
642 startDate = [row objectForKey:@"startDate"];
643 endDate = [row objectForKey:@"endDate"];
644 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
646 rules = [cycleinfo objectForKey:@"rules"];
647 exRules = [cycleinfo objectForKey:@"exRules"];
648 exDates = [cycleinfo objectForKey:@"exDates"];
650 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
651 firstInstanceCalendarDateRange:fir
652 recurrenceRules:rules
653 exceptionRules:exRules
654 exceptionDates:exDates];
655 count = [ranges count];
657 for (i = 0; i < count; i++) {
658 NGCalendarDateRange *rRange;
661 rRange = [ranges objectAtIndex:i];
662 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
665 [_ma addObject:fixedRow];
670 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
671 fetchRange: (NGCalendarDateRange *) _r
673 // TODO: is the result supposed to be sorted by date?
677 if (_records == nil) return nil;
678 if ((count = [_records count]) == 0)
681 ma = [NSMutableArray arrayWithCapacity:count];
682 for (i = 0; i < count; i++) {
683 id row; // TODO: what is the type of the record?
685 row = [_records objectAtIndex:i];
686 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
691 - (NSString *) _sqlStringForComponent: (id) _component
698 if ([_component isKindOfClass: [NSArray class]])
699 components = _component;
701 components = [NSArray arrayWithObject: _component];
704 = [NSString stringWithFormat: @" AND (c_component = '%@')",
705 [components componentsJoinedByString: @"' OR c_component = '"]];
713 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
714 to: (NSCalendarDate *) _endDate
716 unsigned int start, end;
718 start = (unsigned int) [_startDate timeIntervalSince1970];
719 end = (unsigned int) [_endDate timeIntervalSince1970];
721 return [NSString stringWithFormat:
722 @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
726 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
728 NSMutableString *classificationString;
729 NSString *currentRole;
730 unsigned int counter;
731 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
732 iCalAccessConfidential};
734 classificationString = [NSMutableString string];
735 for (counter = 0; counter < 3; counter++)
737 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
739 if ([currentRole length] > 0)
740 [classificationString appendFormat: @"c_classification = %d or ",
744 return classificationString;
747 - (NSString *) _privacySqlString
749 NSString *privacySqlString, *login, *email;
750 SOGoUser *activeUser;
752 activeUser = [context activeUser];
753 login = [activeUser login];
755 if ([login isEqualToString: owner])
756 privacySqlString = @"";
757 else if ([login isEqualToString: @"freebusy"])
758 privacySqlString = @"and (c_isopaque = 1)";
761 #warning we do not manage all the possible user emails
762 email = [[activeUser primaryIdentity] objectForKey: @"email"];
765 = [NSString stringWithFormat:
766 @"(%@(c_orgmail = '%@')"
767 @" or ((c_partmails caseInsensitiveLike '%@%%'"
768 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
769 [self _privacyClassificationStringsForUID: login],
770 email, email, email];
773 return privacySqlString;
776 - (NSArray *) subscriptionRoles
778 return [NSArray arrayWithObjects:
779 SOGoRole_ObjectCreator, SOGoRole_ObjectEraser,
780 SOGoCalendarRole_PublicResponder,
781 SOGoCalendarRole_PublicModifier,
782 SOGoCalendarRole_PublicViewer,
783 SOGoCalendarRole_PublicDAndTViewer,
784 SOGoCalendarRole_PrivateResponder,
785 SOGoCalendarRole_PrivateModifier,
786 SOGoCalendarRole_PrivateViewer,
787 SOGoCalendarRole_PrivateDAndTViewer,
788 SOGoCalendarRole_ConfidentialResponder,
789 SOGoCalendarRole_ConfidentialModifier,
790 SOGoCalendarRole_ConfidentialViewer,
791 SOGoCalendarRole_ConfidentialDAndTViewer, nil];
794 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
795 forUser: (NSString *) uid
797 NSString *accessRole, *prefix, *currentRole, *suffix;
799 NSMutableDictionary *userRoles;
803 if (accessClass == iCalAccessPublic)
805 else if (accessClass == iCalAccessPrivate)
808 prefix = @"Confidential";
810 userRoles = [aclMatrix objectForKey: uid];
813 userRoles = [NSMutableDictionary dictionaryWithCapacity: 3];
814 [aclMatrix setObject: userRoles forKey: uid];
817 accessRole = [userRoles objectForKey: prefix];
820 acls = [[self aclsForUser: uid] objectEnumerator];
821 currentRole = [acls nextObject];
822 while (currentRole && !accessRole)
823 if ([currentRole hasPrefix: prefix])
825 suffix = [currentRole substringFromIndex: [prefix length]];
826 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
829 currentRole = [acls nextObject];
832 [userRoles setObject: accessRole forKey: prefix];
838 - (void) _buildStripFieldsFromFields: (NSArray *) fields
840 stripFields = [[NSMutableArray alloc] initWithCapacity: [fields count]];
841 [stripFields setArray: fields];
842 [stripFields removeObjectsInArray: [NSArray arrayWithObjects: @"c_name",
843 @"c_uid", @"c_startdate",
844 @"c_enddate", @"c_isallday",
847 @"c_component", nil]];
850 - (void) _fixupProtectedInformation: (NSEnumerator *) ma
851 inFields: (NSArray *) fields
852 forUser: (NSString *) uid
854 NSMutableDictionary *currentRecord;
855 NSString *roles[] = {nil, nil, nil};
856 iCalAccessClass accessClass;
857 NSString *fullRole, *role;
860 [self _buildStripFieldsFromFields: fields];
862 #warning we do not take the participation status into account
863 while ((currentRecord = [ma nextObject]))
866 = [[currentRecord objectForKey: @"c_classification"] intValue];
867 role = roles[accessClass];
870 fullRole = [self roleForComponentsWithAccessClass: accessClass
872 if ([fullRole length] > 9)
873 role = [fullRole substringFromIndex: 9];
874 roles[accessClass] = role;
876 if ([role isEqualToString: @"DAndTViewer"])
877 [currentRecord removeObjectsForKeys: stripFields];
881 - (NSArray *) fetchFields: (NSArray *) _fields
882 fromFolder: (GCSFolder *) _folder
883 from: (NSCalendarDate *) _startDate
884 to: (NSCalendarDate *) _endDate
885 title: (NSString *) title
886 component: (id) _component
888 EOQualifier *qualifier;
889 NSMutableArray *fields, *ma = nil;
891 NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
892 *privacySqlString, *currentLogin;
893 NGCalendarDateRange *r;
897 [self errorWithFormat:@"(%s): missing folder for fetch!",
898 __PRETTY_FUNCTION__];
902 if (_startDate && _endDate)
904 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
906 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
915 titleSqlString = [NSString stringWithFormat: @"AND (c_title"
916 @" isCaseInsensitiveLike: '%%%@%%')", title];
918 titleSqlString = @"";
920 componentSqlString = [self _sqlStringForComponent: _component];
921 privacySqlString = [self _privacySqlString];
923 /* prepare mandatory fields */
925 fields = [NSMutableArray arrayWithArray: _fields];
926 [fields addObject: @"c_uid"];
927 [fields addObject: @"c_startdate"];
928 [fields addObject: @"c_enddate"];
931 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
933 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@%@",
934 dateSqlString, titleSqlString,
935 componentSqlString, privacySqlString];
937 /* fetch non-recurrent apts first */
938 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
940 records = [_folder fetchFields: fields matchingQualifier: qualifier];
944 records = [self fixupRecords: records fetchRange: r];
946 [self debugWithFormat: @"fetched %i records: %@",
947 [records count], records];
948 ma = [NSMutableArray arrayWithArray: records];
951 /* fetch recurrent apts now. we do NOT consider the date range when doing that
952 as the c_startdate/c_enddate of a recurring event is always set to the first
953 recurrence - others are generated on the fly */
954 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@", titleSqlString,
955 componentSqlString, privacySqlString];
957 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
959 records = [_folder fetchFields: fields matchingQualifier: qualifier];
964 records = [self fixupCyclicRecords: records fetchRange: r];
967 ma = [NSMutableArray arrayWithCapacity: [records count]];
969 [ma addObjectsFromArray: records];
973 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
978 [self debugWithFormat:@"returning %i records", [ma count]];
980 currentLogin = [[context activeUser] login];
981 if (![currentLogin isEqualToString: owner])
982 [self _fixupProtectedInformation: [ma objectEnumerator]
984 forUser: currentLogin];
985 // [ma makeObjectsPerform: @selector (setObject:forKey:)
987 // withObject: @"owner"];
992 /* override this in subclasses */
993 - (NSArray *) fetchFields: (NSArray *) _fields
994 from: (NSCalendarDate *) _startDate
995 to: (NSCalendarDate *) _endDate
996 title: (NSString *) title
997 component: (id) _component
1001 if ((folder = [self ocsFolder]) == nil) {
1002 [self errorWithFormat:@"(%s): missing folder for fetch!",
1003 __PRETTY_FUNCTION__];
1007 return [self fetchFields: _fields fromFolder: folder
1008 from: _startDate to: _endDate
1010 component: _component];
1013 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
1014 to: (NSCalendarDate *) _endDate
1016 static NSArray *infos = nil; // TODO: move to a plist file
1019 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
1020 @"c_isopaque", @"c_status", nil];
1022 return [self fetchFields: infos from: _startDate to: _endDate
1024 component: @"vevent"];
1027 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
1028 to: (NSCalendarDate *) _endDate
1029 title: (NSString *) title
1030 component: (id) _component
1032 static NSArray *infos = nil; // TODO: move to a plist file
1035 infos = [[NSArray alloc] initWithObjects:
1036 @"c_name", @"c_component",
1037 @"c_title", @"c_location", @"c_orgmail",
1038 @"c_status", @"c_classification",
1039 @"c_isallday", @"c_isopaque",
1040 @"c_participants", @"c_partmails",
1041 @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo",
1044 return [self fetchFields: infos from: _startDate to: _endDate title: title
1045 component: _component];
1048 /* URL generation */
1050 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
1053 // TODO: who calls this?
1056 if ([_uid length] == 0)
1059 url = [self baseURLInContext:_ctx];
1060 if (![url hasSuffix: @"/"])
1061 url = [url stringByAppendingString: @"/"];
1063 // TODO: this should run a query to determine the uid!
1064 return [url stringByAppendingString:_uid];
1067 /* folder management */
1071 NSMutableArray *folderSubscription;
1072 NSUserDefaults *userSettings;
1073 NSMutableDictionary *calendarSettings;
1074 SOGoUser *ownerUser;
1076 rc = [super create];
1079 ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1081 userSettings = [ownerUser userSettings];
1082 calendarSettings = [userSettings objectForKey: @"Calendar"];
1083 if (!calendarSettings)
1085 calendarSettings = [NSMutableDictionary dictionary];
1086 [userSettings setObject: calendarSettings forKey: @"Calendar"];
1089 = [calendarSettings objectForKey: @"ActiveFolders"];
1090 if (!folderSubscription)
1092 folderSubscription = [NSMutableArray array];
1093 [calendarSettings setObject: folderSubscription
1094 forKey: @"ActiveFolders"];
1096 [folderSubscription addObjectUniquely: nameInContainer];
1097 [userSettings synchronize];
1103 - (id) lookupHomeFolderForUID: (NSString *) _uid
1106 // TODO: DUP to SOGoGroupFolder
1107 NSException *error = nil;
1111 if (![_uid isNotNull])
1114 /* create subcontext, so that we don't destroy our environment */
1116 if ((ctx = [context createSubContext]) == nil) {
1117 [self errorWithFormat:@"could not create SOPE subcontext!"];
1123 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1127 result = [[ctx application] traversePathArray:path inContext:ctx
1128 error:&error acquire:NO];
1130 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1135 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1136 _uid, [path componentsJoinedByString:@"=>"], result];
1140 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1142 SOGoFolder *currentContainer;
1143 SOGoAppointmentFolders *parent;
1146 currentContainer = [[container container] container];
1147 currentContainer = [currentContainer lookupName: uid
1150 parent = [currentContainer lookupName: @"Calendar" inContext: context
1152 currentContainer = [parent lookupName: @"personal" inContext: context
1154 if (!currentContainer)
1156 error = [parent newFolderWithName: [parent defaultFolderName]
1157 andNameInContainer: @"personal"];
1159 currentContainer = [parent lookupName: @"personal"
1164 return (SOGoAppointmentFolder *) currentContainer;
1167 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1170 /* Note: can return NSNull objects in the array! */
1171 NSMutableArray *folders;
1173 NSString *uid, *ownerLogin;
1176 ownerLogin = [self ownerInContext: context];
1178 if ([_uids count] == 0) return nil;
1179 folders = [NSMutableArray arrayWithCapacity:16];
1180 e = [_uids objectEnumerator];
1181 while ((uid = [e nextObject]))
1183 if ([uid isEqualToString: ownerLogin])
1187 folder = [self lookupCalendarFolderForUID: uid];
1188 if (![folder isNotNull])
1189 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1193 [folders addObject: folder];
1199 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1200 inContext: (id) _ctx
1202 /* Note: can return NSNull objects in the array! */
1203 NSMutableArray *objs;
1207 if ([_uids count] == 0) return nil;
1208 objs = [NSMutableArray arrayWithCapacity:16];
1209 e = [_uids objectEnumerator];
1210 while ((uid = [e nextObject])) {
1213 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1214 if ([obj isNotNull]) {
1215 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1216 if ([obj isKindOfClass:[NSException class]])
1219 if (![obj isNotNull])
1220 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1222 /* Note: intentionally add 'null' folders to allow a mapping */
1223 [objs addObject:obj ? obj : [NSNull null]];
1228 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1230 /* Note: can return NSNull objects in the array! */
1231 NSMutableArray *uids;
1232 LDAPUserManager *um;
1235 if (_persons == nil)
1238 count = [_persons count];
1239 uids = [NSMutableArray arrayWithCapacity:count + 1];
1240 um = [LDAPUserManager sharedUserManager];
1242 for (i = 0; i < count; i++) {
1247 person = [_persons objectAtIndex:i];
1248 email = [person rfc822Email];
1249 if ([email isNotNull]) {
1250 uid = [um getUIDForEmail:email];
1255 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1260 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1261 inContext: (id) _ctx
1263 /* Note: can return NSNull objects in the array! */
1266 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1269 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1272 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1275 SOGoCustomGroupFolder *folder;
1280 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1281 return [folder autorelease];
1284 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1285 inContext: (id) _ctx
1287 SOGoCustomGroupFolder *folder;
1289 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1292 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1293 if (![folder isNotNull])
1295 if ([folder isKindOfClass:[NSException class]]) {
1296 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1306 - (NSArray *) fetchAllSOGoAppointments
1309 Note: very expensive method, do not use unless absolutely required.
1310 returns an array of SOGoAppointment objects.
1312 Note that we can leave out the filenames, supposed to be stored
1313 in the 'uid' field of the iCalendar object!
1315 NSMutableArray *events;
1316 NSDictionary *files;
1317 NSEnumerator *contents;
1320 /* fetch all raw contents */
1322 files = [self fetchContentStringsAndNamesOfAllObjects];
1323 if (![files isNotNull]) return nil;
1324 if ([files isKindOfClass:[NSException class]]) return (id)files;
1326 /* transform to SOGo appointments */
1328 events = [NSMutableArray arrayWithCapacity:[files count]];
1329 contents = [files objectEnumerator];
1330 while ((content = [contents nextObject]) != nil)
1331 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1336 // #warning We only support ONE calendar per user at this time
1337 // - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1338 // toFolderList: (NSMutableArray *) calendarFolders
1340 // NSEnumerator *keys;
1341 // NSString *currentKey;
1342 // NSMutableDictionary *currentCalendar;
1343 // BOOL firstShouldBeActive;
1344 // unsigned int count;
1346 // firstShouldBeActive = YES;
1348 // keys = [[subscribedFolders allKeys] objectEnumerator];
1349 // currentKey = [keys nextObject];
1351 // while (currentKey)
1353 // currentCalendar = [NSMutableDictionary new];
1354 // [currentCalendar autorelease];
1356 // setDictionary: [subscribedFolders objectForKey: currentKey]];
1357 // [currentCalendar setObject: currentKey forKey: @"folder"];
1358 // [calendarFolders addObject: currentCalendar];
1359 // if ([[currentCalendar objectForKey: @"active"] boolValue])
1360 // firstShouldBeActive = NO;
1362 // currentKey = [keys nextObject];
1365 // return firstShouldBeActive;
1368 // - (NSArray *) calendarFolders
1370 // NSMutableDictionary *userCalendar, *calendarDict;
1371 // NSMutableArray *calendarFolders;
1372 // SOGoUser *calendarUser;
1373 // BOOL firstActive;
1375 // calendarFolders = [NSMutableArray new];
1376 // [calendarFolders autorelease];
1378 // calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1380 // userCalendar = [NSMutableDictionary new];
1381 // [userCalendar autorelease];
1382 // [userCalendar setObject: @"/" forKey: @"folder"];
1383 // [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1384 // [calendarFolders addObject: userCalendar];
1386 // calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1387 // firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1388 // firstActive = ([self _appendSubscribedFolders:
1389 // [calendarDict objectForKey: @"SubscribedFolders"]
1390 // toFolderList: calendarFolders]
1392 // [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1393 // forKey: @"active"];
1395 // return calendarFolders;
1398 // - (NSArray *) fetchContentObjectNames
1400 // NSMutableArray *objectNames;
1401 // NSArray *records;
1402 // NSCalendarDate *today, *startDate, *endDate;
1404 // #warning this should be user-configurable
1405 // objectNames = [NSMutableArray array];
1406 // today = [[NSCalendarDate calendarDate] beginOfDay];
1407 // [today setTimeZone: timeZone];
1409 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1410 // hours: 0 minutes: 0 seconds: 0];
1411 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1412 // hours: 0 minutes: 0 seconds: 0];
1413 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1414 // from: startDate to: endDate
1415 // component: @"vevent"];
1416 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1417 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1418 // from: startDate to: endDate
1419 // component: @"vtodo"];
1420 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1422 // return objectNames;
1425 - (NSArray *) fetchContentObjectNames
1427 static NSArray *cNameField = nil;
1430 cNameField = [[NSArray alloc] initWithObjects: @"c_name", nil];
1432 return [[self fetchFields: cNameField from: nil to: nil
1433 title: nil component: nil] objectsForKey: @"c_name"];
1438 - (NSString *) folderType
1440 return @"Appointment";
1443 - (NSString *) outlookFolderClass
1445 return @"IPF.Appointment";
1450 NSUserDefaults *settings;
1451 NSArray *activeFolders;
1453 settings = [[context activeUser] userSettings];
1455 = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
1457 return [activeFolders containsObject: nameInContainer];
1460 @end /* SOGoAppointmentFolder */