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