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