]> err.no Git - scalable-opengroupware.org/blob - UI/Scheduler/UIxCalView.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1039 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / Scheduler / UIxCalView.m
1 // $Id$
2
3 #import "common.h"
4 //#import <OGoContentStore/OCSFolder.h>
5
6 #import <NGObjWeb/SoSecurityManager.h>
7 #import <NGObjWeb/SoUser.h>
8 #import <NGExtensions/NGCalendarDateRange.h>
9 #import <NGCards/NGCards.h>
10
11 #import <SOGoUI/SOGoAptFormatter.h>
12 #import "UIxComponent+Agenor.h"
13
14 #import "SoObjects/Appointments/SOGoAppointmentFolder.h"
15 #import <SOGo/NSArray+Utilities.h>
16 #import <SOGo/SOGoUser.h>
17 #import <SOGo/SOGoObject.h>
18
19 #import "UIxCalView.h"
20
21 @interface UIxCalView (PrivateAPI)
22 - (NSString *) _userFolderURI;
23 @end
24
25 @implementation UIxCalView
26
27 static BOOL shouldDisplayWeekend = NO;
28
29 + (void) initialize
30 {
31   static BOOL didInit = NO;
32   NSUserDefaults *ud;
33
34   if (didInit) return;
35
36   ud = [NSUserDefaults standardUserDefaults];
37   shouldDisplayWeekend = [ud boolForKey: @"SOGoShouldDisplayWeekend"];
38   didInit = YES;
39 }
40
41 - (id) init
42 {
43   self = [super init];
44   if (self)
45     {
46       NSTimeZone *tz;
47
48       tz = [[self clientObject] userTimeZone];
49       aptFormatter
50         = [[SOGoAptFormatter alloc] initWithDisplayTimeZone: tz];
51       aptTooltipFormatter
52         = [[SOGoAptFormatter alloc] initWithDisplayTimeZone: tz];
53       privateAptFormatter
54         = [[SOGoAptFormatter alloc] initWithDisplayTimeZone: tz];
55       privateAptTooltipFormatter
56         = [[SOGoAptFormatter alloc] initWithDisplayTimeZone: tz];
57       [self configureFormatters];
58       componentsData = [NSMutableDictionary new];
59       calendarFolders = nil;
60     }
61
62   return self;
63 }
64
65 - (void) dealloc
66 {
67   [calendarFolders release];
68   [componentsData release];
69   [appointments               release];
70   [allDayApts                 release];
71   [appointment                release];
72   [currentDay                 release];
73   [aptFormatter               release];
74   [aptTooltipFormatter        release];
75   [privateAptFormatter        release];
76   [privateAptTooltipFormatter release];
77   [super dealloc];
78 }
79
80 /* subclasses should override this */
81 - (void) configureFormatters
82 {
83   NSString *title;
84
85   [aptFormatter setFullDetails];
86   [aptTooltipFormatter setTooltip];
87   [privateAptFormatter setPrivateDetails];
88   [privateAptTooltipFormatter setPrivateTooltip];
89
90   title = [self labelForKey: @"empty title"];
91   [aptFormatter setTitlePlaceholder: title];
92   [aptTooltipFormatter setTitlePlaceholder: title];
93
94   title = [self labelForKey: @"private appointment"];
95   [privateAptFormatter setPrivateTitle: title];
96   [privateAptTooltipFormatter setPrivateTitle: title];
97 }
98
99 - (NSArray *) filterAppointments:(NSArray *) _apts
100 {
101   NSMutableArray *filtered;
102   unsigned i, count;
103   NSString *email;
104   NSDictionary *info;
105   NSArray *partmails;
106   unsigned p, pCount;
107   BOOL shouldAdd;
108   NSString *partmailsString;
109   NSArray  *partstates;
110   NSString *state;
111   NSString *pEmail;
112
113   if ([self shouldDisplayRejectedAppointments])
114     return _apts;
115   {
116     count = [_apts count];
117     filtered = [[[NSMutableArray alloc] initWithCapacity: count] autorelease];
118     email = [self emailForUser];
119
120     for (i = 0; i < count; i++)
121       {
122         shouldAdd = YES;
123         info = [_apts objectAtIndex: i];
124         partmailsString = [info objectForKey: @"partmails"];
125         if ([partmailsString isNotNull])
126           {
127             partmails = [partmailsString componentsSeparatedByString: @"\n"];
128             pCount = [partmails count];
129             for (p = 0; p < pCount; p++)
130               {
131                 pEmail = [partmails objectAtIndex: p];
132                 if ([pEmail isEqualToString: email])
133                   {
134                     partstates = [[info objectForKey: @"partstates"]
135                                    componentsSeparatedByString: @"\n"];
136                     state = [partstates objectAtIndex: p];
137                     if ([state intValue] == iCalPersonPartStatDeclined)
138                       shouldAdd = NO;
139                     break;
140                   }
141               }
142           }
143         if (shouldAdd)
144           [filtered addObject: info];
145       }
146   }
147
148   return filtered;
149 }
150
151 /* accessors */
152
153 - (void) setAppointments:(NSArray *) _apts
154 {
155   _apts = [self filterAppointments: _apts];
156   ASSIGN(appointments, _apts);
157 }
158
159 - (NSArray *) appointments
160 {
161   return appointments;
162 }
163
164 - (void) setAppointment:(id) _apt
165 {
166   NSString *mailtoChunk;
167   NSString *myEmail;
168
169   ASSIGN(appointment, _apt);
170
171   /* cache some info about apt for faster access */
172   
173   mailtoChunk = [_apt valueForKey: @"orgmail"];
174   myEmail = [self emailForUser];
175   if ([mailtoChunk rangeOfString: myEmail].length > 0)
176     {
177       aptFlags.isMyApt = YES;
178       aptFlags.canAccessApt = YES;
179     }
180   else
181     {
182       NSString *partmails;
183
184       aptFlags.isMyApt = NO;
185
186       partmails = [_apt valueForKey: @"partmails"];
187       if ([partmails rangeOfString: myEmail].length)
188         aptFlags.canAccessApt = YES;
189       else
190         aptFlags.canAccessApt
191           = ([[_apt valueForKey: @"classification"] intValue]
192              == iCalAccessPublic);
193     }
194 }
195
196 - (void) setTasks: (NSArray *) _tasks
197 {
198   ASSIGN(tasks, _tasks);
199 }
200
201 - (NSArray *) tasks
202 {
203   return tasks;
204 }
205
206 - (id) appointment
207 {
208   return appointment;
209 }
210
211 - (BOOL) isMyApt
212 {
213   return aptFlags.isMyApt ? YES : NO;
214 }
215
216 - (BOOL) canAccessApt
217 {
218   return aptFlags.canAccessApt ? YES : NO;
219 }
220
221 - (BOOL) canNotAccessApt
222 {
223   return aptFlags.canAccessApt ? NO : YES;
224 }
225
226 - (NSDictionary *) aptTypeDict
227 {
228   return nil;
229 }
230 - (NSString *) aptTypeLabel
231 {
232   return @"aptLabel";
233 }
234 - (NSString *) aptTypeIcon
235 {
236   return @"";
237 }
238
239 - (SOGoAptFormatter *) aptFormatter
240 {
241   if (aptFlags.canAccessApt)
242     return aptFormatter;
243   return privateAptFormatter;
244 }
245
246 - (SOGoAptFormatter *) aptTooltipFormatter
247 {
248   if (aptFlags.canAccessApt)
249     return aptTooltipFormatter;
250   return privateAptTooltipFormatter;
251 }
252
253 /* TODO: remove this */
254 - (NSString *) shortTextForApt
255 {
256   [self warnWithFormat: @"%s IS DEPRECATED!", __PRETTY_FUNCTION__];
257   if (![self canAccessApt])
258     return @"";
259   return [[self aptFormatter] stringForObjectValue: appointment];
260 }
261
262 - (NSString *) shortTitleForApt
263 {
264   NSString *title;
265
266   [self warnWithFormat: @"%s IS DEPRECATED!", __PRETTY_FUNCTION__];
267
268   if (![self canAccessApt])
269     return @"";
270   title = [appointment valueForKey: @"title"];
271   if ([title length] > 12)
272     title = [[title substringToIndex: 11] stringByAppendingString: @"..."];
273   
274   return title;
275 }
276
277 - (NSString *) tooltipForApt
278 {
279   [self warnWithFormat: @"%s IS DEPRECATED!", __PRETTY_FUNCTION__];
280   return [[self aptTooltipFormatter] stringForObjectValue: appointment
281                                      referenceDate: [self currentDay]];
282 }
283
284 - (NSString *) aptStyle
285 {
286   return nil;
287 }
288
289 - (NSCalendarDate *) referenceDateForFormatter
290 {
291   return [self selectedDate];
292 }
293
294 - (NSCalendarDate *) thisMonth
295 {
296   return [self selectedDate];
297 }
298
299 - (NSCalendarDate *) nextMonth
300 {
301   NSCalendarDate *date = [self thisMonth];
302   return [date dateByAddingYears: 0 months: 1 days: 0
303                hours: 0 minutes: 0 seconds: 0];
304 }
305
306 - (NSCalendarDate *) prevMonth
307 {
308   NSCalendarDate *date = [self thisMonth];
309   return [date dateByAddingYears: 0 months:-1 days: 0
310                hours: 0 minutes: 0 seconds: 0];
311 }
312
313 - (NSString *) prevMonthAsString
314 {
315   return [self dateStringForDate: [self prevMonth]];
316 }
317
318 - (NSString *) nextMonthAsString
319 {
320   return [self dateStringForDate: [self nextMonth]];
321 }
322
323 /* current day related */
324
325 - (void) setCurrentDay:(NSCalendarDate *) _day
326 {
327   [_day setTimeZone: [[self clientObject] userTimeZone]];
328   ASSIGN(currentDay, _day);
329 }
330
331 - (NSCalendarDate *) currentDay
332 {
333   return currentDay;
334 }
335
336 - (NSString *) currentDayName
337 {
338   return [self localizedNameForDayOfWeek: [currentDay dayOfWeek]];
339 }
340
341 - (id) holidayInfo
342 {
343   return nil;
344 }
345
346 - (NSArray *) allDayApts
347 {
348   NSArray        *apts;
349   NSMutableArray *filtered;
350   unsigned       i, count;
351
352   if (allDayApts)
353     return allDayApts;
354
355   apts = [self appointments];
356   count = [apts count];
357   filtered = [[NSMutableArray alloc] initWithCapacity: 3];
358   for (i = 0; i < count; i++)
359     {
360       id       apt;
361       NSNumber *bv;
362
363       apt = [apts objectAtIndex: i];
364       bv = [apt valueForKey: @"isallday"];
365       if ([bv boolValue])
366         [filtered addObject: apt];
367     }
368     
369   ASSIGN(allDayApts, filtered);
370   [filtered release];
371   return allDayApts;
372 }
373
374
375 /* special appointments */
376
377 - (BOOL) hasDayInfo
378 {
379   return [self hasHoldidayInfo] || [self hasAllDayApts];
380 }
381
382 - (BOOL) hasHoldidayInfo
383 {
384   return [self holidayInfo] != nil;
385 }
386
387 - (BOOL) hasAllDayApts
388 {
389   return [[self allDayApts] count] != 0;
390 }
391
392
393 /* defaults */
394
395 - (BOOL) showFullNames
396 {
397   return YES;
398 }
399
400 - (BOOL) showAMPMDates
401 {
402   return NO;
403 }
404
405 - (unsigned) dayStartHour
406 {
407   return 0;
408 }
409
410 - (unsigned) dayEndHour
411 {
412   return 23;
413 }
414
415 - (BOOL) shouldDisplayWeekend
416 {
417   return shouldDisplayWeekend;
418 }
419
420 - (BOOL) shouldHideWeekend
421 {
422   return ![self shouldDisplayWeekend];
423 }
424
425
426 /* URLs */
427
428 - (NSString *) appointmentViewURL
429 {
430   id pkey;
431   
432   if (![(pkey = [[self appointment] valueForKey: @"uid"]) isNotNull])
433     return nil;
434   
435   return [[[self clientObject] baseURLForAptWithUID: [pkey stringValue]
436                                inContext: [self context]]
437            stringByAppendingString: @"/view"];
438 }
439
440 /* fetching */
441
442 - (NSCalendarDate *) startDate
443 {
444   return [self selectedDate];
445 }
446
447 - (NSCalendarDate *) endDate
448 {
449   return [[self startDate] tomorrow];
450 }
451
452 #warning We only support ONE calendar per user at this time
453 - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
454 {
455   NSEnumerator *keys;
456   NSString *currentKey;
457   NSMutableDictionary *currentCalendar;
458   BOOL firstShouldBeActive;
459   unsigned int count;
460
461   firstShouldBeActive = YES;
462
463   keys = [[subscribedFolders allKeys] objectEnumerator];
464   currentKey = [keys nextObject];
465   count = 1;
466   while (currentKey)
467     {
468       currentCalendar = [NSMutableDictionary new];
469       [currentCalendar autorelease];
470       [currentCalendar
471         setDictionary: [subscribedFolders objectForKey: currentKey]];
472       [currentCalendar setObject: currentKey forKey: @"folder"];
473       [calendarFolders addObject: currentCalendar];
474       if ([[currentCalendar objectForKey: @"active"] boolValue])
475         firstShouldBeActive = NO;
476       count++;
477       currentKey = [keys nextObject];
478     }
479
480   return firstShouldBeActive;
481 }
482
483 - (void) _setupCalendarFolders
484 {
485   NSMutableDictionary *userCalendar, *calendarDict;
486   SOGoUser *activeUser;
487   BOOL firstActive;
488
489   calendarFolders = [NSMutableArray new];
490   activeUser = [context activeUser];
491
492   userCalendar = [NSMutableDictionary new];
493   [userCalendar autorelease];
494   [userCalendar setObject: @"/" forKey: @"folder"];
495   [userCalendar setObject: [self labelForKey: @"Calendar"]
496                    forKey: @"displayName"];
497   [calendarFolders addObject: userCalendar];
498
499   calendarDict = [[activeUser userSettings] objectForKey: @"Calendar"];
500   firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
501   firstActive = ([self _appendSubscribedFolders:
502                          [calendarDict objectForKey: @"SubscribedFolders"]]
503                  || firstActive);
504   [userCalendar setObject: [NSNumber numberWithBool: firstActive]
505                 forKey: @"active"];
506 }
507
508 - (SOGoAppointmentFolder *) _aptFolder: (NSString *) folder
509                       withClientObject: (SOGoAppointmentFolder *) clientObject
510 {
511   SOGoAppointmentFolder *aptFolder;
512   NSArray *folderParts;
513
514   if ([folder isEqualToString: @"/"])
515     aptFolder = clientObject;
516   else
517     {
518       folderParts = [folder componentsSeparatedByString: @":"];
519       aptFolder
520         = [clientObject lookupCalendarFolderForUID:
521                           [folderParts objectAtIndex: 0]];
522     }
523
524   return aptFolder;
525 }
526
527 - (NSArray *) calendarFolders
528 {
529   if (!calendarFolders)
530     [self _setupCalendarFolders];
531
532   return calendarFolders;
533 }
534
535 - (NSArray *) _activeCalendarFolders
536 {
537   NSMutableArray *activeFolders;
538   NSEnumerator *folders;
539   NSDictionary *currentFolderDict;
540   SOGoAppointmentFolder *currentFolder, *clientObject;
541
542   activeFolders = [NSMutableArray new];
543   [activeFolders autorelease];
544
545   if (!calendarFolders)
546     [self _setupCalendarFolders];
547
548   clientObject = [self clientObject];
549
550   folders = [calendarFolders objectEnumerator];
551   currentFolderDict = [folders nextObject];
552   while (currentFolderDict)
553     {
554       if ([[currentFolderDict objectForKey: @"active"] boolValue])
555         {
556           currentFolder
557             = [self _aptFolder: [currentFolderDict objectForKey: @"folder"]
558                     withClientObject: clientObject];
559           [activeFolders addObject: currentFolder];
560         }
561
562       currentFolderDict = [folders nextObject];
563     }
564
565   return activeFolders;
566 }
567
568 - (NSArray *) _fetchCoreInfosForComponent: (NSString *) component
569 {
570   NSArray *currentInfos;
571   NSMutableArray *infos;
572   NSEnumerator *folders;
573   SOGoAppointmentFolder *currentFolder;
574
575   infos = [componentsData objectForKey: component];
576   if (!infos)
577     {
578       infos = [NSMutableArray array];
579       folders = [[self _activeCalendarFolders] objectEnumerator];
580       currentFolder = [folders nextObject];
581       while (currentFolder)
582         {
583           currentInfos = [currentFolder fetchCoreInfosFrom: [[self startDate] beginOfDay]
584                                         to: [[self endDate] endOfDay]
585                                         component: component];
586           [currentInfos makeObjectsPerform: @selector (setObject:forKey:)
587                         withObject: [currentFolder ownerInContext: nil]
588                         withObject: @"owner"];
589           [infos addObjectsFromArray: currentInfos];
590           currentFolder = [folders nextObject];
591         }
592       [componentsData setObject: infos forKey: component];
593     }
594
595   return infos;
596 }
597
598 - (NSArray *) fetchCoreAppointmentsInfos
599 {
600   if (!appointments)
601     [self setAppointments: [self _fetchCoreInfosForComponent: @"vevent"]];
602   
603   return appointments;
604 }
605
606 - (NSArray *) fetchCoreTasksInfos
607 {
608   if (!tasks)
609     [self setTasks: [self _fetchCoreInfosForComponent: @"vtodo"]];
610   
611   return tasks;
612 }
613
614 /* query parameters */
615
616 - (BOOL) shouldDisplayRejectedAppointments
617 {
618   NSString *bv;
619   
620   bv = [self queryParameterForKey: @"dr"];
621   if (!bv) return NO;
622   return [bv boolValue];
623 }
624
625 - (NSDictionary *) toggleShowRejectedAptsQueryParameters
626 {
627   NSMutableDictionary *qp;
628   BOOL                shouldDisplay;
629   
630   shouldDisplay = ![self shouldDisplayRejectedAppointments];
631   qp = [[[self queryParameters] mutableCopy] autorelease];
632   [qp setObject: shouldDisplay ? @"1" : @"0" forKey: @"dr"];
633   return qp;
634 }
635
636 - (NSString *) toggleShowRejectedAptsLabel
637 {
638   if (![self shouldDisplayRejectedAppointments])
639     return @"show_rejected_apts";
640   return @"hide_rejected_apts";
641 }
642
643 /* date selection & conversion */
644
645 - (NSDictionary *) _dateQueryParametersWithOffset: (int) daysOffset
646 {
647   NSCalendarDate *date;
648   
649   date = [[self startDate] dateByAddingYears: 0 months: 0
650                            days: daysOffset
651                            hours: 0 minutes: 0 seconds: 0];
652   return [self queryParametersBySettingSelectedDate: date];
653 }
654
655 - (NSDictionary *) todayQueryParameters
656 {
657   return [self queryParametersBySettingSelectedDate: [NSCalendarDate date]];
658 }
659
660 - (NSDictionary *) currentDayQueryParameters
661 {
662   return [self queryParametersBySettingSelectedDate: currentDay];
663 }
664
665 /* calendarUIDs */
666
667 - (NSString *) formattedCalendarUIDs
668 {
669   return [[[self clientObject] calendarUIDs]
670            componentsJoinedByString: @", "];
671 }
672
673 /* Actions */
674
675 - (NSString *) _userFolderURI
676 {
677   WOContext *ctx;
678   id        obj;
679   NSURL     *url;
680
681   ctx = [self context];
682   obj = [[ctx objectTraversalStack] objectAtIndex: 1];
683   url = [NSURL URLWithString: [obj baseURLInContext: ctx]];
684   return [[url path] stringByUnescapingURL];
685 }
686
687 - (id) redirectForUIDsAction
688 {
689   NSMutableString *uri;
690   NSString *uidsString, *loc, *prevMethod, *userFolderID;
691   id <WOActionResults> r;
692   BOOL useGroups;
693   unsigned index;
694
695   uidsString = [self queryParameterForKey: @"userUIDString"];
696   uidsString = [uidsString stringByTrimmingSpaces];
697   [self setQueryParameter: nil forKey: @"userUIDString"];
698
699   prevMethod = [self queryParameterForKey: @"previousMethod"];
700   if (prevMethod == nil)
701     prevMethod = @"";
702
703   uri = [[NSMutableString alloc] initWithString: [self _userFolderURI]];
704   /* if we have more than one entry, use groups - otherwise don't */
705   useGroups = [uidsString rangeOfString: @","].length > 0;
706   userFolderID = [uri lastPathComponent];
707   if (useGroups)
708     {
709       NSArray *uids;
710
711       uids = [uidsString componentsSeparatedByString: @","];
712       /* guarantee that our id is the first */
713       if (((index = [uids indexOfObject: userFolderID]) != NSNotFound)
714           && (index != 0))
715         {
716           uids = [[uids mutableCopy] autorelease];
717           [(NSMutableArray *) uids removeObjectAtIndex: index];
718           [(NSMutableArray *) uids insertObject: userFolderID atIndex: 0];
719           uidsString = [uids componentsJoinedByString: @","];
720         }
721       [uri appendString: @"Groups/_custom_"];
722       [uri appendString: uidsString];
723       [uri appendString: @"/"];
724       NSLog (@"Group URI = '%@'", uri);
725     }
726   else
727     {
728       /* check if lastPathComponent is the base that we want to have */
729       if ((([uidsString length] != 0)
730            && (![userFolderID isEqualToString: uidsString])))
731         {
732           NSRange r;
733           
734           /* uri ends with an '/', so we have to adjust the range a little */
735           r = NSMakeRange(0, [uri length] - 1);
736           r = [uri rangeOfString: @"/"
737                    options: NSBackwardsSearch
738                    range: r];
739           r = NSMakeRange(r.location + 1, [uri length] - r.location - 2);
740           [uri replaceCharactersInRange: r withString: uidsString];
741         }
742     }
743   [uri appendString: @"Calendar/"];
744   [uri appendString: prevMethod];
745
746 #if 0
747   NSLog(@"%s redirect uri:%@",
748         __PRETTY_FUNCTION__,
749         uri);
750 #endif
751   loc = [self completeHrefForMethod: uri]; /* this might return uri! */
752   r = [self redirectToLocation: loc];
753   [uri release];
754   return r;
755 }
756
757 @end /* UIxCalView */