]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Scheduler/UIxAppointmentEditor.m
general enhancements
[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, SOGoAppointment, iCalPerson;
28
29 @interface UIxAppointmentEditor : UIxComponent
30 {
31   id appointment;
32   id participants;
33   id item;
34 }
35
36 - (SOGoAppointment *)appointment;
37 - (NSString *)iCalStringTemplate;
38 - (NSString *)iCalString;
39
40 - (NSString *)emailForUser;
41 - (NSString *)cnForUser;
42
43 - (NSString *)_completeURIForMethod:(NSString *)_method;
44
45 - (iCalPerson *)getOrganizer;
46 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
47                           treatAsResource:(BOOL)_isResource;
48 @end
49
50 #include "common.h"
51 #include <SOGoUI/SOGoDateFormatter.h>
52 #include <SOGoLogic/SOGoAppointment.h>
53 #include <Appointments/SOGoAppointmentFolder.h>
54 #include <Appointments/SOGoAppointmentObject.h>
55 #include <NGiCal/NGiCal.h>
56 #include <SOGoLogic/AgenorUserManager.h>
57 #include "iCalPerson+UIx.h"
58
59 @interface NSDate(UsedPrivates)
60 - (NSString *)icalString; // TODO: this is in NGiCal
61 @end
62
63 @interface NSObject (AppointmentHack)
64 - (BOOL)isAppointment;
65 @end
66
67 @implementation NSObject (AppointmentHack)
68 - (BOOL)isAppointment {
69   return [self isKindOfClass:NSClassFromString(@"SOGoAppointmentObject")];
70 }
71 @end
72
73 @implementation UIxAppointmentEditor
74
75 - (void)dealloc {
76   [self->appointment  release];
77   [self->participants release];
78   [self->item release];
79   [super dealloc];
80 }
81
82 /* accessors */
83
84 - (void)setItem:(id)_item {
85     ASSIGN(self->item, _item);
86 }
87 - (id)item {
88     return self->item;
89 }
90
91 - (NSString *)formattedAptStartTime {
92   NSCalendarDate    *date;
93   SOGoDateFormatter *fmt;
94   NSString *s;
95
96   date = [[[[self appointment] startDate] copy] autorelease];
97   [date setTimeZone:[self viewTimeZone]];
98   fmt = [[SOGoDateFormatter alloc] initWithLocale:[self locale]];
99   [fmt setFullWeekdayNameAndDetails];
100   s = [fmt stringForObjectValue:date];
101   [fmt release];
102   return s;
103 }
104
105 - (BOOL)isNewAppointment {
106   /* that doesn't work! */
107   return ![[self clientObject] isAppointment];
108 }
109
110 - (NSString *)iCalString {
111   // TODO: improve check for new appointment
112   NSString *ical;
113   
114   ical = [[self clientObject] valueForKey:@"iCalString"];
115   if ([ical length] == 0) /* a new appointment */
116     ical = [self iCalStringTemplate];
117   
118   return ical;
119 }
120
121 - (NSString *)iCalStringTemplate {
122   static NSString *iCalStringTemplate = \
123     @"BEGIN:VCALENDAR\nMETHOD:REQUEST\nPRODID:OpenGroupware.org SOGo 0.9\n"
124     @"VERSION:2.0\n"
125     @"BEGIN:VEVENT\n"
126     @"UID:%@\n"
127     @"CLASS:PRIVATE\n"
128     @"STATUS:CONFIRMED\n"
129     @"DTSTAMP:%@\n"
130     @"DTSTART:%@\n"
131     @"DTEND:%@\n"
132     @"TRANSP:OPAQUE\n"
133     @"SEQUENCE:1\n"
134     @"END:VEVENT\n"
135     @"END:VCALENDAR";
136   NSCalendarDate *startDate, *endDate;
137   NSString       *template;
138   
139   startDate = [self selectedDate];
140   endDate   = [startDate dateByAddingYears:0 months:0 days:0
141                          hours:1 minutes:0 seconds:0];
142   
143   template = [NSString stringWithFormat:iCalStringTemplate,
144                          [[self clientObject] nameInContainer],
145                          [[NSCalendarDate date] icalString],
146                          [startDate icalString],
147                          [endDate   icalString]];
148   return template;
149 }
150
151
152 /* backend */
153
154 - (SOGoAppointment *)appointment {
155   if (self->appointment == nil) {
156     self->appointment = [[SOGoAppointment alloc]
157                           initWithICalString:[self iCalString]];
158   }
159   return self->appointment;
160 }
161
162 /* JavaScript */
163
164 - (NSString *)jsCode {
165     static NSString *script = \
166     @"function addToTable(tableId, type, cn, dn, email, uid, sn) {\n"
167     @"  var test = document.getElementById(email);"
168     @"  if(test)"
169     @"    return;"
170     @""
171     @"  var table = document.getElementById(tableId);"
172     @"  var tr = document.createElement('tr');"
173     @"  var td, checkbox, text;"
174     @""
175     @"  td = document.createElement('td');"
176     @"  checkbox = document.createElement('input');"
177     @"  checkbox.setAttribute('type', 'checkbox');"
178     @"  checkbox.setAttribute('checked', 'checked');"
179     @"  checkbox.setAttribute('value', email + ';' + cn);"
180     @"  checkbox.setAttribute('id', email);"
181     @"  checkbox.setAttribute('name', tableId);"
182     @"  td.appendChild(checkbox);"
183     @"  tr.appendChild(td);"
184     @"  td = document.createElement('td');"
185     @"  text = document.createTextNode(cn);"
186     @"  td.appendChild(text);"
187     @"  tr.appendChild(td);"
188     @"  table.appendChild(tr);"
189     @"}\n"
190     @"function addParticipant(type, cn, dn, email, uid, sn) {\n"
191     @"  addToTable('participants', type, cn, dn, email, uid, sn);\n"
192     @"}\n"
193     @"function addResource(type, cn, dn, email, uid, sn) {\n"
194     @"  addToTable('resources', type, cn, dn, email, uid, sn);\n"
195     @"}\n"
196     @"";
197     return script;
198 }
199
200 /* helper */
201
202 - (NSString *)_completeURIForMethod:(NSString *)_method {
203     NSString *uri;
204     NSRange r;
205     
206     uri = [[[self context] request] uri];
207     
208     /* first: identify query parameters */
209     r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
210     if(r.length > 0)
211         uri = [uri substringToIndex:r.location];
212     
213     /* next: append trailing slash */
214     if(![uri hasSuffix:@"/"])
215         uri = [uri stringByAppendingString:@"/"];
216     
217     /* next: append method */
218     uri = [uri stringByAppendingString:_method];
219     
220     /* next: append query parameters */
221     return [self completeHrefForMethod:uri];
222 }
223
224
225 /* email, cn */
226
227 - (NSString *)emailForUser {
228     NSString *uid;
229
230     uid = [[self user] login];
231     return [[AgenorUserManager sharedUserManager] getEmailForUID:uid];
232 }
233
234 - (NSString *)cnForUser {
235     NSString *uid;
236     
237     uid = [[self user] login];
238     return [[AgenorUserManager sharedUserManager] getCNForUID:uid];
239 }
240
241 - (NSString *)combinedCNAndEmailForUser {
242     return [NSString stringWithFormat:@"%@;%@",
243         [self emailForUser],
244         [self cnForUser]];
245 }
246
247 - (NSString *)combinedCNAndEmail {
248     return [NSString stringWithFormat:@"%@;%@",
249         [self->item rfc822Email],
250         [self->item cnForDisplay]];
251 }
252
253
254 /* new */
255
256 - (id)newAction {
257   /*
258     This method creates a unique ID and redirects to the "edit" method on the
259     new ID.
260     It is actually a folder method and should be defined on the folder.
261     
262     Note: 'clientObject' is the SOGoAppointmentFolder!
263   */
264   NSString *uri, *objectId, *nextMethod;
265   
266   objectId = [NSClassFromString(@"SOGoAppointmentFolder")
267                                globallyUniqueObjectId];
268   
269   nextMethod = [NSString stringWithFormat:@"../%@/edit", objectId];
270   uri = [self _completeURIForMethod:nextMethod];
271   return [self redirectToLocation:uri];
272 }
273
274 /* save */
275
276 /* returned dates are in GMT */
277 - (NSCalendarDate *)_dateFromString:(NSString *)_str {
278   NSCalendarDate *date;
279   
280   date = [NSCalendarDate dateWithString:_str 
281                          calendarFormat:@"%Y-%m-%d %H:%M %Z"];
282   [date setTimeZone:[self backendTimeZone]];
283   return date;
284 }
285
286 - (iCalPerson *)getOrganizer {
287     iCalPerson *p;
288     NSString *emailProp;
289
290     emailProp = [NSString stringWithFormat:@"mailto:%@",
291         [self emailForUser]];
292     p = [[iCalPerson alloc] init];
293     [p setEmail:emailProp];
294     [p setCn:[self cnForUser]];
295     return [p autorelease];
296 }
297
298 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
299                           treatAsResource:(BOOL)_isResource
300 {
301     unsigned i, count;
302     NSMutableArray *result;
303
304     count = [_values count];
305     result = [[NSMutableArray alloc] initWithCapacity:count];
306     for (i = 0; i < count; i++) {
307         NSString   *pString, *email, *cn;
308         NSRange r;
309         iCalPerson *p;
310         
311         pString = [_values objectAtIndex:i];
312         if([pString length] == 0)
313             continue;
314         
315         /* delimiter between email and cn */
316         r = [pString rangeOfString:@";"];
317         if(r.length > 0) {
318             email = [pString substringToIndex:r.location];
319             if(r.location + 1 < [pString length]) {
320                 cn = [pString substringFromIndex:r.location + 1];
321             }
322             else {
323                 cn = nil;
324             }
325         }
326         else {
327             email = pString;
328             cn = nil;
329         }
330         if(cn == nil) {
331             /* fallback */
332             AgenorUserManager *um = [AgenorUserManager sharedUserManager];
333             cn = [um getCNForUID:[um getUIDForEmail:email]];
334         }
335         p = [[iCalPerson alloc] init];
336         [p setEmail:[@"mailto:" stringByAppendingString:email]];
337         if(cn)
338             [p setCn:cn];
339
340         /* see RFC2445, sect. 4.2.16 for details */
341         if(_isResource) {
342             [p setRole:@"X-OGo-RESOURCE"];
343         }
344         else {
345             [p setRole:@"REQ-PARTICIPANT"];
346         }
347         [result addObject:p];
348         [p release];
349     }
350     return [result autorelease];
351 }
352
353 /* for testing only */
354 - (id)testAction {
355     WORequest      *req;
356     
357     NSLog(@"%s BEEN HERE!",
358           __PRETTY_FUNCTION__);
359
360     req = [[self context] request];
361     NSLog(@"%@", [req formValues]);
362     return self;
363 }
364
365 - (id)saveAction {
366   SOGoAppointment *apt;
367   NSString       *iCalString;
368   NSString       *summary, *location, *uri;
369   NSCalendarDate *sd, *ed;
370   NSArray        *attendees;
371   WORequest      *req;
372
373   
374   if (![[self clientObject] respondsToSelector:@selector(saveContentString:)]){
375     /* return 400 == Bad Request */
376     return [NSException exceptionWithHTTPStatus:400
377                         reason:@"method cannot be invoked on "
378                                @"the specified object"];
379   }
380   
381   req = [[self context] request];
382
383   /* get iCalString from hidden input */
384   iCalString = [req formValueForKey:@"ical"];
385   if ([iCalString length] == 0) {
386     // TODO: improve user experience ... (eg error panel like Zope)
387     /* return 400 == Bad Request */
388     return [NSException exceptionWithHTTPStatus:400
389                         reason:@"missing iCalendar form data in request"];
390   }
391   
392   apt = [[SOGoAppointment alloc] initWithICalString:iCalString];
393   
394   /* merge in form values */
395   
396   sd = [self _dateFromString:[req formValueForKey:@"startDate"]];
397   ed = [self _dateFromString:[req formValueForKey:@"endDate"]];
398   [apt setStartDate:sd];
399   [apt setEndDate:ed];
400   
401   summary = [req formValueForKey:@"summary"];
402   [apt setSummary:summary];
403   location = [req formValueForKey:@"location"];
404   [apt setLocation:location];
405
406   [apt removeAllAttendees]; /* clean up */
407   attendees = \
408       [self getICalPersonsFromFormValues:[req formValuesForKey:@"participants"]
409             treatAsResource:NO];
410   [apt appendAttendees:attendees];
411   attendees = \
412       [self getICalPersonsFromFormValues:[req formValuesForKey:@"resources"]
413                          treatAsResource:YES];
414   [apt appendAttendees:attendees];
415   [apt setOrganizer:[self getOrganizer]];
416
417   /* receive current representation for save operation */
418   iCalString = [apt iCalString];
419   [apt release]; apt = nil;
420   
421   {
422     NSException *ex;
423     
424     ex = [[self clientObject] saveContentString:iCalString];
425     if (ex) return ex;
426     // TODO: add some error handling in form! (eg like in Zope)
427   }
428   
429   uri = [self _completeURIForMethod:@"view"];
430   return [self redirectToLocation:uri];
431 }
432
433 @end /* UIxAppointmentEditor */