]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Scheduler/UIxAppointmentEditor.m
Schedule view and associated functionality, work in progress
[scalable-opengroupware.org] / SOGo / UI / Scheduler / UIxAppointmentEditor.m
1 /*
2   Copyright (C) 2004 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 // $Id$
22
23 #include <SOGoUI/UIxComponent.h>
24
25 /* TODO: CLEAN UP */
26
27 @class NSString;
28 @class iCalPerson;
29 @class SOGoAppointment;
30
31 @interface UIxAppointmentEditor : UIxComponent
32 {
33   NSString *iCalString;
34   NSString *errorText;
35   id item;
36   
37   /* individual values */
38   NSCalendarDate *startDate;
39   NSCalendarDate *endDate;
40   NSString       *title;
41   NSString       *location;
42   NSString       *comment;
43   NSArray        *participants; /* array of iCalPerson's */
44   NSArray        *resources;    /* array of iCalPerson's */
45   NSString       *priority;
46   NSArray        *categories;
47   NSString       *accessClass;
48   BOOL           isPrivate;     /* default: NO */
49 }
50
51 - (NSString *)iCalStringTemplate;
52 - (NSString *)iCalString;
53
54 - (void)setIsPrivate:(BOOL)_yn;
55 - (void)setAccessClass:(NSString *)_class;
56
57 - (NSString *)_completeURIForMethod:(NSString *)_method;
58
59 - (iCalPerson *)getOrganizer;
60 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
61   treatAsResource:(BOOL)_isResource;
62
63 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters;
64 - (NSString *)iCalParticipantsStringFromQueryParameters;
65 - (NSString *)iCalResourcesStringFromQueryParameters;
66 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
67               format:(NSString *)_format;
68 @end
69
70 #include "common.h"
71 #include <SOGoUI/SOGoDateFormatter.h>
72 #include <SOGoLogic/SOGoAppointment.h>
73 #include <Appointments/SOGoAppointmentFolder.h>
74 #include <Appointments/SOGoAppointmentObject.h>
75 #include <NGiCal/NGiCal.h>
76 #include <SOGoLogic/AgenorUserManager.h>
77 #include "iCalPerson+UIx.h"
78 #include "UIxComponent+Agenor.h"
79
80 @interface NSDate(UsedPrivates)
81 - (NSString *)icalString; // TODO: this is in NGiCal
82 @end
83
84 @implementation UIxAppointmentEditor
85
86 - (id)init {
87   self = [super init];
88   if(self) {
89     [self setIsPrivate:NO];
90   }
91   return self;
92 }
93
94 - (void)dealloc {
95   [self->iCalString   release];
96   [self->errorText    release];
97   [self->item         release];
98
99   [self->startDate    release];
100   [self->endDate      release];
101   [self->title        release];
102   [self->location     release];
103   [self->comment      release];
104   [self->participants release];
105   [self->resources    release];
106   [self->priority     release];
107   [self->categories   release];
108   [self->accessClass   release];
109   [super dealloc];
110 }
111
112 /* accessors */
113
114 - (void)setItem:(id)_item {
115   ASSIGN(self->item, _item);
116 }
117 - (id)item {
118   return self->item;
119 }
120
121 - (void)setErrorText:(NSString *)_txt {
122   ASSIGNCOPY(self->errorText, _txt);
123 }
124 - (NSString *)errorText {
125   return self->errorText;
126 }
127 - (BOOL)hasErrorText {
128   return [self->errorText length] > 0 ? YES : NO;
129 }
130
131 - (NSFormatter *)titleDateFormatter {
132   SOGoDateFormatter *fmt;
133   
134   fmt = [[[SOGoDateFormatter alloc] initWithLocale:[self locale]] autorelease];
135   [fmt setFullWeekdayNameAndDetails];
136   return fmt;
137 }
138
139 - (void)setAptStartDate:(NSCalendarDate *)_date {
140   ASSIGN(self->startDate, _date);
141 }
142 - (NSCalendarDate *)aptStartDate {
143   return self->startDate;
144 }
145 - (void)setAptEndDate:(NSCalendarDate *)_date {
146   ASSIGN(self->endDate, _date);
147 }
148 - (NSCalendarDate *)aptEndDate {
149   return self->endDate;
150 }
151
152 - (void)setTitle:(NSString *)_value {
153   ASSIGNCOPY(self->title, _value);
154 }
155 - (NSString *)title {
156   return self->title;
157 }
158 - (void)setLocation:(NSString *)_value {
159   ASSIGNCOPY(self->location, _value);
160 }
161 - (NSString *)location {
162   return self->location;
163 }
164 - (void)setComment:(NSString *)_value {
165   ASSIGNCOPY(self->comment, _value);
166 }
167 - (NSString *)comment {
168   return self->comment;
169 }
170
171 - (void)setParticipants:(NSArray *)_parts {
172   ASSIGN(self->participants, _parts);
173 }
174 - (NSArray *)participants {
175   return self->participants;
176 }
177 - (void)setResources:(NSArray *)_res {
178   ASSIGN(self->resources, _res);
179 }
180 - (NSArray *)resources {
181   return self->resources;
182 }
183
184 /* priorities */
185
186 - (NSArray *)priorities {
187   /* 0 == undefined
188      5 == normal
189      1 == high
190   */
191   static NSArray *priorities = nil;
192
193   if (!priorities)
194     priorities = [[NSArray arrayWithObjects:@"0", @"5", @"1", nil] retain];
195   return priorities;
196 }
197
198 - (NSString *)itemPriorityText {
199   NSString *key;
200   
201   key = [NSString stringWithFormat:@"prio_%@", self->item];
202   return [self labelForKey:key];
203 }
204
205 - (void)setPriority:(NSString *)_priority {
206   ASSIGN(self->priority, _priority);
207 }
208 - (NSString *)priority {
209   return self->priority;
210 }
211
212
213 /* categories */
214
215 - (NSArray *)categoryItems {
216   // TODO: make this configurable?
217   /*
218    Tasks categories will be modified as follow :
219    â€“ by default (a simple logo or no logo at all),
220    â€“ appointment,
221    â€“ outside,
222    â€“ meeting,
223    â€“ holidays,
224    â€“ phone.
225   */
226   static NSArray *categoryItems = nil;
227   
228   if (!categoryItems) {
229     categoryItems = [[NSArray arrayWithObjects:@"APPOINTMENT",
230                                                @"NOT IN OFFICE",
231                                                @"MEETING",
232                                                @"HOLIDAY",
233                                                @"PHONE CALL",
234                                                nil] retain];
235   }
236   return categoryItems;
237 }
238
239 - (NSString *)itemCategoryText {
240   return [self labelForKey:self->item];
241 }
242
243 - (void)setCategories:(NSArray *)_categories {
244   ASSIGN(self->categories, _categories);
245 }
246 - (NSArray *)categories {
247   return self->categories;
248 }
249
250 /* class */
251
252 #if 0
253 - (NSArray *)accessClassItems {
254   static NSArray classItems = nil;
255   
256   if (!classItems) {
257     return [[NSArray arrayWithObjects:@"PUBLIC", @"PRIVATE", nil] retain];
258   }
259   return classItems;
260 }
261 #endif
262
263 - (void)setAccessClass:(NSString *)_class {
264   ASSIGN(self->accessClass, _class);
265 }
266 - (NSString *)accessClass {
267   return self->accessClass;
268 }
269
270 - (void)setIsPrivate:(BOOL)_yn {
271   if (_yn)
272     [self setAccessClass:@"PRIVATE"];
273   else
274     [self setAccessClass:@"PUBLIC"];
275   self->isPrivate = _yn;
276 }
277
278 - (BOOL)isPrivate {
279   return self->isPrivate;
280 }
281
282
283 /* transparency */
284
285 - (NSString *)transparency {
286   return @"TRANSPARENT";
287 }
288
289
290 /* iCal */
291
292 - (void)setICalString:(NSString *)_s {
293   ASSIGNCOPY(self->iCalString, _s);
294 }
295 - (NSString *)iCalString {
296   return self->iCalString;
297 }
298
299 - (NSString *)iCalStringTemplate {
300   static NSString *iCalStringTemplate = \
301     @"BEGIN:VCALENDAR\r\n"
302     @"METHOD:REQUEST\r\n"
303     @"PRODID:OpenGroupware.org SOGo 0.9\r\n"
304     @"VERSION:2.0\r\n"
305     @"BEGIN:VEVENT\r\n"
306     @"UID:%@\r\n"
307     @"CLASS:PUBLIC\r\n"
308     @"STATUS:CONFIRMED\r\n"
309     @"DTSTAMP:%@\r\n"
310     @"DTSTART:%@\r\n"
311     @"DTEND:%@\r\n"
312     @"TRANSP:%@\r\n"
313     @"SEQUENCE:1\r\n"
314     @"PRIORITY:5\r\n"
315     @"%@"
316     @"END:VEVENT\r\n"
317     @"END:VCALENDAR";
318
319   NSCalendarDate *lStartDate, *lEndDate;
320   NSString       *template, *s;
321   unsigned       minutes;
322
323   s = [self queryParameterForKey:@"dur"];
324   if(s && [s length] > 0) {
325     minutes = [s intValue];
326   }
327   else {
328     minutes = 60;
329   }
330   lStartDate = [self selectedDate];
331   lEndDate   = [lStartDate dateByAddingYears:0 months:0 days:0
332                            hours:0 minutes:minutes seconds:0];
333   
334   s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
335   template = [NSString stringWithFormat:iCalStringTemplate,
336                          [[self clientObject] nameInContainer],
337                          [[NSCalendarDate date] icalString],
338                          [lStartDate icalString],
339              [lEndDate icalString],
340              [self transparency],
341              s];
342   return template;
343 }
344
345 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters {
346   NSString *s;
347   
348   s = [self iCalParticipantsStringFromQueryParameters];
349   return [s stringByAppendingString:
350             [self iCalResourcesStringFromQueryParameters]];
351 }
352
353 - (NSString *)iCalParticipantsStringFromQueryParameters {
354   static NSString *iCalParticipantString = \
355     @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
356   
357   return [self iCalStringFromQueryParameter:@"ps"
358                format:iCalParticipantString];
359 }
360
361 - (NSString *)iCalResourcesStringFromQueryParameters {
362   static NSString *iCalResourceString = \
363     @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
364
365   return [self iCalStringFromQueryParameter:@"rs"
366                format:iCalResourceString];
367 }
368
369 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
370               format:(NSString *)_format
371 {
372   AgenorUserManager *um;
373   NSMutableString *iCalRep;
374   NSString *s;
375
376   um = [AgenorUserManager sharedUserManager];
377   iCalRep = (NSMutableString *)[NSMutableString string];
378   s = [self queryParameterForKey:_qp];
379   if(s && [s length] > 0) {
380     NSArray *es;
381     unsigned i, count;
382     
383     es = [s componentsSeparatedByString:@","];
384     count = [es count];
385     for(i = 0; i < count; i++) {
386       NSString *email, *cn;
387       
388       email = [es objectAtIndex:i];
389       cn = [um getCNForUID:[um getUIDForEmail:email]];
390       [iCalRep appendFormat:_format, cn, email];
391     }
392   }
393   return iCalRep;
394 }
395
396 /* helper */
397
398 - (NSString *)_completeURIForMethod:(NSString *)_method {
399   NSString *uri;
400   NSRange r;
401     
402   uri = [[[self context] request] uri];
403     
404   /* first: identify query parameters */
405   r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
406   if (r.length > 0)
407     uri = [uri substringToIndex:r.location];
408     
409   /* next: append trailing slash */
410   if (![uri hasSuffix:@"/"])
411     uri = [uri stringByAppendingString:@"/"];
412   
413   /* next: append method */
414   uri = [uri stringByAppendingString:_method];
415     
416   /* next: append query parameters */
417   return [self completeHrefForMethod:uri];
418 }
419
420 /* new */
421
422 - (id)newAction {
423   /*
424     This method creates a unique ID and redirects to the "edit" method on the
425     new ID.
426     It is actually a folder method and should be defined on the folder.
427     
428     Note: 'clientObject' is the SOGoAppointmentFolder!
429           Update: remember that there are group folders as well.
430   */
431   NSString *uri, *objectId, *method;
432
433   objectId = [NSClassFromString(@"SOGoAppointmentFolder")
434                                globallyUniqueObjectId];
435   if ([objectId length] == 0) {
436     return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
437                         reason:@"could not create a unique ID"];
438   }
439
440   method = [NSString stringWithFormat:@"Calendar/%@/edit", objectId];
441   method = [[self userFolderPath] stringByAppendingPathComponent:method];
442
443   /* add all current calendarUIDs as default participants */
444   if ([[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
445     AgenorUserManager *um;
446     NSArray           *uids;
447     NSMutableArray    *emails;
448     NSString          *ps;
449     unsigned          i, count;
450
451     um     = [AgenorUserManager sharedUserManager];
452     uids   = [[self clientObject] calendarUIDs];
453     count  = [uids count];
454     emails = [NSMutableArray arrayWithCapacity:count];
455     
456     for (i = 0; i < count; i++) {
457       NSString *email;
458       
459       email = [um getEmailForUID:[uids objectAtIndex:i]];
460       if (email)
461         [emails addObject:email];
462     }
463     ps = [emails componentsJoinedByString:@","];
464     [self setQueryParameter:ps forKey:@"ps"];
465   }
466   uri = [self completeHrefForMethod:method];
467   return [self redirectToLocation:uri];
468 }
469
470 /* save */
471
472 /* returned dates are in GMT */
473 - (NSCalendarDate *)_dateFromString:(NSString *)_str {
474   NSCalendarDate *date;
475   
476   date = [NSCalendarDate dateWithString:_str 
477                          calendarFormat:@"%Y-%m-%d %H:%M %Z"];
478   [date setTimeZone:[self backendTimeZone]];
479   return date;
480 }
481
482 - (iCalPerson *)getOrganizer {
483   iCalPerson *p;
484   NSString   *emailProp;
485   
486   emailProp = [@"mailto:" stringByAppendingString:[self emailForUser]];
487   p = [[[iCalPerson alloc] init] autorelease];
488   [p setEmail:emailProp];
489   [p setCn:[self cnForUser]];
490   return p;
491 }
492
493 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
494   treatAsResource:(BOOL)_isResource
495 {
496   unsigned i, count;
497   NSMutableArray *result;
498
499   count = [_values count];
500   result = [[NSMutableArray alloc] initWithCapacity:count];
501   for (i = 0; i < count; i++) {
502     NSString   *pString, *email, *cn;
503     NSRange    r;
504     iCalPerson *p;
505     
506     pString = [_values objectAtIndex:i];
507     if ([pString length] == 0)
508       continue;
509     
510     /* delimiter between email and cn */
511     r = [pString rangeOfString:@";"];
512     if (r.length > 0) {
513       email = [pString substringToIndex:r.location];
514       cn = (r.location + 1 < [pString length])
515         ? [pString substringFromIndex:r.location + 1]
516         : nil;
517     }
518     else {
519       email = pString;
520       cn    = nil;
521     }
522     if (cn == nil) {
523       /* fallback */
524       AgenorUserManager *um = [AgenorUserManager sharedUserManager];
525       cn = [um getCNForUID:[um getUIDForEmail:email]];
526     }
527     
528     p = [[iCalPerson alloc] init];
529     [p setEmail:[@"mailto:" stringByAppendingString:email]];
530     if ([cn isNotNull]) [p setCn:cn];
531     
532     /* see RFC2445, sect. 4.2.16 for details */
533     [p setRole:_isResource ? @"NON-PARTICIPANT" : @"REQ-PARTICIPANT"];
534     [result addObject:p];
535     [p release];
536   }
537   return [result autorelease];
538 }
539
540 - (BOOL)isWriteableClientObject {
541   return [[self clientObject] 
542                 respondsToSelector:@selector(saveContentString:)];
543 }
544
545 - (void)loadValuesFromAppointment:(SOGoAppointment *)_appointment {
546   NSString *s;
547
548   if ((self->startDate = [[_appointment startDate] copy]) == nil)
549     self->startDate = [[[NSCalendarDate date] hour:11 minute:0] copy];
550   if ((self->endDate = [[_appointment endDate] copy]) == nil) {
551     self->endDate =
552       [[self->startDate hour:[self->startDate hourOfDay] + 1 minute:0] copy];
553   }
554   [self->startDate setTimeZone:[self viewTimeZone]];
555   [self->endDate   setTimeZone:[self viewTimeZone]];
556   
557   self->title        = [[_appointment summary]  copy];
558   self->location     = [[_appointment location] copy];
559   self->comment      = [[_appointment comment]  copy];
560   self->priority     = [[_appointment priority] copy];
561   self->categories   = [[_appointment categories]   retain];
562   self->participants = [[_appointment participants] retain];
563   self->resources    = [[_appointment resources]    retain];
564
565   s                  = [_appointment accessClass];
566   if(!s || [s isEqualToString:@"PUBLIC"])
567     [self setIsPrivate:NO];
568   else
569     [self setIsPrivate:YES]; /* we're possibly loosing information here */
570 }
571
572 - (void)saveValuesIntoAppointment:(SOGoAppointment *)_appointment {
573   /* merge in form values */
574   NSArray *attendees, *lResources;
575   
576   [_appointment setStartDate:[self aptStartDate]];
577   [_appointment setEndDate:[self aptEndDate]];
578   
579   [_appointment setSummary:[self title]];
580   [_appointment setLocation:[self location]];
581   [_appointment setComment:[self comment]];
582   [_appointment setPriority:[self priority]];
583   [_appointment setCategories:[self categories]];
584
585   [_appointment setAccessClass:[self accessClass]];
586   [_appointment setTransparency:[self transparency]];
587
588   attendees  = [self participants];
589   lResources = [self resources];
590   if ([lResources count] > 0) {
591     attendees = ([attendees count] > 0)
592       ? [attendees arrayByAddingObjectsFromArray:lResources]
593       : lResources;
594   }
595   [_appointment setAttendees:attendees];
596   
597   [_appointment setOrganizer:[self getOrganizer]];
598 }
599
600 - (void)loadValuesFromICalString:(NSString *)_ical {
601   SOGoAppointment *apt;
602
603   apt = [[SOGoAppointment alloc] initWithICalString:_ical];
604   [self loadValuesFromAppointment:apt];
605   [apt release];
606 }
607
608 /* contact editor compatibility */
609
610 - (void)setContentString:(NSString *)_s {
611   [self setICalString:_s];
612 }
613 - (NSString *)contentStringTemplate {
614   return [self iCalStringTemplate];
615 }
616
617 - (void)loadValuesFromContentString:(NSString *)_s {
618   [self loadValuesFromICalString:_s];
619 }
620
621
622 /* access */
623
624 - (BOOL)isMyApt {
625   NSString *owner;
626
627   owner = [[self clientObject] ownerInContext:[self context]];
628   if (!owner)
629     return YES;
630   return [owner isEqualToString:[[self user] login]];
631 }
632
633 - (BOOL)canAccessApt {
634   return [self isMyApt];
635 }
636
637 - (BOOL)canEditApt {
638   return [self isMyApt];
639 }
640
641
642 /* actions */
643
644 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
645   return YES;
646 }
647
648 - (id)testAction {
649   /* for testing only */
650   WORequest       *req;
651   SOGoAppointment *apt;
652   NSString        *content;
653
654   req = [[self context] request];
655   apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
656   [self saveValuesIntoAppointment:apt];
657   content = [apt iCalString];
658   [self logWithFormat:@"%s -- iCal:\n%@",
659     __PRETTY_FUNCTION__,
660     content];
661   [apt release];
662   return self;
663 }
664
665 - (id)defaultAction {
666   NSString *ical;
667   
668   /* load iCalendar file */
669   
670   // TODO: can't we use [clientObject contentAsString]?
671   ical = [[self clientObject] valueForKey:@"iCalString"];
672   if ([ical length] == 0) /* a new appointment */
673     ical = [self contentStringTemplate];
674   
675   [self setContentString:ical];
676   [self loadValuesFromContentString:ical];
677   
678   return self;
679 }
680
681 - (id)saveAction {
682   SOGoAppointment *apt;
683   NSException     *ex;
684   NSString        *content;
685   
686   if (![self isWriteableClientObject]) {
687     /* return 400 == Bad Request */
688     return [NSException exceptionWithHTTPStatus:400
689                         reason:@"method cannot be invoked on "
690                                @"the specified object"];
691   }
692   
693   apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
694   if (apt == nil) {
695     [self setErrorText:@"Invalid iCalendar data ..."]; // localize
696     return self;
697   }
698   
699   [self saveValuesIntoAppointment:apt];
700   content = [apt iCalString];
701   [apt release]; apt = nil;
702   
703   if (content == nil) {
704     [self setErrorText:@"Could not create iCalendar data ..."]; // localize
705     return self;
706   }
707   
708   ex = [[self clientObject] saveContentString:content];
709   if (ex != nil) {
710     [self setErrorText:[ex reason]];
711     return self;
712   }
713   
714   return [self redirectToLocation:[self _completeURIForMethod:@".."]];
715 }
716
717 @end /* UIxAppointmentEditor */