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/SOGoCache.h>
52 #import <SOGo/SOGoCustomGroupFolder.h>
53 #import <SOGo/LDAPUserManager.h>
54 #import <SOGo/SOGoPermissions.h>
55 #import <SOGo/NSArray+Utilities.h>
56 #import <SOGo/NSString+Utilities.h>
57 #import <SOGo/SOGoUser.h>
59 #import "SOGoAppointmentObject.h"
60 #import "SOGoAppointmentFolders.h"
61 #import "SOGoTaskObject.h"
63 #import "SOGoAppointmentFolder.h"
65 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
66 @interface NSDate(UsedPrivates)
67 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
71 @implementation SOGoAppointmentFolder
73 static NGLogger *logger = nil;
74 static NSNumber *sharedYes = nil;
78 return [super version] + 1 /* v1 */;
84 static BOOL didInit = NO;
85 // SoClassSecurityInfo *securityInfo;
90 NSAssert2([super version] == 0,
91 @"invalid superclass (%@) version %i !",
92 NSStringFromClass([self superclass]), [super version]);
94 lm = [NGLoggerManager defaultLoggerManager];
95 logger = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
97 // securityInfo = [self soClassSecurityInfo];
98 // [securityInfo declareRole: SOGoRole_Delegate
99 // asDefaultForPermission: SoPerm_AddDocumentsImagesAndFiles];
100 // [securityInfo declareRole: SOGoRole_Delegate
101 // asDefaultForPermission: SoPerm_ChangeImagesAndFiles];
102 // [securityInfo declareRoles: [NSArray arrayWithObjects:
103 // SOGoRole_Delegate,
104 // SOGoRole_Assistant, nil]
105 // asDefaultForPermission: SoPerm_View];
107 sharedYes = [[NSNumber numberWithBool: YES] retain];
110 - (id) initWithName: (NSString *) name
111 inContainer: (id) newContainer
113 if ((self = [super initWithName: name inContainer: newContainer]))
115 timeZone = [[context activeUser] timeZone];
116 aclMatrix = [NSMutableDictionary new];
127 [stripFields release];
128 [uidToFilename release];
141 - (NSArray *) calendarUIDs
143 /* this is used for group calendars (this folder just returns itself) */
146 s = [[self container] nameInContainer];
147 // [self logWithFormat:@"CAL UID: %@", s];
148 return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
153 - (BOOL) isValidAppointmentName: (NSString *)_key
155 return ([_key length] != 0);
158 - (void) appendObject: (NSDictionary *) object
159 withBaseURL: (NSString *) baseURL
160 toREPORTResponse: (WOResponse *) r
162 SOGoCalendarComponent *component;
163 Class componentClass;
164 NSString *name, *etagLine, *calString;
166 name = [object objectForKey: @"c_name"];
168 if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
169 componentClass = [SOGoAppointmentObject class];
171 componentClass = [SOGoTaskObject class];
173 component = [componentClass objectWithName: name inContainer: self];
175 [r appendContentString: @" <D:response>\r\n"];
176 [r appendContentString: @" <D:href>"];
177 [r appendContentString: baseURL];
178 if (![baseURL hasSuffix: @"/"])
179 [r appendContentString: @"/"];
180 [r appendContentString: name];
181 [r appendContentString: @"</D:href>\r\n"];
183 [r appendContentString: @" <D:propstat>\r\n"];
184 [r appendContentString: @" <D:prop>\r\n"];
185 etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
186 [component davEntityTag]];
187 [r appendContentString: etagLine];
188 [r appendContentString: @" </D:prop>\r\n"];
189 [r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
190 [r appendContentString: @" </D:propstat>\r\n"];
191 [r appendContentString: @" <C:calendar-data>"];
192 calString = [[component contentAsString] stringByEscapingXMLString];
193 [r appendContentString: calString];
194 [r appendContentString: @"</C:calendar-data>\r\n"];
195 [r appendContentString: @" </D:response>\r\n"];
198 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
199 toFilter: (NSMutableDictionary *) filter
201 NSCalendarDate *parsedDate;
203 parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
204 [filter setObject: parsedDate forKey: @"start"];
205 parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
206 [filter setObject: parsedDate forKey: @"end"];
209 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
211 NSMutableDictionary *filterData;
212 id <DOMNode> parentNode;
213 id <DOMNodeList> ranges;
214 NSString *componentName;
216 parentNode = [filterElement parentNode];
217 if ([[parentNode tagName] isEqualToString: @"comp-filter"]
218 && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
220 componentName = [[filterElement attribute: @"name"] lowercaseString];
221 filterData = [NSMutableDictionary new];
222 [filterData autorelease];
223 [filterData setObject: componentName forKey: @"name"];
224 ranges = [filterElement getElementsByTagName: @"time-range"];
226 [self _appendTimeRange: [ranges objectAtIndex: 0]
227 toFilter: filterData];
235 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
237 NSEnumerator *children;
239 NSMutableArray *filters;
240 NSDictionary *filter;
242 filters = [NSMutableArray array];
243 children = [[parentNode getElementsByTagName: @"comp-filter"]
245 node = [children nextObject];
248 filter = [self _parseCalendarFilter: node];
250 [filters addObject: filter];
251 node = [children nextObject];
257 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
258 toResponse: (WOResponse *) response
261 unsigned int count, max;
262 NSDictionary *currentFilter, *appointment;
263 NSEnumerator *appointments;
266 baseURL = [self baseURLInContext: context];
268 max = [filters count];
269 for (count = 0; count < max; count++)
271 currentFilter = [filters objectAtIndex: count];
272 apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
273 to: [currentFilter objectForKey: @"end"]
274 title: [currentFilter objectForKey: @"title"]
275 component: [currentFilter objectForKey: @"name"]];
276 appointments = [apts objectEnumerator];
277 appointment = [appointments nextObject];
280 [self appendObject: appointment
282 toREPORTResponse: response];
283 appointment = [appointments nextObject];
288 - (NSArray *) davNamespaces
290 return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
293 - (id) davCalendarQuery: (id) queryContext
297 id <DOMDocument> document;
299 r = [context response];
301 [r setContentEncoding: NSUTF8StringEncoding];
302 [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"];
303 [r setHeader: @"no-cache" forKey: @"pragma"];
304 [r setHeader: @"no-cache" forKey: @"cache-control"];
305 [r appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
306 [r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
307 @" xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\r\n"];
309 document = [[context request] contentAsDOMDocument];
310 filters = [self _parseCalendarFilters: [document documentElement]];
311 [self _appendComponentsMatchingFilters: filters
313 [r appendContentString:@"</D:multistatus>\r\n"];
318 - (Class) objectClassForContent: (NSString *) content
320 iCalCalendar *calendar;
327 calendar = [iCalCalendar parseSingleFromSource: content];
330 elements = [calendar allObjects];
331 if ([elements count])
333 firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString];
334 if ([firstTag isEqualToString: @"VEVENT"])
335 objectClass = [SOGoAppointmentObject class];
336 else if ([firstTag isEqualToString: @"VTODO"])
337 objectClass = [SOGoTaskObject class];
344 - (id) deduceObjectForName: (NSString *)_key
352 request = [_ctx request];
353 method = [request method];
354 if ([method isEqualToString: @"PUT"])
355 objectClass = [self objectClassForContent: [request contentAsString]];
357 objectClass = [self objectClassForResourceNamed: _key];
360 obj = [objectClass objectWithName: _key inContainer: self];
367 - (BOOL) requestNamedIsHandledLater: (NSString *) name
369 return [name isEqualToString: @"OPTIONS"];
372 - (id) lookupName: (NSString *)_key
380 /* first check attributes directly bound to the application */
381 handledLater = [self requestNamedIsHandledLater: _key];
386 obj = [super lookupName:_key inContext:_ctx acquire:NO];
389 if ([self isValidAppointmentName:_key])
391 url = [[[_ctx request] uri] urlWithoutParameters];
392 if ([url hasSuffix: @"AsTask"])
393 obj = [SOGoTaskObject objectWithName: _key
395 else if ([url hasSuffix: @"AsAppointment"])
396 obj = [SOGoAppointmentObject objectWithName: _key
399 obj = [self deduceObjectForName: _key
404 obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
408 [[SOGoCache sharedCache] registerObject: obj
410 inContainer: container];
415 - (NSArray *) davComplianceClassesInContext: (id)_ctx
417 NSMutableArray *classes;
418 NSArray *primaryClasses;
420 classes = [NSMutableArray new];
421 [classes autorelease];
423 primaryClasses = [super davComplianceClassesInContext: _ctx];
425 [classes addObjectsFromArray: primaryClasses];
426 [classes addObject: @"access-control"];
427 [classes addObject: @"calendar-access"];
432 - (NSArray *) groupDavResourceType
434 return [NSArray arrayWithObjects: @"vevent-collection",
435 @"vtodo-collection", nil];
438 - (NSArray *) davResourceType
440 static NSArray *colType = nil;
441 NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
445 gdRT = [self groupDavResourceType];
446 gdVEventCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 0],
447 XMLNS_GROUPDAV, nil];
448 gdVTodoCol = [NSArray arrayWithObjects: [gdRT objectAtIndex: 1],
449 XMLNS_GROUPDAV, nil];
450 cdCol = [NSArray arrayWithObjects: @"calendar", XMLNS_CALDAV, nil];
451 colType = [NSArray arrayWithObjects: @"collection", cdCol,
452 gdVEventCol, gdVTodoCol, nil];
459 /* vevent UID handling */
461 - (NSString *) resourceNameForEventUID: (NSString *)_u
462 inFolder: (GCSFolder *)_f
464 static NSArray *nameFields = nil;
465 EOQualifier *qualifier;
468 if (![_u isNotNull]) return nil;
470 [self errorWithFormat:@"(%s): missing folder for fetch!",
471 __PRETTY_FUNCTION__];
475 if (nameFields == nil)
476 nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
478 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
479 records = [_f fetchFields: nameFields matchingQualifier: qualifier];
481 if ([records count] == 1)
482 return [[records objectAtIndex:0] valueForKey:@"c_name"];
483 if ([records count] == 0)
486 [self errorWithFormat:
487 @"The storage contains more than file with the same UID!"];
488 return [[records objectAtIndex:0] valueForKey:@"c_name"];
491 - (NSString *) resourceNameForEventUID: (NSString *) _uid
497 if (![_uid isNotNull])
499 if ((rname = [uidToFilename objectForKey:_uid]) != nil)
500 return [rname isNotNull] ? rname : nil;
502 if ((folder = [self ocsFolder]) == nil) {
503 [self errorWithFormat:@"(%s): missing folder for fetch!",
504 __PRETTY_FUNCTION__];
508 if (uidToFilename == nil)
509 uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
511 if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
512 [uidToFilename setObject:[NSNull null] forKey:_uid];
514 [uidToFilename setObject:rname forKey:_uid];
519 - (Class) objectClassForResourceNamed: (NSString *) name
521 EOQualifier *qualifier;
526 qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
527 records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
528 matchingQualifier: qualifier];
532 component = [[records objectAtIndex:0] valueForKey: @"c_component"];
533 if ([component isEqualToString: @"vevent"])
534 objectClass = [SOGoAppointmentObject class];
535 else if ([component isEqualToString: @"vtodo"])
536 objectClass = [SOGoTaskObject class];
548 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
549 fetchRange: (NGCalendarDateRange *) _r
551 NSMutableDictionary *md;
554 md = [[_record mutableCopy] autorelease];
556 if ((tmp = [_record objectForKey:@"c_startdate"])) {
557 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
558 (NSTimeInterval)[tmp unsignedIntValue]];
559 [tmp setTimeZone: timeZone];
560 if (tmp) [md setObject:tmp forKey:@"startDate"];
564 [self logWithFormat:@"missing 'startdate' in record?"];
566 if ((tmp = [_record objectForKey:@"c_enddate"])) {
567 tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
568 (NSTimeInterval)[tmp unsignedIntValue]];
569 [tmp setTimeZone: timeZone];
570 if (tmp) [md setObject:tmp forKey:@"endDate"];
574 [self logWithFormat:@"missing 'enddate' in record?"];
579 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
580 cycleRange: (NGCalendarDateRange *) _r
582 NSMutableDictionary *md;
585 md = [[_record mutableCopy] autorelease];
587 /* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
588 the reccurence since we use those when displaying events in SOGo Web */
589 tmp = [_r startDate];
590 [tmp setTimeZone: timeZone];
591 [md setObject:tmp forKey:@"startDate"];
592 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"];
594 [tmp setTimeZone: timeZone];
595 [md setObject:tmp forKey:@"endDate"];
596 [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"];
601 - (NSArray *) fixupRecords: (NSArray *) records
602 fetchRange: (NGCalendarDateRange *) r
604 // TODO: is the result supposed to be sorted by date?
607 id row; // TODO: what is the type of the record?
611 max = [records count];
612 ma = [NSMutableArray arrayWithCapacity: max];
613 for (count = 0; count < max; count++)
615 row = [self fixupRecord: [records objectAtIndex: count]
627 - (void) _flattenCycleRecord: (NSDictionary *) _row
628 forRange: (NGCalendarDateRange *) _r
629 intoArray: (NSMutableArray *) _ma
631 NSMutableDictionary *row;
632 NSDictionary *cycleinfo;
633 NSCalendarDate *startDate, *endDate;
634 NGCalendarDateRange *fir;
635 NSArray *rules, *exRules, *exDates, *ranges;
638 cycleinfo = [[_row objectForKey:@"c_cycleinfo"] propertyList];
639 if (cycleinfo == nil) {
640 [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
644 row = [self fixupRecord:_row fetchRange: _r];
645 [row removeObjectForKey: @"c_cycleinfo"];
646 [row setObject: sharedYes forKey:@"isRecurrentEvent"];
648 startDate = [row objectForKey:@"startDate"];
649 endDate = [row objectForKey:@"endDate"];
650 fir = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
652 rules = [cycleinfo objectForKey:@"rules"];
653 exRules = [cycleinfo objectForKey:@"exRules"];
654 exDates = [cycleinfo objectForKey:@"exDates"];
656 ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
657 firstInstanceCalendarDateRange:fir
658 recurrenceRules:rules
659 exceptionRules:exRules
660 exceptionDates:exDates];
661 count = [ranges count];
663 for (i = 0; i < count; i++) {
664 NGCalendarDateRange *rRange;
667 rRange = [ranges objectAtIndex:i];
668 fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
671 [_ma addObject:fixedRow];
676 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
677 fetchRange: (NGCalendarDateRange *) _r
679 // TODO: is the result supposed to be sorted by date?
683 if (_records == nil) return nil;
684 if ((count = [_records count]) == 0)
687 ma = [NSMutableArray arrayWithCapacity:count];
688 for (i = 0; i < count; i++) {
689 id row; // TODO: what is the type of the record?
691 row = [_records objectAtIndex:i];
692 [self _flattenCycleRecord:row forRange:_r intoArray:ma];
697 - (NSString *) _sqlStringForComponent: (id) _component
704 if ([_component isKindOfClass: [NSArray class]])
705 components = _component;
707 components = [NSArray arrayWithObject: _component];
710 = [NSString stringWithFormat: @" AND (c_component = '%@')",
711 [components componentsJoinedByString: @"' OR c_component = '"]];
719 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
720 to: (NSCalendarDate *) _endDate
722 unsigned int start, end;
724 start = (unsigned int) [_startDate timeIntervalSince1970];
725 end = (unsigned int) [_endDate timeIntervalSince1970];
727 return [NSString stringWithFormat:
728 @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
732 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
734 NSMutableString *classificationString;
735 NSString *currentRole;
736 unsigned int counter;
737 iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
738 iCalAccessConfidential};
740 classificationString = [NSMutableString string];
741 for (counter = 0; counter < 3; counter++)
743 currentRole = [self roleForComponentsWithAccessClass: classes[counter]
745 if ([currentRole length] > 0)
746 [classificationString appendFormat: @"c_classification = %d or ",
750 return classificationString;
753 - (NSString *) _privacySqlString
755 NSString *privacySqlString, *login, *email;
756 SOGoUser *activeUser;
758 activeUser = [context activeUser];
759 login = [activeUser login];
761 if ([login isEqualToString: owner])
762 privacySqlString = @"";
763 else if ([login isEqualToString: @"freebusy"])
764 privacySqlString = @"and (c_isopaque = 1)";
767 #warning we do not manage all the possible user emails
768 email = [[activeUser primaryIdentity] objectForKey: @"email"];
771 = [NSString stringWithFormat:
772 @"(%@(c_orgmail = '%@')"
773 @" or ((c_partmails caseInsensitiveLike '%@%%'"
774 @" or c_partmails caseInsensitiveLike '%%\n%@%%')))",
775 [self _privacyClassificationStringsForUID: login],
776 email, email, email];
779 return privacySqlString;
782 - (NSArray *) subscriptionRoles
784 return [NSArray arrayWithObjects:
785 SOGoRole_ObjectCreator, SOGoRole_ObjectEraser,
786 SOGoCalendarRole_PublicResponder,
787 SOGoCalendarRole_PublicModifier,
788 SOGoCalendarRole_PublicViewer,
789 SOGoCalendarRole_PublicDAndTViewer,
790 SOGoCalendarRole_PrivateResponder,
791 SOGoCalendarRole_PrivateModifier,
792 SOGoCalendarRole_PrivateViewer,
793 SOGoCalendarRole_PrivateDAndTViewer,
794 SOGoCalendarRole_ConfidentialResponder,
795 SOGoCalendarRole_ConfidentialModifier,
796 SOGoCalendarRole_ConfidentialViewer,
797 SOGoCalendarRole_ConfidentialDAndTViewer, nil];
800 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
801 forUser: (NSString *) uid
803 NSString *accessRole, *prefix, *currentRole, *suffix;
805 NSMutableDictionary *userRoles;
809 if (accessClass == iCalAccessPublic)
811 else if (accessClass == iCalAccessPrivate)
814 prefix = @"Confidential";
816 userRoles = [aclMatrix objectForKey: uid];
819 userRoles = [NSMutableDictionary dictionaryWithCapacity: 3];
820 [aclMatrix setObject: userRoles forKey: uid];
823 accessRole = [userRoles objectForKey: prefix];
826 acls = [[self aclsForUser: uid] objectEnumerator];
827 currentRole = [acls nextObject];
828 while (currentRole && !accessRole)
829 if ([currentRole hasPrefix: prefix])
831 suffix = [currentRole substringFromIndex: [prefix length]];
832 accessRole = [NSString stringWithFormat: @"Component%@", suffix];
835 currentRole = [acls nextObject];
838 [userRoles setObject: accessRole forKey: prefix];
844 - (void) _buildStripFieldsFromFields: (NSArray *) fields
846 stripFields = [[NSMutableArray alloc] initWithCapacity: [fields count]];
847 [stripFields setArray: fields];
848 [stripFields removeObjectsInArray: [NSArray arrayWithObjects: @"c_name",
849 @"c_uid", @"c_startdate",
850 @"c_enddate", @"c_isallday",
853 @"c_component", nil]];
856 - (void) _fixupProtectedInformation: (NSEnumerator *) ma
857 inFields: (NSArray *) fields
858 forUser: (NSString *) uid
860 NSMutableDictionary *currentRecord;
861 NSString *roles[] = {nil, nil, nil};
862 iCalAccessClass accessClass;
863 NSString *fullRole, *role;
866 [self _buildStripFieldsFromFields: fields];
868 #warning we do not take the participation status into account
869 while ((currentRecord = [ma nextObject]))
872 = [[currentRecord objectForKey: @"c_classification"] intValue];
873 role = roles[accessClass];
876 fullRole = [self roleForComponentsWithAccessClass: accessClass
878 if ([fullRole length] > 9)
879 role = [fullRole substringFromIndex: 9];
880 roles[accessClass] = role;
882 if ([role isEqualToString: @"DAndTViewer"])
883 [currentRecord removeObjectsForKeys: stripFields];
887 - (NSArray *) fetchFields: (NSArray *) _fields
888 fromFolder: (GCSFolder *) _folder
889 from: (NSCalendarDate *) _startDate
890 to: (NSCalendarDate *) _endDate
891 title: (NSString *) title
892 component: (id) _component
894 EOQualifier *qualifier;
895 NSMutableArray *fields, *ma = nil;
897 NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
898 *privacySqlString, *currentLogin;
899 NGCalendarDateRange *r;
903 [self errorWithFormat:@"(%s): missing folder for fetch!",
904 __PRETTY_FUNCTION__];
908 if (_startDate && _endDate)
910 r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
912 dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
921 titleSqlString = [NSString stringWithFormat: @"AND (c_title"
922 @" isCaseInsensitiveLike: '%%%@%%')", title];
924 titleSqlString = @"";
926 componentSqlString = [self _sqlStringForComponent: _component];
927 privacySqlString = [self _privacySqlString];
929 /* prepare mandatory fields */
931 fields = [NSMutableArray arrayWithArray: _fields];
932 [fields addObject: @"c_uid"];
933 [fields addObject: @"c_startdate"];
934 [fields addObject: @"c_enddate"];
937 [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
939 sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@%@",
940 dateSqlString, titleSqlString,
941 componentSqlString, privacySqlString];
943 /* fetch non-recurrent apts first */
944 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
946 records = [_folder fetchFields: fields matchingQualifier: qualifier];
950 records = [self fixupRecords: records fetchRange: r];
952 [self debugWithFormat: @"fetched %i records: %@",
953 [records count], records];
954 ma = [NSMutableArray arrayWithArray: records];
957 /* fetch recurrent apts now. we do NOT consider the date range when doing that
958 as the c_startdate/c_enddate of a recurring event is always set to the first
959 recurrence - others are generated on the fly */
960 sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@", titleSqlString,
961 componentSqlString, privacySqlString];
963 qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
965 records = [_folder fetchFields: fields matchingQualifier: qualifier];
970 records = [self fixupCyclicRecords: records fetchRange: r];
973 ma = [NSMutableArray arrayWithCapacity: [records count]];
975 [ma addObjectsFromArray: records];
979 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
984 [self debugWithFormat:@"returning %i records", [ma count]];
986 currentLogin = [[context activeUser] login];
987 if (![currentLogin isEqualToString: owner])
988 [self _fixupProtectedInformation: [ma objectEnumerator]
990 forUser: currentLogin];
991 // [ma makeObjectsPerform: @selector (setObject:forKey:)
993 // withObject: @"owner"];
998 /* override this in subclasses */
999 - (NSArray *) fetchFields: (NSArray *) _fields
1000 from: (NSCalendarDate *) _startDate
1001 to: (NSCalendarDate *) _endDate
1002 title: (NSString *) title
1003 component: (id) _component
1007 if ((folder = [self ocsFolder]) == nil) {
1008 [self errorWithFormat:@"(%s): missing folder for fetch!",
1009 __PRETTY_FUNCTION__];
1013 return [self fetchFields: _fields fromFolder: folder
1014 from: _startDate to: _endDate
1016 component: _component];
1019 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
1020 to: (NSCalendarDate *) _endDate
1022 static NSArray *infos = nil; // TODO: move to a plist file
1025 infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
1026 @"c_isopaque", @"c_status", nil];
1028 return [self fetchFields: infos from: _startDate to: _endDate
1030 component: @"vevent"];
1033 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
1034 to: (NSCalendarDate *) _endDate
1035 title: (NSString *) title
1036 component: (id) _component
1038 static NSArray *infos = nil; // TODO: move to a plist file
1041 infos = [[NSArray alloc] initWithObjects:
1042 @"c_name", @"c_component",
1043 @"c_title", @"c_location", @"c_orgmail",
1044 @"c_status", @"c_classification",
1045 @"c_isallday", @"c_isopaque",
1046 @"c_participants", @"c_partmails",
1047 @"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo",
1050 return [self fetchFields: infos from: _startDate to: _endDate title: title
1051 component: _component];
1054 /* URL generation */
1056 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
1059 // TODO: who calls this?
1062 if ([_uid length] == 0)
1065 url = [self baseURLInContext:_ctx];
1066 if (![url hasSuffix: @"/"])
1067 url = [url stringByAppendingString: @"/"];
1069 // TODO: this should run a query to determine the uid!
1070 return [url stringByAppendingString:_uid];
1073 /* folder management */
1077 NSMutableArray *folderSubscription;
1078 NSUserDefaults *userSettings;
1079 NSMutableDictionary *calendarSettings;
1080 SOGoUser *ownerUser;
1082 rc = [super create];
1085 ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1087 userSettings = [ownerUser userSettings];
1088 calendarSettings = [userSettings objectForKey: @"Calendar"];
1089 if (!calendarSettings)
1091 calendarSettings = [NSMutableDictionary dictionary];
1092 [userSettings setObject: calendarSettings forKey: @"Calendar"];
1095 = [calendarSettings objectForKey: @"ActiveFolders"];
1096 if (!folderSubscription)
1098 folderSubscription = [NSMutableArray array];
1099 [calendarSettings setObject: folderSubscription
1100 forKey: @"ActiveFolders"];
1102 [folderSubscription addObjectUniquely: nameInContainer];
1103 [userSettings synchronize];
1109 - (id) lookupHomeFolderForUID: (NSString *) _uid
1112 // TODO: DUP to SOGoGroupFolder
1113 NSException *error = nil;
1117 if (![_uid isNotNull])
1120 /* create subcontext, so that we don't destroy our environment */
1122 if ((ctx = [context createSubContext]) == nil) {
1123 [self errorWithFormat:@"could not create SOPE subcontext!"];
1129 path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1133 result = [[ctx application] traversePathArray:path inContext:ctx
1134 error:&error acquire:NO];
1136 [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1141 [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1142 _uid, [path componentsJoinedByString:@"=>"], result];
1146 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1148 SOGoFolder *currentContainer;
1149 SOGoAppointmentFolders *parent;
1152 currentContainer = [[container container] container];
1153 currentContainer = [currentContainer lookupName: uid
1156 parent = [currentContainer lookupName: @"Calendar" inContext: context
1158 currentContainer = [parent lookupName: @"personal" inContext: context
1160 if (!currentContainer)
1162 error = [parent newFolderWithName: [parent defaultFolderName]
1163 andNameInContainer: @"personal"];
1165 currentContainer = [parent lookupName: @"personal"
1170 return (SOGoAppointmentFolder *) currentContainer;
1173 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1176 /* Note: can return NSNull objects in the array! */
1177 NSMutableArray *folders;
1179 NSString *uid, *ownerLogin;
1182 ownerLogin = [self ownerInContext: context];
1184 if ([_uids count] == 0) return nil;
1185 folders = [NSMutableArray arrayWithCapacity:16];
1186 e = [_uids objectEnumerator];
1187 while ((uid = [e nextObject]))
1189 if ([uid isEqualToString: ownerLogin])
1193 folder = [self lookupCalendarFolderForUID: uid];
1194 if (![folder isNotNull])
1195 [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1199 [folders addObject: folder];
1205 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1206 inContext: (id) _ctx
1208 /* Note: can return NSNull objects in the array! */
1209 NSMutableArray *objs;
1213 if ([_uids count] == 0) return nil;
1214 objs = [NSMutableArray arrayWithCapacity:16];
1215 e = [_uids objectEnumerator];
1216 while ((uid = [e nextObject])) {
1219 obj = [self lookupHomeFolderForUID:uid inContext:nil];
1220 if ([obj isNotNull]) {
1221 obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
1222 if ([obj isKindOfClass:[NSException class]])
1225 if (![obj isNotNull])
1226 [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1228 /* Note: intentionally add 'null' folders to allow a mapping */
1229 [objs addObject:obj ? obj : [NSNull null]];
1234 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1236 /* Note: can return NSNull objects in the array! */
1237 NSMutableArray *uids;
1238 LDAPUserManager *um;
1241 if (_persons == nil)
1244 count = [_persons count];
1245 uids = [NSMutableArray arrayWithCapacity:count + 1];
1246 um = [LDAPUserManager sharedUserManager];
1248 for (i = 0; i < count; i++) {
1253 person = [_persons objectAtIndex:i];
1254 email = [person rfc822Email];
1255 if ([email isNotNull]) {
1256 uid = [um getUIDForEmail:email];
1261 [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1266 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1267 inContext: (id) _ctx
1269 /* Note: can return NSNull objects in the array! */
1272 if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1275 return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1278 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1281 SOGoCustomGroupFolder *folder;
1286 folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1287 return [folder autorelease];
1290 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1291 inContext: (id) _ctx
1293 SOGoCustomGroupFolder *folder;
1295 if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1298 folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1299 if (![folder isNotNull])
1301 if ([folder isKindOfClass:[NSException class]]) {
1302 [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1312 - (NSArray *) fetchAllSOGoAppointments
1315 Note: very expensive method, do not use unless absolutely required.
1316 returns an array of SOGoAppointment objects.
1318 Note that we can leave out the filenames, supposed to be stored
1319 in the 'uid' field of the iCalendar object!
1321 NSMutableArray *events;
1322 NSDictionary *files;
1323 NSEnumerator *contents;
1326 /* fetch all raw contents */
1328 files = [self fetchContentStringsAndNamesOfAllObjects];
1329 if (![files isNotNull]) return nil;
1330 if ([files isKindOfClass:[NSException class]]) return (id)files;
1332 /* transform to SOGo appointments */
1334 events = [NSMutableArray arrayWithCapacity:[files count]];
1335 contents = [files objectEnumerator];
1336 while ((content = [contents nextObject]) != nil)
1337 [events addObject: [iCalCalendar parseSingleFromSource: content]];
1342 // #warning We only support ONE calendar per user at this time
1343 // - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1344 // toFolderList: (NSMutableArray *) calendarFolders
1346 // NSEnumerator *keys;
1347 // NSString *currentKey;
1348 // NSMutableDictionary *currentCalendar;
1349 // BOOL firstShouldBeActive;
1350 // unsigned int count;
1352 // firstShouldBeActive = YES;
1354 // keys = [[subscribedFolders allKeys] objectEnumerator];
1355 // currentKey = [keys nextObject];
1357 // while (currentKey)
1359 // currentCalendar = [NSMutableDictionary new];
1360 // [currentCalendar autorelease];
1362 // setDictionary: [subscribedFolders objectForKey: currentKey]];
1363 // [currentCalendar setObject: currentKey forKey: @"folder"];
1364 // [calendarFolders addObject: currentCalendar];
1365 // if ([[currentCalendar objectForKey: @"active"] boolValue])
1366 // firstShouldBeActive = NO;
1368 // currentKey = [keys nextObject];
1371 // return firstShouldBeActive;
1374 // - (NSArray *) calendarFolders
1376 // NSMutableDictionary *userCalendar, *calendarDict;
1377 // NSMutableArray *calendarFolders;
1378 // SOGoUser *calendarUser;
1379 // BOOL firstActive;
1381 // calendarFolders = [NSMutableArray new];
1382 // [calendarFolders autorelease];
1384 // calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1386 // userCalendar = [NSMutableDictionary new];
1387 // [userCalendar autorelease];
1388 // [userCalendar setObject: @"/" forKey: @"folder"];
1389 // [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1390 // [calendarFolders addObject: userCalendar];
1392 // calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1393 // firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1394 // firstActive = ([self _appendSubscribedFolders:
1395 // [calendarDict objectForKey: @"SubscribedFolders"]
1396 // toFolderList: calendarFolders]
1398 // [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1399 // forKey: @"active"];
1401 // return calendarFolders;
1404 // - (NSArray *) fetchContentObjectNames
1406 // NSMutableArray *objectNames;
1407 // NSArray *records;
1408 // NSCalendarDate *today, *startDate, *endDate;
1410 // #warning this should be user-configurable
1411 // objectNames = [NSMutableArray array];
1412 // today = [[NSCalendarDate calendarDate] beginOfDay];
1413 // [today setTimeZone: timeZone];
1415 // startDate = [today dateByAddingYears: 0 months: 0 days: -1
1416 // hours: 0 minutes: 0 seconds: 0];
1417 // endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
1418 // hours: 0 minutes: 0 seconds: 0];
1419 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1420 // from: startDate to: endDate
1421 // component: @"vevent"];
1422 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1423 // records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
1424 // from: startDate to: endDate
1425 // component: @"vtodo"];
1426 // [objectNames addObjectsFromArray: [records valueForKey: @"c_name"]];
1428 // return objectNames;
1431 - (NSArray *) fetchContentObjectNames
1433 static NSArray *cNameField = nil;
1436 cNameField = [[NSArray alloc] initWithObjects: @"c_name", nil];
1438 return [[self fetchFields: cNameField from: nil to: nil
1439 title: nil component: nil] objectsForKey: @"c_name"];
1444 - (NSString *) folderType
1446 return @"Appointment";
1449 - (NSString *) outlookFolderClass
1451 return @"IPF.Appointment";
1456 NSUserDefaults *settings;
1457 NSArray *activeFolders;
1459 settings = [[context activeUser] userSettings];
1461 = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
1463 return [activeFolders containsObject: nameInContainer];
1466 @end /* SOGoAppointmentFolder */