]> err.no Git - scalable-opengroupware.org/blob - UI/Scheduler/UIxTaskEditor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1032 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / Scheduler / UIxTaskEditor.m
1 /*
2   Copyright (C) 2004-2005 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
22 #import <SOGo/NSCalendarDate+SOGo.h>
23
24 #import "UIxTaskEditor.h"
25
26 /* TODO: CLEAN UP */
27
28 #import "common.h"
29 #import <NGCards/NGCards.h>
30 #import <NGExtensions/NGCalendarDateRange.h>
31 #import <SOGoUI/SOGoDateFormatter.h>
32 #import <SOGo/AgenorUserManager.h>
33 #import <Appointments/SOGoAppointmentFolder.h>
34 #import <Appointments/SOGoTaskObject.h>
35 #import "UIxComponent+Agenor.h"
36
37 @implementation UIxTaskEditor
38
39 - (void) dealloc
40 {
41   [dueDate release];
42   [super dealloc];
43 }
44
45 - (void) setTaskStartDate: (NSCalendarDate *) _date
46 {
47   [self setStartDate: _date];
48 }
49
50 - (NSCalendarDate *) taskStartDate
51 {
52   return [self startDate];
53 }
54
55 - (void) setTaskDueDate: (NSCalendarDate *) _date
56 {
57   ASSIGN(dueDate, _date);
58 }
59
60 - (NSCalendarDate *) taskDueDate
61 {
62   return dueDate;
63 }
64
65 /* iCal */
66
67 - (NSString *) iCalStringTemplate
68 {
69   static NSString *iCalStringTemplate = \
70     @"BEGIN:VCALENDAR\r\n"
71     @"METHOD:REQUEST\r\n"
72     @"PRODID://Inverse groupe conseil/SOGo 0.9\r\n"
73     @"VERSION:2.0\r\n"
74     @"BEGIN:VTODO\r\n"
75     @"UID:%@\r\n"
76     @"CLASS:PUBLIC\r\n"
77     @"STATUS:NEEDS-ACTION\r\n" /* confirmed by default */
78     @"PERCENT-COMPLETE:0\r\n"
79     @"DTSTART:%@Z\r\n"
80     @"DUE:%@Z\r\n"
81     @"DTSTAMP:%@Z\r\n"
82     @"SEQUENCE:1\r\n"
83     @"PRIORITY:5\r\n"
84     @"%@"                   /* organizer */
85     @"%@"                   /* participants and resources */
86     @"END:VTODO\r\n"
87     @"END:VCALENDAR";
88
89   NSCalendarDate *stamp, *lStartDate, *lDueDate;
90   NSString *template, *s;
91   NSTimeZone *utc;
92   unsigned minutes;
93
94   s = [self queryParameterForKey:@"dur"];
95   if ([s length] > 0)
96     minutes = [s intValue];
97   else
98     minutes = 60;
99
100   utc = [NSTimeZone timeZoneWithName: @"GMT"];
101   lStartDate = [self newStartDate];
102   [lStartDate setTimeZone: utc];
103   lDueDate = [lStartDate dateByAddingYears: 0 months: 0 days: 0
104                          hours: 0 minutes: minutes seconds: 0];
105   stamp = [NSCalendarDate calendarDate];
106   [stamp setTimeZone: utc];
107   
108   s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
109   template   = [NSString stringWithFormat:iCalStringTemplate,
110                          [[self clientObject] nameInContainer],
111                          [lStartDate iCalFormattedDateTimeString],
112                          [lDueDate iCalFormattedDateTimeString],
113                          [stamp iCalFormattedDateTimeString],
114                          [self iCalOrganizerString],
115                          s];
116   return template;
117 }
118
119 /* new */
120
121 - (id) newAction
122 {
123   /*
124     This method creates a unique ID and redirects to the "edit" method on the
125     new ID.
126     It is actually a folder method and should be defined on the folder.
127     
128     Note: 'clientObject' is the SOGoAppointmentFolder!
129           Update: remember that there are group folders as well.
130   */
131   NSString *uri, *objectId, *method, *ps;
132
133   objectId = [NSClassFromString(@"SOGoAppointmentFolder")
134                                globallyUniqueObjectId];
135   if ([objectId length] == 0) {
136     return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
137                         reason:@"could not create a unique ID"];
138   }
139
140   method = [NSString stringWithFormat:@"Calendar/%@/editAsTask", objectId];
141   method = [[self userFolderPath] stringByAppendingPathComponent:method];
142
143   /* check if participants have already been provided */
144   ps     = [self queryParameterForKey:@"ps"];
145 //   if (ps) {
146 //     [self setQueryParameter:ps forKey:@"ps"];
147 //   }
148  if (!ps
149      && [[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
150     AgenorUserManager *um;
151     NSArray *uids;
152     NSMutableArray *emails;
153     unsigned i, count;
154
155     /* add all current calendarUIDs as default participants */
156
157     um     = [AgenorUserManager sharedUserManager];
158     uids   = [[self clientObject] calendarUIDs];
159     count  = [uids count];
160     emails = [NSMutableArray arrayWithCapacity:count];
161     
162     for (i = 0; i < count; i++) {
163       NSString *email;
164       
165       email = [um getEmailForUID:[uids objectAtIndex:i]];
166       if (email)
167         [emails addObject:email];
168     }
169     ps = [emails componentsJoinedByString:@","];
170     [self setQueryParameter:ps forKey:@"ps"];
171   }
172   uri = [self completeHrefForMethod:method];
173   return [self redirectToLocation:uri];
174 }
175
176 /* save */
177
178 - (void) loadValuesFromTask: (iCalToDo *) _task
179 {
180   NSTimeZone *uTZ;
181
182   [self loadValuesFromComponent: _task];
183
184   uTZ = [[self clientObject] userTimeZone];
185   dueDate = [_task due];
186 //   if (!dueDate)
187 //     dueDate = [[self startDate] dateByAddingYears: 0 months: 0 days: 0
188 //                                 hours: 1 minutes: 0 seconds: 0];
189   if (dueDate)
190     {
191       [dueDate setTimeZone: uTZ];
192       [dueDate retain];
193     }
194 }
195
196 - (void) saveValuesIntoTask: (iCalToDo *) _task
197 {
198   /* merge in form values */
199   NSArray *attendees, *lResources;
200   iCalRecurrenceRule *rrule;
201   NSCalendarDate *dateTime;
202
203   if (hasStartDate)
204     dateTime = [self taskStartDate];
205   else
206     dateTime = nil;
207   [_task setStartDate: dateTime];
208   if (hasDueDate)
209     dateTime = [self taskDueDate];
210   else
211     dateTime = nil;
212   [_task setDue: dateTime];
213
214   [_task setSummary: [self title]];
215   [_task setUrl: [self url]];
216   [_task setLocation: [self location]];
217   [_task setComment: [self comment]];
218   [_task setPriority:[self priority]];
219   [_task setAccessClass: [self privacy]];
220   [_task setStatus: [self status]];
221
222 //   [_task setCategories: [[self categories] componentsJoinedByString: @","]];
223   
224 #if 0
225   /*
226     Note: bad, bad, bad!
227     Organizer is no form value, thus we MUST NOT change it
228   */
229   [_task setOrganizer:organizer];
230 #endif
231   attendees  = [self participants];
232   lResources = [self resources];
233   if ([lResources count] > 0) {
234     attendees = ([attendees count] > 0)
235       ? [attendees arrayByAddingObjectsFromArray:lResources]
236       : lResources;
237   }
238   [attendees makeObjectsPerformSelector: @selector (setTag:)
239              withObject: @"attendee"];
240   [_task setAttendees:attendees];
241
242   /* cycles */
243   [_task removeAllRecurrenceRules];
244   rrule = [self rrule];
245   if (rrule)
246     [_task addToRecurrenceRules: rrule];
247 }
248
249 - (iCalToDo *) taskFromString: (NSString *) _iCalString
250 {
251   iCalCalendar *calendar;
252   iCalToDo *task;
253
254   calendar = [iCalCalendar parseSingleFromSource: _iCalString];
255   task = (iCalToDo *) [calendar firstChildWithTag: @"vtodo"];
256
257   return task;
258 }
259
260 /* conflict management */
261
262 - (BOOL) containsConflict: (id) _task
263 {
264   NSArray *attendees, *uids;
265   SOGoAppointmentFolder *groupCalendar;
266   NSArray *infos;
267   NSArray *ranges;
268   id folder;
269
270   [self logWithFormat:@"search from %@ to %@", 
271           [_task startDate], [_task due]];
272
273   folder    = [[self clientObject] container];
274   attendees = [_task attendees];
275   uids      = [folder uidsFromICalPersons:attendees];
276   if ([uids count] == 0) {
277     [self logWithFormat:@"Note: no UIDs selected."];
278     return NO;
279   }
280
281   groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
282                           inContext:[self context]];
283   [self debugWithFormat:@"group calendar: %@", groupCalendar];
284   
285   if (![groupCalendar respondsToSelector:@selector(fetchFreeBusyInfosFrom:to:)]) {
286     [self errorWithFormat:@"invalid folder to run freebusy query on!"];
287     return NO;
288   }
289
290   infos = [groupCalendar fetchFreeBusyInfosFrom:[_task startDate]
291                          to:[_task due]];
292   [self debugWithFormat:@"  process: %d tasks", [infos count]];
293
294   ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
295                   andEndDateKey:@"dueDate"];
296   ranges = [ranges arrayByCompactingContainedDateRanges];
297   [self debugWithFormat:@"  blocked ranges: %@", ranges];
298
299   return [ranges count] != 0 ? YES : NO;
300 }
301
302 - (id <WOActionResults>) defaultAction
303 {
304   NSString *ical;
305
306   /* load iCalendar file */
307   
308   // TODO: can't we use [clientObject contentAsString]?
309 //   ical = [[self clientObject] valueForKey:@"iCalString"];
310   ical = [[self clientObject] contentAsString];
311   if ([ical length] == 0)
312     {
313       newTask = YES;
314       ical = [self iCalStringTemplate];
315     }
316   else
317     newTask = NO;
318
319   [self setICalString:ical];
320   [self loadValuesFromTask: [self taskFromString: ical]];
321   
322 //   if (![self canEditComponent]) {
323 //     /* TODO: we need proper ACLs */
324 //     return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
325 //   }
326
327   return self;
328 }
329
330 - (id <WOActionResults>) saveAction
331 {
332   iCalToDo *task;
333   iCalPerson *p;
334   id <WOActionResults> result;
335   NSString *content;
336   NSException *ex;
337
338   if (![self isWriteableClientObject]) {
339     /* return 400 == Bad Request */
340     return [NSException exceptionWithHTTPStatus:400
341                         reason: @"method cannot be invoked on "
342                                 @"the specified object"];
343   }
344
345   task = [self taskFromString: [self iCalString]];
346   if (task == nil) {
347     NSString *s;
348     
349     s = [self labelForKey: @"Invalid iCal data!"];
350     [self setErrorText: s];
351
352     return self;
353   }
354
355   [self saveValuesIntoTask:task];
356   p = [task findParticipantWithEmail:[self emailForUser]];
357   if (p) {
358     [p setParticipationStatus:iCalPersonPartStatAccepted];
359   }
360
361   if ([self checkForConflicts]) {
362     if ([self containsConflict:task]) {
363       NSString *s;
364       
365       s = [self labelForKey:@"Conflicts found!"];
366       [self setErrorText:s];
367
368       return self;
369     }
370   }
371   content = [[task parent] versitString];
372 //   [task release]; task = nil;
373   
374   if (content == nil) {
375     NSString *s;
376     
377     s = [self labelForKey: @"Could not create iCal data!"];
378     [self setErrorText: s];
379     return self;
380   }
381   
382   ex = [[self clientObject] saveContentString:content];
383   if (ex != nil) {
384     [self setErrorText:[ex reason]];
385     return self;
386   }
387
388   if ([[[[self context] request] formValueForKey: @"nojs"] intValue])
389     result = [self redirectToLocation: [self applicationPath]];
390   else
391     result = [self jsCloseWithRefreshMethod: @"refreshTasks()"];
392
393   return result;
394 }
395
396 - (id) changeStatusAction
397 {
398   iCalToDo *task;
399   SOGoTaskObject *taskObject;
400   NSString *content;
401   id ex;
402   int newStatus;
403
404   newStatus = [[self queryParameterForKey: @"status"] intValue];
405
406   taskObject = [self clientObject];
407   task = (iCalToDo *) [taskObject component];
408   switch (newStatus)
409     {
410     case 1:
411       [task setCompleted: [NSCalendarDate calendarDate]];
412       break;
413     case 2:
414       [task setStatus: @"IN-PROCESS"];
415       break;
416     case 3:
417       [task setStatus: @"CANCELLED"];
418       break;
419     default:
420       [task setStatus: @"NEEDS-ACTION"];
421     }
422
423   content = [[task parent] versitString];
424   ex = [[self clientObject] saveContentString: content];
425   if (ex != nil) {
426     [self setErrorText:[ex reason]];
427     return self;
428   }
429   
430   return [self redirectToLocation: [self completeURIForMethod: @".."]];
431 }
432
433 - (id)acceptAction {
434   return [self acceptOrDeclineAction:YES];
435 }
436
437 - (id)declineAction {
438   return [self acceptOrDeclineAction:NO];
439 }
440
441 - (NSString *) saveUrl
442 {
443   return [NSString stringWithFormat: @"%@/saveAsTask",
444                    [[self clientObject] baseURL]];
445 }
446
447 // TODO: add tentatively
448
449 - (id)acceptOrDeclineAction:(BOOL)_accept {
450   // TODO: this should live in the SoObjects
451   NSException *ex;
452
453   if ((ex = [self validateObjectForStatusChange]) != nil)
454     return ex;
455   
456   ex = [[self clientObject] changeParticipationStatus:
457                               _accept ? @"ACCEPTED" : @"DECLINED"
458                             inContext:[self context]];
459   if (ex != nil) return ex;
460   
461   return self;
462 //   return [self redirectToLocation: [self completeURIForMethod:@"../view"]];
463 }
464
465 - (void) setHasStartDate: (BOOL) aBool
466 {
467   hasStartDate = aBool;
468 }
469
470 - (BOOL) hasStartDate
471 {
472   return (!newTask && [self taskStartDate] != nil);
473 }
474
475 - (BOOL) startDateDisabled
476 {
477   return (![self hasStartDate]);
478 }
479
480 - (void) setHasDueDate: (BOOL) aBool
481 {
482   hasDueDate = aBool;
483 }
484
485 - (BOOL) hasDueDate
486 {
487   return (!newTask && [self taskDueDate] != nil);
488 }
489
490 - (BOOL) dueDateDisabled
491 {
492   return (![self hasDueDate]);
493 }
494
495 - (void) setDueDateDisabled: (BOOL) aBool
496 {
497 }
498
499 - (void) setStartDateDisabled: (BOOL) aBool
500 {
501 }
502
503 @end /* UIxTaskEditor */