]> err.no Git - scalable-opengroupware.org/blob - UI/Scheduler/UIxComponentEditor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1025 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / Scheduler / UIxComponentEditor.m
1 /* UIxComponentEditor.m - this file is part of SOGo
2  *
3  * Copyright (C) 2006 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
7  * This file is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #import <Foundation/NSArray.h>
24 #import <Foundation/NSBundle.h>
25 #import <Foundation/NSException.h>
26 #import <Foundation/NSCalendarDate.h>
27 #import <Foundation/NSString.h>
28 #import <Foundation/NSUserDefaults.h>
29 #import <Foundation/NSURL.h>
30
31 #import <NGCards/iCalPerson.h>
32 #import <NGCards/iCalRepeatableEntityObject.h>
33 #import <NGCards/iCalRecurrenceRule.h>
34 #import <NGCards/NSString+NGCards.h>
35 #import <NGCards/NSCalendarDate+NGCards.h>
36 #import <NGExtensions/NSCalendarDate+misc.h>
37 #import <NGExtensions/NSObject+Logs.h>
38 #import <NGExtensions/NSString+misc.h>
39 #import <NGObjWeb/NSException+HTTP.h>
40 #import <NGObjWeb/WORequest.h>
41
42 #import <SOGo/AgenorUserManager.h>
43 #import <SOGo/SOGoUser.h>
44 #import <SOGoUI/SOGoDateFormatter.h>
45 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
46 #import <SoObjects/Appointments/SOGoTaskObject.h>
47
48 #import "UIxComponent+Agenor.h"
49
50 #import "UIxComponentEditor.h"
51
52 @implementation UIxComponentEditor
53
54 - (id) init
55 {
56   if ((self = [super init]))
57     {
58       [self setPrivacy: @"PUBLIC"];
59       [self setCheckForConflicts: NO];
60       [self setIsCycleEndNever];
61       componentOwner = @"";
62       componentLoaded = NO;
63     }
64
65   return self;
66 }
67
68 - (void) dealloc
69 {
70   [iCalString release];
71   [errorText release];
72   [item release];
73   [startDate release];
74   [cycleUntilDate release];
75   [title release];
76   [location release];
77   [organizer release];
78   [comment release];
79   [participants release];
80   [resources release];
81   [priority release];
82   [categories release];
83   [cycle release];
84   [cycleEnd release];
85   [url release];
86
87   [super dealloc];
88 }
89
90 /* accessors */
91
92 - (void) setItem: (id) _item
93 {
94   ASSIGN(item, _item);
95 }
96
97 - (id) item
98 {
99   return item;
100 }
101
102 - (NSString *) itemPriorityText
103 {
104   return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
105 }
106
107 - (NSString *) itemPrivacyText
108 {
109   return [self labelForKey: [NSString stringWithFormat: @"privacy_%@", item]];
110 }
111
112 - (NSString *) itemStatusText
113 {
114   return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
115 }
116
117 - (void) setErrorText: (NSString *) _txt
118 {
119   ASSIGNCOPY(errorText, _txt);
120 }
121
122 - (NSString *) errorText
123 {
124   return errorText;
125 }
126
127 - (BOOL) hasErrorText
128 {
129   return [errorText length] > 0 ? YES : NO;
130 }
131
132 - (void) setStartDate: (NSCalendarDate *) _date
133 {
134   ASSIGN(startDate, _date);
135 }
136
137 - (NSCalendarDate *) startDate
138 {
139   return startDate;
140 }
141
142 - (void) setTitle: (NSString *) _value
143 {
144   ASSIGNCOPY(title, _value);
145 }
146
147 - (NSString *) title
148 {
149   return title;
150 }
151
152 - (void) setUrl: (NSString *) _url
153 {
154   ASSIGNCOPY(url, _url);
155 }
156
157 - (NSString *) url
158 {
159   return url;
160 }
161
162 - (void) setLocation: (NSString *) _value
163 {
164   ASSIGNCOPY(location, _value);
165 }
166
167 - (NSString *) location
168 {
169   return location;
170 }
171
172 - (void) setComment: (NSString *) _value
173 {
174   ASSIGNCOPY(comment, _value);
175 }
176
177 - (NSString *) comment
178 {
179   return comment;
180 }
181
182 - (NSArray *) categoryItems
183 {
184   // TODO: make this configurable?
185   /*
186    Tasks categories will be modified as follow :
187    – by default (a simple logo or no logo at all),
188    – task,
189    – outside,
190    – meeting,
191    – holidays,
192    – phone.
193   */
194   static NSArray *categoryItems = nil;
195   
196   if (!categoryItems)
197     {
198       categoryItems = [NSArray arrayWithObjects: @"APPOINTMENT",
199                                @"NOT IN OFFICE",
200                                @"MEETING",
201                                @"HOLIDAY",
202                                @"PHONE CALL",
203                                nil];
204       [categoryItems retain];
205     }
206
207   return categoryItems;
208 }
209
210 - (void) setCategories: (NSArray *) _categories
211 {
212   ASSIGN(categories, _categories);
213 }
214
215 - (NSArray *) categories
216 {
217   return categories;
218 }
219
220 - (NSString *) itemCategoryText
221 {
222   return [[self labelForKey: item] stringByEscapingHTMLString];
223 }
224
225 /* priorities */
226
227 - (NSArray *) priorities
228 {
229   /* 0 == undefined
230      5 == normal
231      1 == high
232   */
233   static NSArray *priorities = nil;
234
235   if (!priorities)
236     {
237       priorities = [NSArray arrayWithObjects:@"0", @"5", @"1", nil];
238       [priorities retain];
239     }
240
241   return priorities;
242 }
243
244 - (void) setPriority: (NSString *) _priority
245 {
246   ASSIGN(priority, _priority);
247 }
248
249 - (NSString *) priority
250 {
251   return priority;
252 }
253
254 - (NSArray *) privacyClasses
255 {
256   static NSArray *priorities = nil;
257
258   if (!priorities)
259     {
260       priorities = [NSArray arrayWithObjects: @"PUBLIC",
261                             @"CONFIDENTIAL", @"PRIVATE", nil];
262       [priorities retain];
263     }
264
265   return priorities;
266 }
267
268 - (void) setPrivacy: (NSString *) _privacy
269 {
270   ASSIGN(privacy, _privacy);
271 }
272
273 - (NSString *) privacy
274 {
275   return privacy;
276 }
277
278 - (NSArray *) statusTypes
279 {
280   static NSArray *priorities = nil;
281
282   if (!priorities)
283     {
284       priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
285       [priorities retain];
286     }
287
288   return priorities;
289 }
290
291 - (void) setStatus: (NSString *) _status
292 {
293   ASSIGN(status, _status);
294 }
295
296 - (NSString *) status
297 {
298   return status;
299 }
300
301 - (void) setParticipants: (NSArray *) _parts
302 {
303   ASSIGN(participants, _parts);
304 }
305
306 - (NSArray *) participants
307 {
308   return participants;
309 }
310
311 - (void) setResources: (NSArray *) _res
312 {
313   ASSIGN(resources, _res);
314 }
315
316 - (NSArray *) resources
317 {
318   return resources;
319 }
320
321 - (void) setCheckForConflicts: (BOOL) _checkForConflicts
322 {
323   checkForConflicts = _checkForConflicts;
324 }
325
326 - (BOOL) checkForConflicts
327 {
328   return checkForConflicts;
329 }
330
331 - (NSArray *) cycles
332 {
333   NSBundle *bundle;
334   NSString *path;
335   static NSArray *cycles = nil;
336   
337   if (!cycles)
338     {
339       bundle = [NSBundle bundleForClass:[self class]];
340       path   = [bundle pathForResource:@"cycles" ofType:@"plist"];
341       NSAssert(path != nil, @"Cannot find cycles.plist!");
342       cycles = [[NSArray arrayWithContentsOfFile:path] retain];
343       NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
344     }
345
346   return cycles;
347 }
348
349 - (void) setCycle: (NSDictionary *) _cycle
350 {
351   ASSIGN(cycle, _cycle);
352 }
353
354 - (NSDictionary *) cycle
355 {
356   return cycle;
357 }
358
359 - (BOOL) hasCycle
360 {
361   return ([cycle objectForKey: @"rule"] != nil);
362 }
363
364 - (NSString *) cycleLabel
365 {
366   NSString *key;
367   
368   key = [(NSDictionary *)item objectForKey:@"label"];
369
370   return [self labelForKey:key];
371 }
372
373 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
374 {
375   NSCalendarDate *until;
376
377   /* copy hour/minute/second from startDate */
378   until = [_cycleUntilDate hour: [startDate hourOfDay]
379                            minute: [startDate minuteOfHour]
380                            second: [startDate secondOfMinute]];
381   [until setTimeZone: [startDate timeZone]];
382   ASSIGN(cycleUntilDate, until);
383 }
384
385 - (NSCalendarDate *) cycleUntilDate
386 {
387   return cycleUntilDate;
388 }
389
390 - (iCalRecurrenceRule *) rrule
391 {
392   NSString *ruleRep;
393   iCalRecurrenceRule *rule;
394
395   if (![self hasCycle])
396     return nil;
397   ruleRep = [cycle objectForKey:@"rule"];
398   rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
399
400   if (cycleUntilDate && [self isCycleEndUntil])
401     [rule setUntilDate:cycleUntilDate];
402
403   return rule;
404 }
405
406 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
407 {
408   NSDictionary *c;
409   NSCalendarDate *until;
410   
411   c = [self cycleMatchingRRule:_rrule];
412   [self setCycle:c];
413
414   until = [[[_rrule untilDate] copy] autorelease];
415   if (!until)
416     until = startDate;
417   else
418     [self setIsCycleEndUntil];
419
420   [until setTimeZone:[[self clientObject] userTimeZone]];
421   [self setCycleUntilDate:until];
422 }
423
424 /*
425  This method is necessary, because we have a fixed sets of cycles in the UI.
426  The model is able to represent arbitrary rules, however.
427  There SHOULD be a different UI, similar to iCal.app, to allow modelling
428  of more complex rules.
429  
430  This method obviously cannot map all existing rules back to the fixed list
431  in cycles.plist. This should be fixed in a future version when interop
432  becomes more important.
433  */
434 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
435 {
436   NSString *cycleRep;
437   NSArray *cycles;
438   unsigned i, count;
439
440   if (!_rrule)
441     return [[self cycles] objectAtIndex:0];
442
443   cycleRep = [_rrule versitString];
444   cycles   = [self cycles];
445   count    = [cycles count];
446   for (i = 1; i < count; i++) {
447     NSDictionary *c;
448     NSString *cr;
449
450     c  = [cycles objectAtIndex:i];
451     cr = [c objectForKey:@"rule"];
452     if ([cr isEqualToString:cycleRep])
453       return c;
454   }
455   [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
456   return nil;
457 }
458
459 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
460 - (NSArray *) cycleEnds
461 {
462   static NSArray *ends = nil;
463   
464   if (!ends)
465     {
466       ends = [NSArray arrayWithObjects: @"cycle_end_never",
467                       @"cycle_end_until", nil];
468       [ends retain];
469     }
470
471   return ends;
472 }
473
474 - (void) setCycleEnd: (NSString *) _cycleEnd
475 {
476   ASSIGNCOPY(cycleEnd, _cycleEnd);
477 }
478
479 - (NSString *) cycleEnd
480 {
481   return cycleEnd;
482 }
483
484 - (BOOL) isCycleEndUntil
485 {
486   return (cycleEnd &&
487           [cycleEnd isEqualToString:@"cycle_end_until"]);
488 }
489
490 - (void) setIsCycleEndUntil
491 {
492   [self setCycleEnd:@"cycle_end_until"];
493 }
494
495 - (void) setIsCycleEndNever
496 {
497   [self setCycleEnd:@"cycle_end_never"];
498 }
499
500 /* helpers */
501 - (NSFormatter *) titleDateFormatter
502 {
503   SOGoDateFormatter *fmt;
504   
505   fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
506   [fmt autorelease];
507   [fmt setFullWeekdayNameAndDetails];
508
509   return fmt;
510 }
511
512 - (NSString *) completeURIForMethod: (NSString *) _method
513 {
514   NSString *uri;
515   NSRange r;
516     
517   uri = [[[self context] request] uri];
518     
519   /* first: identify query parameters */
520   r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
521   if (r.length > 0)
522     uri = [uri substringToIndex:r.location];
523     
524   /* next: append trailing slash */
525   if (![uri hasSuffix:@"/"])
526     uri = [uri stringByAppendingString:@"/"];
527   
528   /* next: append method */
529   uri = [uri stringByAppendingString:_method];
530     
531   /* next: append query parameters */
532   return [self completeHrefForMethod:uri];
533 }
534
535 - (BOOL) isWriteableClientObject
536 {
537   return [[self clientObject] 
538                 respondsToSelector:@selector(saveContentString:)];
539 }
540
541 - (BOOL) shouldTakeValuesFromRequest: (WORequest *) _rq
542                            inContext: (WOContext*) _c
543 {
544   return YES;
545 }
546
547 - (BOOL) containsConflict: (id) _component
548 {
549   [self subclassResponsibility: _cmd];
550
551   return NO;
552 }
553
554 /* access */
555
556 #if 0
557 - (iCalPerson *) getOrganizer
558 {
559   iCalPerson *p;
560   NSString *emailProp;
561   
562   emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
563   p = [[[iCalPerson alloc] init] autorelease];
564   [p setEmail:emailProp];
565   [p setCn:[self cnForUser]];
566   return p;
567 }
568 #endif
569
570 - (BOOL) isMyComponent
571 {
572   // TODO: this should check a set of emails against the SoUser
573   return ([[organizer rfc822Email] isEqualToString: [self emailForUser]]);
574 }
575
576 - (BOOL) canEditComponent
577 {
578   return [self isMyComponent];
579 }
580
581 /* response generation */
582
583 - (NSString *) initialCycleVisibility
584 {
585   return ([self hasCycle]
586           ? @"visibility: visible;"
587           : @"visibility: hidden;");
588 }
589
590 - (NSString *) initialCycleEndUntilVisibility {
591   return ([self isCycleEndUntil]
592           ? @"visibility: visible;"
593           : @"visibility: hidden;");
594 }
595
596 /* subclasses */
597 - (NSCalendarDate *) newStartDate
598 {
599   NSCalendarDate *newStartDate, *now;
600   int hour;
601
602   newStartDate = [self selectedDate];
603   if ([[self queryParameterForKey: @"hm"] length] == 0)
604     {
605       now = [NSCalendarDate calendarDate];
606       [now setTimeZone: [[self clientObject] userTimeZone]];
607       if ([now isDateOnSameDay: newStartDate])
608         {
609           hour = [now hourOfDay];
610           if (hour < 8)
611             newStartDate = [now hour: 8 minute: 0];
612           else if (hour > 18)
613             newStartDate = [[now tomorrow] hour: 8 minute: 0];
614           else
615             newStartDate = now;
616         }
617       else
618         newStartDate = [newStartDate hour: 8 minute: 0];
619     }
620
621   return newStartDate;
622 }
623
624 - (void) loadValuesFromComponent: (iCalRepeatableEntityObject *) component
625 {
626   iCalRecurrenceRule *rrule;
627   NSTimeZone *uTZ;
628   SOGoObject *co;
629
630   co = [self clientObject];
631   componentOwner = [co ownerInContext: nil];
632   componentLoaded = YES;
633
634   startDate = [component startDate];
635 //   if ((startDate = [component startDate]) == nil)
636 //     startDate = [[NSCalendarDate date] hour:11 minute:0];
637   uTZ = [co userTimeZone];
638   if (startDate)
639     {
640       [startDate setTimeZone: uTZ];
641       [startDate retain];
642     }
643
644   title        = [[component summary] copy];
645   location     = [[component location] copy];
646   comment      = [[component comment] copy];
647   url          = [[[component url] absoluteString] copy];
648   privacy      = [[component accessClass] copy];
649   priority     = [[component priority] copy];
650   status       = [[component status] copy];
651   categories   = [[[component categories] commaSeparatedValues] retain];
652   organizer    = [[component organizer] retain];
653   participants = [[component participants] retain];
654   resources    = [[component resources] retain];
655
656   /* cycles */
657   if ([component isRecurrent])
658     {
659       rrule = [[component recurrenceRules] objectAtIndex: 0];
660       [self adjustCycleControlsForRRule: rrule];
661     }
662 }
663
664 - (NSString *) iCalStringTemplate
665 {
666   [self subclassResponsibility: _cmd];
667
668   return @"";
669 }
670
671 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
672 {
673   NSString *s;
674   
675   s = [self iCalParticipantsStringFromQueryParameters];
676   return [s stringByAppendingString:
677               [self iCalResourcesStringFromQueryParameters]];
678 }
679
680 - (NSString *) iCalParticipantsStringFromQueryParameters
681 {
682   static NSString *iCalParticipantString = \
683     @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
684   
685   return [self iCalStringFromQueryParameter: @"ps"
686                format: iCalParticipantString];
687 }
688
689 - (NSString *) iCalResourcesStringFromQueryParameters
690 {
691   static NSString *iCalResourceString = \
692     @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
693
694   return [self iCalStringFromQueryParameter: @"rs"
695                format: iCalResourceString];
696 }
697
698 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
699                                      format: (NSString *) _format
700 {
701   AgenorUserManager *um;
702   NSMutableString *iCalRep;
703   NSString *s;
704
705   um = [AgenorUserManager sharedUserManager];
706   iCalRep = (NSMutableString *)[NSMutableString string];
707   s = [self queryParameterForKey:_qp];
708   if(s && [s length] > 0) {
709     NSArray *es;
710     unsigned i, count;
711     
712     es = [s componentsSeparatedByString:@","];
713     count = [es count];
714     for(i = 0; i < count; i++) {
715       NSString *email, *cn;
716       
717       email = [es objectAtIndex:i];
718       cn = [um getCNForUID:[um getUIDForEmail:email]];
719       [iCalRep appendFormat:_format, cn, email];
720     }
721   }
722   return iCalRep;
723 }
724
725 - (NSString *) iCalOrganizerString
726 {
727   return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
728                    [self cnForUser], [self emailForUser]];
729 }
730
731 - (NSString *) saveUrl
732 {
733   [self subclassResponsibility: _cmd];
734
735   return @"";
736 }
737
738 - (NSException *) validateObjectForStatusChange
739 {
740   id co;
741
742   co = [self clientObject];
743   if (![co
744          respondsToSelector: @selector(changeParticipationStatus:inContext:)])
745     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
746                         reason:
747                           @"method cannot be invoked on the specified object"];
748
749   return nil;
750 }
751
752 /* contact editor compatibility */
753
754 - (void) setICalString: (NSString *) _s
755 {
756   ASSIGNCOPY(iCalString, _s);
757 }
758
759 - (NSString *) iCalString
760 {
761   return iCalString;
762 }
763
764
765 - (NSArray *) availableCalendars
766 {
767   NSEnumerator *rawContacts;
768   NSString *list, *currentId;
769   NSMutableArray *calendars;
770   SOGoUser *user;
771
772   calendars = [NSMutableArray array];
773
774   user = [context activeUser];
775   list = [[user userDefaults] stringForKey: @"calendaruids"];
776   if ([list length] == 0)
777     list = [self shortUserNameForDisplay];
778
779   rawContacts
780     = [[list componentsSeparatedByString: @","] objectEnumerator];
781   currentId = [rawContacts nextObject];
782   while (currentId)
783     {
784       if ([currentId hasPrefix: @"-"])
785         [calendars addObject: [currentId substringFromIndex: 1]];
786       else
787         [calendars addObject: currentId];
788       currentId = [rawContacts nextObject];
789     }
790
791   return calendars;
792 }
793
794 - (NSString *) componentOwner
795 {
796   return componentOwner;
797 }
798
799 - (NSString *) urlButtonClasses
800 {
801   NSString *classes;
802
803   if ([url length])
804     classes = @"button";
805   else
806     classes = @"button _disabled";
807
808   return classes;
809 }
810
811 - (NSString *) _toolbarForCalObject: (iCalEntityObject *) calObject
812 {
813   NSString *filename, *myEmail;
814   iCalPerson *person;
815   NSEnumerator *persons;
816   iCalPersonPartStat myParticipationStatus;
817   BOOL found;
818
819   myEmail = [[[self context] activeUser] email];
820   if ([[organizer rfc822Email] isEqualToString: myEmail])
821     filename = @"SOGoAppointmentObject.toolbar";
822   else
823     {
824       filename = @"";
825       found = NO;
826       persons = [participants objectEnumerator];
827       person = [persons nextObject];
828       while (person && !found)
829         if ([[person rfc822Email] isEqualToString: myEmail])
830           {
831             found = YES;
832             myParticipationStatus = [person participationStatus];
833             if (myParticipationStatus == iCalPersonPartStatAccepted)
834               filename = @"SOGoAppointmentObjectDecline.toolbar";
835             else if (myParticipationStatus == iCalPersonPartStatDeclined)
836               filename = @"SOGoAppointmentObjectAccept.toolbar";
837             else
838               filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
839           }
840         else
841           person = [persons nextObject];
842     }
843
844   return filename;
845 }
846
847 - (NSString *) toolbar
848 {
849   NSString *filename;
850   iCalEntityObject *calObject;
851   SOGoCalendarComponent *co;
852
853   if (componentLoaded)
854     {
855       co = [self clientObject];
856       calObject = [co component];
857       filename = [self _toolbarForCalObject: calObject];
858     }
859   else
860     filename = @"";
861
862   return filename;
863 }
864
865 @end