]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoAppointmentFolder.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1292 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Appointments / SOGoAppointmentFolder.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #import <Foundation/NSCalendarDate.h>
23 #import <Foundation/NSEnumerator.h>
24 #import <Foundation/NSUserDefaults.h>
25 #import <Foundation/NSValue.h>
26
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>
49
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>
57
58 #import "SOGoAppointmentObject.h"
59 #import "SOGoAppointmentFolders.h"
60 #import "SOGoTaskObject.h"
61
62 #import "SOGoAppointmentFolder.h"
63
64 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
65 @interface NSDate(UsedPrivates)
66 - (id)initWithTimeIntervalSince1970:(NSTimeInterval)_interval;
67 @end
68 #endif
69
70 @implementation SOGoAppointmentFolder
71
72 static NGLogger   *logger    = nil;
73 static NSNumber   *sharedYes = nil;
74
75 + (int) version
76 {
77   return [super version] + 1 /* v1 */;
78 }
79
80 + (void) initialize
81 {
82   NGLoggerManager *lm;
83   static BOOL     didInit = NO;
84 //   SoClassSecurityInfo *securityInfo;
85
86   if (didInit) return;
87   didInit = YES;
88   
89   NSAssert2([super version] == 0,
90             @"invalid superclass (%@) version %i !",
91             NSStringFromClass([self superclass]), [super version]);
92
93   lm      = [NGLoggerManager defaultLoggerManager];
94   logger  = [lm loggerForDefaultKey: @"SOGoAppointmentFolderDebugEnabled"];
95
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];
105
106   sharedYes = [[NSNumber numberWithBool: YES] retain];
107 }
108
109 - (id) initWithName: (NSString *) name
110         inContainer: (id) newContainer
111 {
112   if ((self = [super initWithName: name inContainer: newContainer]))
113     {
114       timeZone = [[context activeUser] timeZone];
115       aclMatrix = [NSMutableDictionary new];
116       stripFields = nil;
117       uidToFilename = nil;
118     }
119
120   return self;
121 }
122
123 - (void) dealloc
124 {
125   [aclMatrix release];
126   [stripFields release];
127   [uidToFilename release];
128   [super dealloc];
129 }
130
131 /* logging */
132
133 - (id) debugLogger
134 {
135   return logger;
136 }
137
138 /* selection */
139
140 - (NSArray *) calendarUIDs 
141 {
142   /* this is used for group calendars (this folder just returns itself) */
143   NSString *s;
144   
145   s = [[self container] nameInContainer];
146 //   [self logWithFormat:@"CAL UID: %@", s];
147   return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil;
148 }
149
150 /* name lookup */
151
152 - (BOOL) isValidAppointmentName: (NSString *)_key
153 {
154   return ([_key length] != 0);
155 }
156
157 - (void) appendObject: (NSDictionary *) object
158           withBaseURL: (NSString *) baseURL
159      toREPORTResponse: (WOResponse *) r
160 {
161   SOGoCalendarComponent *component;
162   Class componentClass;
163   NSString *name, *etagLine, *calString;
164
165   name = [object objectForKey: @"c_name"];
166
167   if ([[object objectForKey: @"c_component"] isEqualToString: @"vevent"])
168     componentClass = [SOGoAppointmentObject class];
169   else
170     componentClass = [SOGoTaskObject class];
171
172   component = [componentClass objectWithName: name inContainer: self];
173
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"];
181
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"];
195 }
196
197 - (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
198                  toFilter: (NSMutableDictionary *) filter
199 {
200   NSCalendarDate *parsedDate;
201
202   parsedDate = [[timeRangeElement attribute: @"start"] asCalendarDate];
203   [filter setObject: parsedDate forKey: @"start"];
204   parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
205   [filter setObject: parsedDate forKey: @"end"];
206 }
207
208 - (NSDictionary *) _parseCalendarFilter: (id <DOMElement>) filterElement
209 {
210   NSMutableDictionary *filterData;
211   id <DOMNode> parentNode;
212   id <DOMNodeList> ranges;
213   NSString *componentName;
214
215   parentNode = [filterElement parentNode];
216   if ([[parentNode tagName] isEqualToString: @"comp-filter"]
217       && [[parentNode attribute: @"name"] isEqualToString: @"VCALENDAR"])
218     {
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"];
224       if ([ranges count])
225         [self _appendTimeRange: [ranges objectAtIndex: 0]
226               toFilter: filterData];
227     }
228   else
229     filterData = nil;
230
231   return filterData;
232 }
233
234 - (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
235 {
236   NSEnumerator *children;
237   id<DOMElement> node;
238   NSMutableArray *filters;
239   NSDictionary *filter;
240
241   filters = [NSMutableArray array];
242   children = [[parentNode getElementsByTagName: @"comp-filter"]
243                objectEnumerator];
244   node = [children nextObject];
245   while (node)
246     {
247       filter = [self _parseCalendarFilter: node];
248       if (filter)
249         [filters addObject: filter];
250       node = [children nextObject];
251     }
252
253   return filters;
254 }
255
256 - (void) _appendComponentsMatchingFilters: (NSArray *) filters
257                                toResponse: (WOResponse *) response
258 {
259   NSArray *apts;
260   unsigned int count, max;
261   NSDictionary *currentFilter, *appointment;
262   NSEnumerator *appointments;
263   NSString *baseURL;
264
265   baseURL = [self baseURLInContext: context];
266
267   max = [filters count];
268   for (count = 0; count < max; count++)
269     {
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];
277       while (appointment)
278         {
279           [self appendObject: appointment
280                 withBaseURL: baseURL
281                 toREPORTResponse: response];
282           appointment = [appointments nextObject];
283         }
284     }
285 }
286
287 - (NSArray *) davNamespaces
288 {
289   return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:caldav"];
290 }
291
292 - (id) davCalendarQuery: (id) queryContext
293 {
294   WOResponse *r;
295   NSArray *filters;
296   id <DOMDocument> document;
297
298   r = [context response];
299   [r setStatus: 207];
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"];
307
308   document = [[context request] contentAsDOMDocument];
309   filters = [self _parseCalendarFilters: [document documentElement]];
310   [self _appendComponentsMatchingFilters: filters
311         toResponse: r];
312   [r appendContentString:@"</D:multistatus>\r\n"];
313
314   return r;
315 }
316
317 - (Class) objectClassForContent: (NSString *) content
318 {
319   iCalCalendar *calendar;
320   NSArray *elements;
321   NSString *firstTag;
322   Class objectClass;
323
324   objectClass = Nil;
325
326   calendar = [iCalCalendar parseSingleFromSource: content];
327   if (calendar)
328     {
329       elements = [calendar allObjects];
330       if ([elements count])
331         {
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];
337         }
338     }
339
340   return objectClass;
341 }
342
343 - (id) deduceObjectForName: (NSString *)_key
344                  inContext: (id)_ctx
345 {
346   WORequest *request;
347   NSString *method;
348   Class objectClass;
349   id obj;
350
351   request = [_ctx request];
352   method = [request method];
353   if ([method isEqualToString: @"PUT"])
354     objectClass = [self objectClassForContent: [request contentAsString]];
355   else
356     objectClass = [self objectClassForResourceNamed: _key];
357
358   if (objectClass)
359     obj = [objectClass objectWithName: _key inContainer: self];
360   else
361     obj = nil;
362
363   return obj;
364 }
365
366 - (BOOL) requestNamedIsHandledLater: (NSString *) name
367 {
368   return [name isEqualToString: @"OPTIONS"];
369 }
370
371 - (id) lookupName: (NSString *)_key
372         inContext: (id)_ctx
373           acquire: (BOOL)_flag
374 {
375   id obj;
376   NSString *url;
377   BOOL handledLater;
378
379   /* first check attributes directly bound to the application */
380   handledLater = [self requestNamedIsHandledLater: _key];
381   if (handledLater)
382     obj = nil;
383   else
384     {
385       obj = [super lookupName:_key inContext:_ctx acquire:NO];
386       if (!obj)
387         {
388           if ([self isValidAppointmentName:_key])
389             {
390               url = [[[_ctx request] uri] urlWithoutParameters];
391               if ([url hasSuffix: @"AsTask"])
392                 obj = [SOGoTaskObject objectWithName: _key
393                                       inContainer: self];
394               else if ([url hasSuffix: @"AsAppointment"])
395                 obj = [SOGoAppointmentObject objectWithName: _key
396                                              inContainer: self];
397               else
398                 obj = [self deduceObjectForName: _key
399                             inContext: _ctx];
400             }
401         }
402       if (!obj)
403         obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */];
404     }
405
406   return obj;
407 }
408
409 - (NSArray *) davComplianceClassesInContext: (id)_ctx
410 {
411   NSMutableArray *classes;
412   NSArray *primaryClasses;
413
414   classes = [NSMutableArray new];
415   [classes autorelease];
416
417   primaryClasses = [super davComplianceClassesInContext: _ctx];
418   if (primaryClasses)
419     [classes addObjectsFromArray: primaryClasses];
420   [classes addObject: @"access-control"];
421   [classes addObject: @"calendar-access"];
422
423   return classes;
424 }
425
426 - (NSArray *) groupDavResourceType
427 {
428   return [NSArray arrayWithObjects: @"vevent-collection",
429                   @"vtodo-collection", nil];
430 }
431
432 - (NSArray *) davResourceType
433 {
434   static NSArray *colType = nil;
435   NSArray *cdCol, *gdRT, *gdVEventCol, *gdVTodoCol;
436
437   if (!colType)
438     {
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];
447       [colType retain];
448     }
449
450   return colType;
451 }
452
453 /* vevent UID handling */
454
455 - (NSString *) resourceNameForEventUID: (NSString *)_u
456                               inFolder: (GCSFolder *)_f
457 {
458   static NSArray *nameFields = nil;
459   EOQualifier *qualifier;
460   NSArray     *records;
461   
462   if (![_u isNotNull]) return nil;
463   if (_f == nil) {
464     [self errorWithFormat:@"(%s): missing folder for fetch!",
465             __PRETTY_FUNCTION__];
466     return nil;
467   }
468   
469   if (nameFields == nil)
470     nameFields = [[NSArray alloc] initWithObjects: @"c_name", nil];
471   
472   qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_uid = %@", _u];
473   records   = [_f fetchFields: nameFields matchingQualifier: qualifier];
474   
475   if ([records count] == 1)
476     return [[records objectAtIndex:0] valueForKey:@"c_name"];
477   if ([records count] == 0)
478     return nil;
479   
480   [self errorWithFormat:
481           @"The storage contains more than file with the same UID!"];
482   return [[records objectAtIndex:0] valueForKey:@"c_name"];
483 }
484
485 - (NSString *) resourceNameForEventUID: (NSString *) _uid
486 {
487   /* caches UIDs */
488   GCSFolder *folder;
489   NSString  *rname;
490   
491   if (![_uid isNotNull])
492     return nil;
493   if ((rname = [uidToFilename objectForKey:_uid]) != nil)
494     return [rname isNotNull] ? rname : nil;
495   
496   if ((folder = [self ocsFolder]) == nil) {
497     [self errorWithFormat:@"(%s): missing folder for fetch!",
498       __PRETTY_FUNCTION__];
499     return nil;
500   }
501
502   if (uidToFilename == nil)
503     uidToFilename = [[NSMutableDictionary alloc] initWithCapacity:16];
504   
505   if ((rname = [self resourceNameForEventUID:_uid inFolder:folder]) == nil)
506     [uidToFilename setObject:[NSNull null] forKey:_uid];
507   else
508     [uidToFilename setObject:rname forKey:_uid];
509   
510   return rname;
511 }
512
513 - (Class) objectClassForResourceNamed: (NSString *) name
514 {
515   EOQualifier *qualifier;
516   NSArray *records;
517   NSString *component;
518   Class objectClass;
519
520   qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
521   records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
522                               matchingQualifier: qualifier];
523
524   if ([records count])
525     {
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];
531       else
532         objectClass = Nil;
533     }
534   else
535     objectClass = Nil;
536   
537   return objectClass;
538 }
539
540 /* fetching */
541
542 - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record
543                            fetchRange: (NGCalendarDateRange *) _r
544 {
545   NSMutableDictionary *md;
546   id tmp;
547   
548   md = [[_record mutableCopy] autorelease];
549  
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"];
555     [tmp release];
556   }
557   else
558     [self logWithFormat:@"missing 'startdate' in record?"];
559
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"];
565     [tmp release];
566   }
567   else
568     [self logWithFormat:@"missing 'enddate' in record?"];
569
570   return md;
571 }
572
573 - (NSMutableDictionary *) fixupCycleRecord: (NSDictionary *) _record
574                                 cycleRange: (NGCalendarDateRange *) _r
575 {
576   NSMutableDictionary *md;
577   id tmp;
578   
579   md = [[_record mutableCopy] autorelease];
580   
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"];
587   tmp = [_r endDate];
588   [tmp setTimeZone: timeZone];
589   [md setObject:tmp forKey:@"endDate"];
590   [md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"];
591   
592   return md;
593 }
594
595 - (NSArray *) fixupRecords: (NSArray *) records
596                 fetchRange: (NGCalendarDateRange *) r
597 {
598   // TODO: is the result supposed to be sorted by date?
599   NSMutableArray *ma;
600   unsigned count, max;
601   id row; // TODO: what is the type of the record?
602
603   if (records)
604     {
605       max = [records count];
606       ma = [NSMutableArray arrayWithCapacity: max];
607       for (count = 0; count < max; count++)
608         {
609           row = [self fixupRecord: [records objectAtIndex: count]
610                       fetchRange: r];
611           if (row)
612             [ma addObject: row];
613         }
614     }
615   else
616     ma = nil;
617
618   return ma;
619 }
620
621 - (void) _flattenCycleRecord: (NSDictionary *) _row
622                     forRange: (NGCalendarDateRange *) _r
623                    intoArray: (NSMutableArray *) _ma
624 {
625   NSMutableDictionary *row;
626   NSDictionary        *cycleinfo;
627   NSCalendarDate      *startDate, *endDate;
628   NGCalendarDateRange *fir;
629   NSArray             *rules, *exRules, *exDates, *ranges;
630   unsigned            i, count;
631
632   cycleinfo  = [[_row objectForKey:@"c_cycleinfo"] propertyList];
633   if (cycleinfo == nil) {
634     [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", _row];
635     return;
636   }
637
638   row = [self fixupRecord:_row fetchRange: _r];
639   [row removeObjectForKey: @"c_cycleinfo"];
640   [row setObject: sharedYes forKey:@"isRecurrentEvent"];
641
642   startDate = [row objectForKey:@"startDate"];
643   endDate   = [row objectForKey:@"endDate"];
644   fir       = [NGCalendarDateRange calendarDateRangeWithStartDate:startDate
645                                    endDate:endDate];
646   rules     = [cycleinfo objectForKey:@"rules"];
647   exRules   = [cycleinfo objectForKey:@"exRules"];
648   exDates   = [cycleinfo objectForKey:@"exDates"];
649   
650   ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
651                                      firstInstanceCalendarDateRange:fir
652                                      recurrenceRules:rules
653                                      exceptionRules:exRules
654                                      exceptionDates:exDates];
655   count = [ranges count];
656
657   for (i = 0; i < count; i++) {
658     NGCalendarDateRange *rRange;
659     id fixedRow;
660     
661     rRange   = [ranges objectAtIndex:i];
662     fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
663     if (fixedRow != nil)
664       {
665         [_ma addObject:fixedRow];
666       }
667   }
668 }
669
670 - (NSArray *) fixupCyclicRecords: (NSArray *) _records
671                       fetchRange: (NGCalendarDateRange *) _r
672 {
673   // TODO: is the result supposed to be sorted by date?
674   NSMutableArray *ma;
675   unsigned i, count;
676   
677   if (_records == nil) return nil;
678   if ((count = [_records count]) == 0)
679     return _records;
680   
681   ma = [NSMutableArray arrayWithCapacity:count];
682   for (i = 0; i < count; i++) {
683     id row; // TODO: what is the type of the record?
684     
685     row = [_records objectAtIndex:i];
686     [self _flattenCycleRecord:row forRange:_r intoArray:ma];
687   }
688   return ma;
689 }
690
691 - (NSString *) _sqlStringForComponent: (id) _component
692 {
693   NSString *sqlString;
694   NSArray *components;
695
696   if (_component)
697     {
698       if ([_component isKindOfClass: [NSArray class]])
699         components = _component;
700       else
701         components = [NSArray arrayWithObject: _component];
702
703       sqlString
704         = [NSString stringWithFormat: @" AND (c_component = '%@')",
705                     [components componentsJoinedByString: @"' OR c_component = '"]];
706     }
707   else
708     sqlString = @"";
709
710   return sqlString;
711 }
712
713 - (NSString *) _sqlStringRangeFrom: (NSCalendarDate *) _startDate
714                                 to: (NSCalendarDate *) _endDate
715 {
716   unsigned int start, end;
717
718   start = (unsigned int) [_startDate timeIntervalSince1970];
719   end = (unsigned int) [_endDate timeIntervalSince1970];
720
721   return [NSString stringWithFormat:
722                      @" AND (c_startdate <= %u) AND (c_enddate >= %u)",
723                    end, start];
724 }
725
726 - (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
727 {
728   NSMutableString *classificationString;
729   NSString *currentRole;
730   unsigned int counter;
731   iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
732                                iCalAccessConfidential};
733
734   classificationString = [NSMutableString string];
735   for (counter = 0; counter < 3; counter++)
736     {
737       currentRole = [self roleForComponentsWithAccessClass: classes[counter]
738                           forUser: uid];
739       if ([currentRole length] > 0)
740         [classificationString appendFormat: @"c_classification = %d or ",
741                               classes[counter]];
742     }
743
744   return classificationString;
745 }
746
747 - (NSString *) _privacySqlString
748 {
749   NSString *privacySqlString, *login, *email;
750   SOGoUser *activeUser;
751
752   activeUser = [context activeUser];
753   login = [activeUser login];
754
755   if ([login isEqualToString: owner])
756     privacySqlString = @"";
757   else if ([login isEqualToString: @"freebusy"])
758     privacySqlString = @"and (c_isopaque = 1)";
759   else
760     {
761 #warning we do not manage all the possible user emails
762       email = [[activeUser primaryIdentity] objectForKey: @"email"];
763       
764       privacySqlString
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];
771     }
772
773   return privacySqlString;
774 }
775
776 - (NSArray *) subscriptionRoles
777 {
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];
792 }
793
794 - (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
795                                         forUser: (NSString *) uid
796 {
797   NSString *accessRole, *prefix, *currentRole, *suffix;
798   NSEnumerator *acls;
799   NSMutableDictionary *userRoles;
800
801   accessRole = nil;
802
803   if (accessClass == iCalAccessPublic)
804     prefix = @"Public";
805   else if (accessClass == iCalAccessPrivate)
806     prefix = @"Private";
807   else
808     prefix = @"Confidential";
809
810   userRoles = [aclMatrix objectForKey: uid];
811   if (!userRoles)
812     {
813       userRoles = [NSMutableDictionary dictionaryWithCapacity: 3];
814       [aclMatrix setObject: userRoles forKey: uid];
815     }
816
817   accessRole = [userRoles objectForKey: prefix];
818   if (!accessRole)
819     {
820       acls = [[self aclsForUser: uid] objectEnumerator];
821       currentRole = [acls nextObject];
822       while (currentRole && !accessRole)
823         if ([currentRole hasPrefix: prefix])
824           {
825             suffix = [currentRole substringFromIndex: [prefix length]];
826             accessRole = [NSString stringWithFormat: @"Component%@", suffix];
827           }
828         else
829           currentRole = [acls nextObject];
830       if (!accessRole)
831         accessRole = @"";
832       [userRoles setObject: accessRole forKey: prefix];
833     }
834
835   return accessRole;
836 }
837
838 - (void) _buildStripFieldsFromFields: (NSArray *) fields
839 {
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",
845                                               @"c_iscycle",
846                                               @"c_classification",
847                                               @"c_component", nil]];
848 }
849
850 - (void) _fixupProtectedInformation: (NSEnumerator *) ma
851                            inFields: (NSArray *) fields
852                             forUser: (NSString *) uid
853 {
854   NSMutableDictionary *currentRecord;
855   NSString *roles[] = {nil, nil, nil};
856   iCalAccessClass accessClass;
857   NSString *fullRole, *role;
858
859   if (!stripFields)
860     [self _buildStripFieldsFromFields: fields];
861
862 #warning we do not take the participation status into account
863   while ((currentRecord = [ma nextObject]))
864     {
865       accessClass
866         = [[currentRecord objectForKey: @"c_classification"] intValue];
867       role = roles[accessClass];
868       if (!role)
869         {
870           fullRole = [self roleForComponentsWithAccessClass: accessClass
871                            forUser: uid];
872           if ([fullRole length] > 9)
873             role = [fullRole substringFromIndex: 9];
874           roles[accessClass] = role;
875         }
876       if ([role isEqualToString: @"DAndTViewer"])
877         [currentRecord removeObjectsForKeys: stripFields];
878     }
879 }
880
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
887 {
888   EOQualifier *qualifier;
889   NSMutableArray *fields, *ma = nil;
890   NSArray *records;
891   NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
892     *privacySqlString, *currentLogin;
893   NGCalendarDateRange *r;
894
895   if (!_folder)
896     {
897       [self errorWithFormat:@"(%s): missing folder for fetch!",
898             __PRETTY_FUNCTION__];
899       return nil;
900     }
901
902   if (_startDate && _endDate)
903     {
904       r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
905                                endDate: _endDate];
906       dateSqlString = [self _sqlStringRangeFrom: _startDate to: _endDate];
907     }
908   else
909     {
910       r = nil;
911       dateSqlString = @"";
912     }
913
914   if ([title length])
915     titleSqlString = [NSString stringWithFormat: @"AND (c_title"
916                                @" isCaseInsensitiveLike: '%%%@%%')", title];
917   else
918     titleSqlString = @"";
919
920   componentSqlString = [self _sqlStringForComponent: _component];
921   privacySqlString = [self _privacySqlString];
922
923   /* prepare mandatory fields */
924
925   fields = [NSMutableArray arrayWithArray: _fields];
926   [fields addObject: @"c_uid"];
927   [fields addObject: @"c_startdate"];
928   [fields addObject: @"c_enddate"];
929
930   if (logger)
931     [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate];
932
933   sql = [NSString stringWithFormat: @"(c_iscycle = 0)%@%@%@%@",
934                   dateSqlString, titleSqlString,
935                   componentSqlString, privacySqlString];
936
937   /* fetch non-recurrent apts first */
938   qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
939
940   records = [_folder fetchFields: fields matchingQualifier: qualifier];
941   if (records)
942     {
943       if (r)
944         records = [self fixupRecords: records fetchRange: r];
945       if (logger)
946         [self debugWithFormat: @"fetched %i records: %@",
947               [records count], records];
948       ma = [NSMutableArray arrayWithArray: records];
949     }
950
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];
956
957   qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
958
959   records = [_folder fetchFields: fields matchingQualifier: qualifier];
960
961   if (records)
962     {
963       if (r) {
964         records = [self fixupCyclicRecords: records fetchRange: r];
965       }
966       if (!ma)
967         ma = [NSMutableArray arrayWithCapacity: [records count]];
968
969       [ma addObjectsFromArray: records];
970     }
971   else if (!ma)
972     {
973       [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
974       return nil;
975     }
976
977   if (logger)
978     [self debugWithFormat:@"returning %i records", [ma count]];
979
980   currentLogin = [[context activeUser] login];
981   if (![currentLogin isEqualToString: owner])
982     [self _fixupProtectedInformation: [ma objectEnumerator]
983           inFields: _fields
984           forUser: currentLogin];
985 //   [ma makeObjectsPerform: @selector (setObject:forKey:)
986 //       withObject: owner
987 //       withObject: @"owner"];
988
989   return ma;
990 }
991
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
998 {
999   GCSFolder *folder;
1000   
1001   if ((folder = [self ocsFolder]) == nil) {
1002     [self errorWithFormat:@"(%s): missing folder for fetch!",
1003       __PRETTY_FUNCTION__];
1004     return nil;
1005   }
1006
1007   return [self fetchFields: _fields fromFolder: folder
1008                from: _startDate to: _endDate
1009                title: title
1010                component: _component];
1011 }
1012
1013 - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
1014                                   to: (NSCalendarDate *) _endDate
1015 {
1016   static NSArray *infos = nil; // TODO: move to a plist file
1017   
1018   if (!infos)
1019     infos = [[NSArray alloc] initWithObjects: @"c_partmails", @"c_partstates",
1020                              @"c_isopaque", @"c_status", nil];
1021
1022   return [self fetchFields: infos from: _startDate to: _endDate
1023                title: nil
1024                component: @"vevent"];
1025 }
1026
1027 - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate
1028                               to: (NSCalendarDate *) _endDate
1029                            title: (NSString *) title
1030                        component: (id) _component
1031 {
1032   static NSArray *infos = nil; // TODO: move to a plist file
1033
1034   if (!infos)
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",
1042                              nil];
1043
1044   return [self fetchFields: infos from: _startDate to: _endDate title: title
1045                component: _component];
1046 }
1047
1048 /* URL generation */
1049
1050 - (NSString *) baseURLForAptWithUID: (NSString *)_uid
1051                           inContext: (id)_ctx
1052 {
1053   // TODO: who calls this?
1054   NSString *url;
1055   
1056   if ([_uid length] == 0)
1057     return nil;
1058   
1059   url = [self baseURLInContext:_ctx];
1060   if (![url hasSuffix: @"/"])
1061     url = [url stringByAppendingString: @"/"];
1062   
1063   // TODO: this should run a query to determine the uid!
1064   return [url stringByAppendingString:_uid];
1065 }
1066
1067 /* folder management */
1068 - (BOOL) create
1069 {
1070   BOOL rc;
1071   NSMutableArray *folderSubscription;
1072   NSUserDefaults *userSettings;
1073   NSMutableDictionary *calendarSettings;
1074   SOGoUser *ownerUser;
1075
1076   rc = [super create];
1077   if (rc)
1078     {
1079       ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1080                             roles: nil];
1081       userSettings = [ownerUser userSettings];
1082       calendarSettings = [userSettings objectForKey: @"Calendar"];
1083       if (!calendarSettings)
1084         {
1085           calendarSettings = [NSMutableDictionary dictionary];
1086           [userSettings setObject: calendarSettings forKey: @"Calendar"];
1087         }
1088       folderSubscription
1089         = [calendarSettings objectForKey: @"ActiveFolders"];
1090       if (!folderSubscription)
1091         {
1092           folderSubscription = [NSMutableArray array];
1093           [calendarSettings setObject: folderSubscription
1094                             forKey: @"ActiveFolders"];
1095         }
1096       [folderSubscription addObjectUniquely: nameInContainer];
1097       [userSettings synchronize];
1098     }
1099
1100   return rc;
1101 }
1102
1103 - (id) lookupHomeFolderForUID: (NSString *) _uid
1104                     inContext: (id)_ctx
1105 {
1106   // TODO: DUP to SOGoGroupFolder
1107   NSException *error = nil;
1108   NSArray     *path;
1109   id          ctx, result;
1110
1111   if (![_uid isNotNull])
1112     return nil;
1113
1114   /* create subcontext, so that we don't destroy our environment */
1115   
1116   if ((ctx = [context createSubContext]) == nil) {
1117     [self errorWithFormat:@"could not create SOPE subcontext!"];
1118     return nil;
1119   }
1120   
1121   /* build path */
1122   
1123   path = _uid != nil ? [NSArray arrayWithObjects:&_uid count:1] : nil;
1124   
1125   /* traverse path */
1126   
1127   result = [[ctx application] traversePathArray:path inContext:ctx
1128                               error:&error acquire:NO];
1129   if (error != nil) {
1130     [self errorWithFormat: @"folder lookup failed (c_uid=%@): %@",
1131             _uid, error];
1132     return nil;
1133   }
1134   
1135   [self debugWithFormat:@"Note: got folder for uid %@ path %@: %@",
1136           _uid, [path componentsJoinedByString:@"=>"], result];
1137   return result;
1138 }
1139
1140 - (SOGoAppointmentFolder *) lookupCalendarFolderForUID: (NSString *) uid
1141 {
1142   SOGoFolder *currentContainer;
1143   SOGoAppointmentFolders *parent;
1144   NSException *error;
1145
1146   currentContainer = [[container container] container];
1147   currentContainer = [currentContainer lookupName: uid
1148                                        inContext: context
1149                                        acquire: NO];
1150   parent = [currentContainer lookupName: @"Calendar" inContext: context
1151                              acquire: NO];
1152   currentContainer = [parent lookupName: @"personal" inContext: context
1153                              acquire: NO];
1154   if (!currentContainer)
1155     {
1156       error = [parent newFolderWithName: [parent defaultFolderName]
1157                       andNameInContainer: @"personal"];
1158       if (!error)
1159         currentContainer = [parent lookupName: @"personal"
1160                                    inContext: context
1161                                    acquire: NO];
1162     }
1163
1164   return (SOGoAppointmentFolder *) currentContainer;
1165 }
1166
1167 - (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
1168                                  inContext: (id)_ctx
1169 {
1170   /* Note: can return NSNull objects in the array! */
1171   NSMutableArray *folders;
1172   NSEnumerator *e;
1173   NSString *uid, *ownerLogin;
1174   id folder;
1175
1176   ownerLogin = [self ownerInContext: context];
1177
1178   if ([_uids count] == 0) return nil;
1179   folders = [NSMutableArray arrayWithCapacity:16];
1180   e = [_uids objectEnumerator];
1181   while ((uid = [e nextObject]))
1182     {
1183       if ([uid isEqualToString: ownerLogin])
1184         folder = self;
1185       else
1186         {
1187           folder = [self lookupCalendarFolderForUID: uid];
1188           if (![folder isNotNull])
1189             [self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
1190         }
1191
1192       if (folder)
1193         [folders addObject: folder];
1194     }
1195
1196   return folders;
1197 }
1198
1199 - (NSArray *) lookupFreeBusyObjectsForUIDs: (NSArray *) _uids
1200                                  inContext: (id) _ctx
1201 {
1202   /* Note: can return NSNull objects in the array! */
1203   NSMutableArray *objs;
1204   NSEnumerator   *e;
1205   NSString       *uid;
1206   
1207   if ([_uids count] == 0) return nil;
1208   objs = [NSMutableArray arrayWithCapacity:16];
1209   e    = [_uids objectEnumerator];
1210   while ((uid = [e nextObject])) {
1211     id obj;
1212     
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]])
1217         obj = nil;
1218     }
1219     if (![obj isNotNull])
1220       [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
1221     
1222     /* Note: intentionally add 'null' folders to allow a mapping */
1223     [objs addObject:obj ? obj : [NSNull null]];
1224   }
1225   return objs;
1226 }
1227
1228 - (NSArray *) uidsFromICalPersons: (NSArray *) _persons
1229 {
1230   /* Note: can return NSNull objects in the array! */
1231   NSMutableArray    *uids;
1232   LDAPUserManager *um;
1233   unsigned          i, count;
1234   
1235   if (_persons == nil)
1236     return nil;
1237
1238   count = [_persons count];
1239   uids  = [NSMutableArray arrayWithCapacity:count + 1];
1240   um    = [LDAPUserManager sharedUserManager];
1241   
1242   for (i = 0; i < count; i++) {
1243     iCalPerson *person;
1244     NSString   *email;
1245     NSString   *uid;
1246     
1247     person = [_persons objectAtIndex:i];
1248     email  = [person rfc822Email];
1249     if ([email isNotNull]) {
1250       uid = [um getUIDForEmail:email];
1251     }
1252     else
1253       uid = nil;
1254     
1255     [uids addObject:(uid != nil ? uid : (id)[NSNull null])];
1256   }
1257   return uids;
1258 }
1259
1260 - (NSArray *)lookupCalendarFoldersForICalPerson: (NSArray *) _persons
1261                                       inContext: (id) _ctx
1262 {
1263   /* Note: can return NSNull objects in the array! */
1264   NSArray *uids;
1265
1266   if ((uids = [self uidsFromICalPersons:_persons]) == nil)
1267     return nil;
1268   
1269   return [self lookupCalendarFoldersForUIDs:uids inContext:_ctx];
1270 }
1271
1272 - (id) lookupGroupFolderForUIDs: (NSArray *) _uids
1273                       inContext: (id)_ctx
1274 {
1275   SOGoCustomGroupFolder *folder;
1276   
1277   if (_uids == nil)
1278     return nil;
1279
1280   folder = [[SOGoCustomGroupFolder alloc] initWithUIDs:_uids inContainer:self];
1281   return [folder autorelease];
1282 }
1283
1284 - (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids
1285                               inContext: (id) _ctx
1286 {
1287   SOGoCustomGroupFolder *folder;
1288   
1289   if ((folder = [self lookupGroupFolderForUIDs:_uids inContext:_ctx]) == nil)
1290     return nil;
1291   
1292   folder = [folder lookupName:@"Calendar" inContext:_ctx acquire:NO];
1293   if (![folder isNotNull])
1294     return nil;
1295   if ([folder isKindOfClass:[NSException class]]) {
1296     [self debugWithFormat:@"Note: could not lookup 'Calendar' in folder: %@",
1297             folder];
1298     return nil;
1299   }
1300   
1301   return folder;
1302 }
1303
1304 /* bulk fetches */
1305
1306 - (NSArray *) fetchAllSOGoAppointments
1307 {
1308   /* 
1309      Note: very expensive method, do not use unless absolutely required.
1310            returns an array of SOGoAppointment objects.
1311            
1312      Note that we can leave out the filenames, supposed to be stored
1313      in the 'uid' field of the iCalendar object!
1314   */
1315   NSMutableArray *events;
1316   NSDictionary *files;
1317   NSEnumerator *contents;
1318   NSString     *content;
1319   
1320   /* fetch all raw contents */
1321   
1322   files = [self fetchContentStringsAndNamesOfAllObjects];
1323   if (![files isNotNull]) return nil;
1324   if ([files isKindOfClass:[NSException class]]) return (id)files;
1325   
1326   /* transform to SOGo appointments */
1327   
1328   events   = [NSMutableArray arrayWithCapacity:[files count]];
1329   contents = [files objectEnumerator];
1330   while ((content = [contents nextObject]) != nil)
1331     [events addObject: [iCalCalendar parseSingleFromSource: content]];
1332   
1333   return events;
1334 }
1335
1336 // #warning We only support ONE calendar per user at this time
1337 // - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
1338 //                   toFolderList: (NSMutableArray *) calendarFolders
1339 // {
1340 //   NSEnumerator *keys;
1341 //   NSString *currentKey;
1342 //   NSMutableDictionary *currentCalendar;
1343 //   BOOL firstShouldBeActive;
1344 //   unsigned int count;
1345
1346 //   firstShouldBeActive = YES;
1347
1348 //   keys = [[subscribedFolders allKeys] objectEnumerator];
1349 //   currentKey = [keys nextObject];
1350 //   count = 1;
1351 //   while (currentKey)
1352 //     {
1353 //       currentCalendar = [NSMutableDictionary new];
1354 //       [currentCalendar autorelease];
1355 //       [currentCalendar
1356 //      setDictionary: [subscribedFolders objectForKey: currentKey]];
1357 //       [currentCalendar setObject: currentKey forKey: @"folder"];
1358 //       [calendarFolders addObject: currentCalendar];
1359 //       if ([[currentCalendar objectForKey: @"active"] boolValue])
1360 //      firstShouldBeActive = NO;
1361 //       count++;
1362 //       currentKey = [keys nextObject];
1363 //     }
1364
1365 //   return firstShouldBeActive;
1366 // }
1367
1368 // - (NSArray *) calendarFolders
1369 // {
1370 //   NSMutableDictionary *userCalendar, *calendarDict;
1371 //   NSMutableArray *calendarFolders;
1372 //   SOGoUser *calendarUser;
1373 //   BOOL firstActive;
1374
1375 //   calendarFolders = [NSMutableArray new];
1376 //   [calendarFolders autorelease];
1377
1378 //   calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
1379 //                         roles: nil];
1380 //   userCalendar = [NSMutableDictionary new];
1381 //   [userCalendar autorelease];
1382 //   [userCalendar setObject: @"/" forKey: @"folder"];
1383 //   [userCalendar setObject: @"Calendar" forKey: @"displayName"];
1384 //   [calendarFolders addObject: userCalendar];
1385
1386 //   calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
1387 //   firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
1388 //   firstActive = ([self _appendSubscribedFolders:
1389 //                       [calendarDict objectForKey: @"SubscribedFolders"]
1390 //                     toFolderList: calendarFolders]
1391 //               || firstActive);
1392 //   [userCalendar setObject: [NSNumber numberWithBool: firstActive]
1393 //              forKey: @"active"];
1394
1395 //   return calendarFolders;
1396 // }
1397
1398 // - (NSArray *) fetchContentObjectNames
1399 // {
1400 //   NSMutableArray *objectNames;
1401 //   NSArray *records;
1402 //   NSCalendarDate *today, *startDate, *endDate;
1403
1404 // #warning this should be user-configurable
1405 //   objectNames = [NSMutableArray array];
1406 //   today = [[NSCalendarDate calendarDate] beginOfDay];
1407 //   [today setTimeZone: timeZone];
1408
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"]];
1421
1422 //   return objectNames;
1423 // }
1424
1425 - (NSArray *) fetchContentObjectNames
1426 {
1427   static NSArray *cNameField = nil;
1428
1429   if (!cNameField)
1430     cNameField = [[NSArray alloc] initWithObjects: @"c_name", nil];
1431
1432   return [[self fetchFields: cNameField from: nil to: nil
1433                 title: nil component: nil] objectsForKey: @"c_name"];
1434 }
1435
1436 /* folder type */
1437
1438 - (NSString *) folderType
1439 {
1440   return @"Appointment";
1441 }
1442
1443 - (NSString *) outlookFolderClass
1444 {
1445   return @"IPF.Appointment";
1446 }
1447
1448 - (BOOL) isActive
1449 {
1450   NSUserDefaults *settings;
1451   NSArray *activeFolders;
1452
1453   settings = [[context activeUser] userSettings];
1454   activeFolders
1455     = [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
1456
1457   return [activeFolders containsObject: nameInContainer];
1458 }
1459
1460 @end /* SOGoAppointmentFolder */