]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Scheduler/UIxAppointmentEditor.m
Added "AnaisUI" bundle, new formatter for SOGoUI, various enhancements
[scalable-opengroupware.org] / SOGo / UI / Scheduler / UIxAppointmentEditor.m
1 /*
2   Copyright (C) 2000-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
24 #include "common.h"
25 #include <SOGoUI/UIxComponent.h>
26 #include <SOGoUI/SOGoDateFormatter.h>
27 #include <SOGoLogic/SOGoAppointment.h>
28 #include <NGiCal/NGiCal.h>
29
30
31 /* TODO: CLEAN THIS MESS UP */
32
33 @interface NSDate(UsedPrivates)
34 - (NSString *)icalString; // TODO: this is in NGiCal
35 @end
36
37 @interface NSObject (AppointmentHack)
38 - (BOOL)isAppointment;
39 @end
40
41 @implementation NSObject (AppointmentHack)
42 - (BOOL)isAppointment {
43   return [self isKindOfClass:NSClassFromString(@"SOGoAppointmentObject")];
44 }
45 @end
46
47 @interface iCalPerson (Convenience)
48 - (NSString *)rfc822Email;
49 @end
50
51 @implementation iCalPerson (Convenience)
52 - (NSString *)rfc822Email {
53     NSString *_email = [self email];
54     NSRange colon = [_email rangeOfString:@":"];
55     if(colon.location != NSNotFound) {
56         return [_email substringFromIndex:colon.location + 1];
57     }
58     return _email;
59 }
60 @end
61
62 @interface UIxAppointmentEditor : UIxComponent
63 {
64     id appointment;
65     id participants;
66 }
67
68 - (SOGoAppointment *)appointment;
69 - (NSString *)iCalStringTemplate;
70 - (NSString *)iCalString;
71
72 @end
73
74 @implementation UIxAppointmentEditor
75
76 - (void)dealloc {
77     [self->appointment release];
78     [self->participants release];
79     [super dealloc];
80 }
81
82
83 /* accessors */
84
85
86 - (NSString *)formattedAptStartTime {
87     NSCalendarDate *date;
88     SOGoDateFormatter *fmt;
89     NSString *s;
90
91     date = [[[[self appointment] startDate] copy] autorelease];
92     [date setTimeZone:[self viewTimeZone]];
93     fmt = [[SOGoDateFormatter alloc] initWithLocale:[self locale]];
94     [fmt setFullWeekdayNameAndDetails];
95     s = [fmt stringForObjectValue:date];
96     [fmt release];
97     return s;
98 }
99
100 - (BOOL)isNewAppointment {
101   /* that doesn't work! */
102   return ![[self clientObject] isAppointment];
103 }
104
105 - (NSString *)iCalString {
106   // TODO: improve check for new appointment
107   NSString *ical;
108
109   ical = [[self clientObject] valueForKey:@"iCalString"];
110   if ([ical length] == 0) /* a new appointment */
111     ical = [self iCalStringTemplate];
112   
113   return ical;
114 }
115
116 - (NSString *)iCalStringTemplate {
117   static NSString *iCalStringTemplate = \
118     @"BEGIN:VCALENDAR\nMETHOD:REQUEST\nPRODID:OpenGroupware.org SOGo 0.9\n"
119     @"VERSION:2.0\n"
120     @"BEGIN:VEVENT\n"
121     @"UID:%@\n"
122     @"CLASS:PRIVATE\n"
123     @"STATUS:CONFIRMED\n"
124     @"DTSTAMP:%@\n"
125     @"DTSTART:%@\n"
126     @"DTEND:%@\n"
127     @"TRANSP:OPAQUE\n"
128     @"SEQUENCE:1\n"
129     @"END:VEVENT\n"
130     @"END:VCALENDAR";
131   NSCalendarDate *startDate, *endDate;
132   NSString       *template;
133   
134   startDate = [self selectedDate];
135   endDate   = [startDate dateByAddingYears:0 months:0 days:0
136                          hours:1 minutes:0 seconds:0];
137   
138   template = [NSString stringWithFormat:iCalStringTemplate,
139                          [[self clientObject] nameInContainer],
140                          [[NSCalendarDate date] icalString],
141                          [startDate icalString],
142                          [endDate   icalString]];
143   return template;
144 }
145
146
147 /* backend */
148
149
150 - (SOGoAppointment *)appointment {
151   if(self->appointment == nil) {
152     self->appointment = [[SOGoAppointment alloc]
153                           initWithICalString:[self iCalString]];
154   }
155   return self->appointment;
156 }
157
158 - (id)participants {
159   NSArray *attendees;
160   NSMutableArray *emailAddresses;
161   unsigned i, count;
162   
163   if (self->participants)
164     return self->participants;
165
166   attendees      = [self->appointment attendees];
167   count          = [attendees count];
168   emailAddresses = [[NSMutableArray alloc] initWithCapacity:count];
169   for (i = 0; i < count; i++) {
170     NSString *email;
171             
172     email = [[attendees objectAtIndex:i] rfc822Email];
173     if (email)
174       [emailAddresses addObject:email];
175   }
176   
177   self->participants = 
178     [[emailAddresses componentsJoinedByString:@"\n"] copy];
179   [emailAddresses release];
180   return self->participants;
181 }
182
183
184 /* helper */
185
186 - (NSString *)uriAsFormat {
187   NSString *uri, *qp;
188   NSRange r;
189
190   uri = [[[self context] request] uri];
191
192   /* first: identify query parameters */
193   r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
194   if (r.length > 0) {
195     qp = [uri substringFromIndex:r.location];
196     uri = [uri substringToIndex:r.location];
197   }
198   else
199     qp = nil;
200     
201   /* next: strip trailing slash */
202   if ([uri hasSuffix:@"/"])
203     uri = [uri substringToIndex:([uri length] - 1)];
204   r = [uri rangeOfString:@"/" options:NSBackwardsSearch];
205     
206   /* then: cut of last path component */
207   if (r.length == 0) // no slash? are we at root?
208     uri = @"/";
209   else
210     uri = [uri substringToIndex:(r.location + 1)];
211
212   /* next: append format token */
213   uri = [uri stringByAppendingString:@"%@"];
214   if(qp != nil)
215     uri = [uri stringByAppendingString:qp];
216   return uri;
217 }
218
219
220 /* new */
221
222
223 - (id)newAction {
224   /*
225     This method creates a unique ID and redirects to the "edit" method on the
226     new ID.
227     It is actually a folder method and should be defined on the folder.
228     
229     Note: 'clientObject' is the SOGoAppointmentFolder!
230   */
231   WORequest *req;
232   WOResponse *r;
233   NSString *uri, *uriFormat, *objectId, *nextMethod;
234   
235   objectId = [NSClassFromString(@"SOGoAppointmentFolder")
236                                globallyUniqueObjectId];
237   
238   nextMethod = [NSString stringWithFormat:@"%@/edit", objectId];
239   uriFormat  = [self uriAsFormat];
240   uri = [[NSString alloc] initWithFormat:uriFormat, nextMethod];
241   req = [[self context] request];
242   r = [WOResponse responseWithRequest:req];
243   [r setStatus:302 /* moved */];
244   [r setHeader:uri forKey:@"location"];
245   [uri release]; uri = nil;
246   return r;
247 }
248
249
250 /* save */
251
252 /* returned dates are in GMT */
253 - (NSCalendarDate *)_dateFromString:(NSString *)_str {
254     NSCalendarDate *date;
255
256     date = [NSCalendarDate dateWithString:_str 
257                            calendarFormat:@"%Y-%m-%d %H:%M %Z"];
258     [date setTimeZone:[self backendTimeZone]];
259     return date;
260 }
261
262 - (id)saveAction {
263   SOGoAppointment *apt;
264   NSString       *iCalString;
265   NSString       *summary, *location, *uri, *uriFormat;
266   NSCalendarDate *sd, *ed;
267   NSArray        *ps;
268   unsigned       i, count;
269   WOResponse     *r;
270   WORequest      *req;
271
272   req = [[self context] request];
273
274   /* get iCalString from hidden input */
275   
276   iCalString = [req formValueForKey:@"ical"];
277   if ([iCalString length] == 0) {
278     // TODO: improve user experience ... (eg error panel like Zope)
279     return [NSException exceptionWithHTTPStatus:400 /* bad request */
280                         reason:@"missing iCalendar form data in request"];
281   }
282   
283   apt = [[SOGoAppointment alloc] initWithICalString:iCalString];
284   
285   /* merge in form values */
286   
287   sd = [self _dateFromString:[req formValueForKey:@"startDate"]];
288   ed = [self _dateFromString:[req formValueForKey:@"endDate"]];
289   [apt setStartDate:sd];
290   [apt setEndDate:ed];
291   
292   summary = [req formValueForKey:@"summary"];
293   [apt setSummary:summary];
294   location = [req formValueForKey:@"location"];
295   [apt setLocation:location];
296
297   [apt removeAllAttendees]; /* clean up */
298   ps = [[req formValueForKey:@"participants"]
299              componentsSeparatedByString:@"\n"];
300   count = [ps count];
301   for (i = 0; i < count; i++) {
302     NSString   *email;
303     iCalPerson *p;
304     NSRange    cnr;
305     
306     email = [ps objectAtIndex:i];
307     if ([email length] == 0)
308       continue;
309     
310     p = [[iCalPerson alloc] init];
311     [p setEmail:[@"mailto:" stringByAppendingString:[email stringValue]]];
312     /* construct a fake CN */
313     cnr = [email rangeOfString:@"@"];
314     if (cnr.length > 0)
315       [p setCn:[email substringToIndex:cnr.location]];
316     [apt addToAttendees:p];
317     [p release];
318   }
319   
320   /* receive current representation for save operation */
321   iCalString = [apt iCalString];
322   [apt release]; apt = nil;
323     
324 #if 0
325   NSLog(@"%s new iCalString:\n%@", __PRETTY_FUNCTION__, iCalString);
326 #else
327   {
328     NSException *ex;
329     
330     ex = [[self clientObject] saveContentString:iCalString];
331     if (ex) return ex;
332     // TODO: add some error handling in form! (eg like in Zope)
333   }
334 #endif
335   
336   uriFormat = [self uriAsFormat];
337   uri = [NSString stringWithFormat:uriFormat, @"view"];
338
339   r = [WOResponse responseWithRequest:req];
340   [r setStatus:302 /* moved */];
341   [r setHeader:uri forKey:@"location"];
342   return r;
343 }
344
345 @end /* UIxAppointmentEditor */