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