]> err.no Git - scalable-opengroupware.org/blob - UI/Scheduler/UIxTaskEditor.m
9335c23a105340456717a5ac26326755be2de4a5
[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   SOGoTaskObject *clientObject;
254
255   clientObject = [self clientObject];
256   calendar = [iCalCalendar parseSingleFromSource: _iCalString];
257   task = [clientObject firstTaskFromCalendar: calendar];
258
259   return task;
260 }
261
262 /* conflict management */
263
264 - (BOOL) containsConflict: (id) _task
265 {
266   NSArray *attendees, *uids;
267   SOGoAppointmentFolder *groupCalendar;
268   NSArray *infos;
269   NSArray *ranges;
270   id folder;
271
272   [self logWithFormat:@"search from %@ to %@", 
273           [_task startDate], [_task due]];
274
275   folder    = [[self clientObject] container];
276   attendees = [_task attendees];
277   uids      = [folder uidsFromICalPersons:attendees];
278   if ([uids count] == 0) {
279     [self logWithFormat:@"Note: no UIDs selected."];
280     return NO;
281   }
282
283   groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
284                           inContext:[self context]];
285   [self debugWithFormat:@"group calendar: %@", groupCalendar];
286   
287   if (![groupCalendar respondsToSelector:@selector(fetchFreeBusyInfosFrom:to:)]) {
288     [self errorWithFormat:@"invalid folder to run freebusy query on!"];
289     return NO;
290   }
291
292   infos = [groupCalendar fetchFreeBusyInfosFrom:[_task startDate]
293                          to:[_task due]];
294   [self debugWithFormat:@"  process: %d tasks", [infos count]];
295
296   ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
297                   andEndDateKey:@"dueDate"];
298   ranges = [ranges arrayByCompactingContainedDateRanges];
299   [self debugWithFormat:@"  blocked ranges: %@", ranges];
300
301   return [ranges count] != 0 ? YES : NO;
302 }
303
304 - (id <WOActionResults>) defaultAction
305 {
306   NSString *ical;
307
308   /* load iCalendar file */
309   
310   // TODO: can't we use [clientObject contentAsString]?
311 //   ical = [[self clientObject] valueForKey:@"iCalString"];
312   ical = [[self clientObject] contentAsString];
313   if ([ical length] == 0)
314     {
315       newTask = YES;
316       ical = [self iCalStringTemplate];
317     }
318   else
319     newTask = NO;
320
321   [self setICalString:ical];
322   [self loadValuesFromTask: [self taskFromString: ical]];
323   
324 //   if (![self canEditComponent]) {
325 //     /* TODO: we need proper ACLs */
326 //     return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
327 //   }
328
329   return self;
330 }
331
332 - (id <WOActionResults>) saveAction
333 {
334   iCalToDo *task;
335   iCalPerson *p;
336   id <WOActionResults> result;
337   NSString *content;
338   NSException *ex;
339
340   if (![self isWriteableClientObject]) {
341     /* return 400 == Bad Request */
342     return [NSException exceptionWithHTTPStatus:400
343                         reason: @"method cannot be invoked on "
344                                 @"the specified object"];
345   }
346
347   task = [self taskFromString: [self iCalString]];
348   if (task == nil) {
349     NSString *s;
350     
351     s = [self labelForKey: @"Invalid iCal data!"];
352     [self setErrorText: s];
353
354     return self;
355   }
356
357   [self saveValuesIntoTask:task];
358   p = [task findParticipantWithEmail:[self emailForUser]];
359   if (p) {
360     [p setParticipationStatus:iCalPersonPartStatAccepted];
361   }
362
363   if ([self checkForConflicts]) {
364     if ([self containsConflict:task]) {
365       NSString *s;
366       
367       s = [self labelForKey:@"Conflicts found!"];
368       [self setErrorText:s];
369
370       return self;
371     }
372   }
373   content = [[task parent] versitString];
374 //   [task release]; task = nil;
375   
376   if (content == nil) {
377     NSString *s;
378     
379     s = [self labelForKey: @"Could not create iCal data!"];
380     [self setErrorText: s];
381     return self;
382   }
383   
384   ex = [[self clientObject] saveContentString:content];
385   if (ex != nil) {
386     [self setErrorText:[ex reason]];
387     return self;
388   }
389
390   if ([[[[self context] request] formValueForKey: @"nojs"] intValue])
391     result = [self redirectToLocation: [self applicationPath]];
392   else
393     result = [self jsCloseWithRefreshMethod: @"refreshTasks()"];
394
395   return result;
396 }
397
398 - (id) changeStatusAction
399 {
400   iCalToDo *task;
401   SOGoTaskObject *taskObject;
402   NSString *content;
403   id ex;
404   int newStatus;
405
406   newStatus = [[self queryParameterForKey: @"status"] intValue];
407
408   taskObject = [self clientObject];
409   task = [taskObject task];
410   switch (newStatus)
411     {
412     case 1:
413       [task setCompleted: [NSCalendarDate calendarDate]];
414       break;
415     case 2:
416       [task setStatus: @"IN-PROCESS"];
417       break;
418     case 3:
419       [task setStatus: @"CANCELLED"];
420       break;
421     default:
422       [task setStatus: @"NEEDS-ACTION"];
423     }
424
425   content = [[task parent] versitString];
426   ex = [[self clientObject] saveContentString: content];
427   if (ex != nil) {
428     [self setErrorText:[ex reason]];
429     return self;
430   }
431   
432   return [self redirectToLocation: [self completeURIForMethod: @".."]];
433 }
434
435 - (id)acceptAction {
436   return [self acceptOrDeclineAction:YES];
437 }
438
439 - (id)declineAction {
440   return [self acceptOrDeclineAction:NO];
441 }
442
443 - (NSString *) saveUrl
444 {
445   return [NSString stringWithFormat: @"%@/saveAsTask",
446                    [[self clientObject] baseURL]];
447 }
448
449 // TODO: add tentatively
450
451 - (id)acceptOrDeclineAction:(BOOL)_accept {
452   // TODO: this should live in the SoObjects
453   NSException *ex;
454
455   if ((ex = [self validateObjectForStatusChange]) != nil)
456     return ex;
457   
458   ex = [[self clientObject] changeParticipationStatus:
459                               _accept ? @"ACCEPTED" : @"DECLINED"
460                             inContext:[self context]];
461   if (ex != nil) return ex;
462   
463   return self;
464 //   return [self redirectToLocation: [self completeURIForMethod:@"../view"]];
465 }
466
467 - (void) setHasStartDate: (BOOL) aBool
468 {
469   hasStartDate = aBool;
470 }
471
472 - (BOOL) hasStartDate
473 {
474   return (!newTask && [self taskStartDate] != nil);
475 }
476
477 - (BOOL) startDateDisabled
478 {
479   return (![self hasStartDate]);
480 }
481
482 - (void) setHasDueDate: (BOOL) aBool
483 {
484   hasDueDate = aBool;
485 }
486
487 - (BOOL) hasDueDate
488 {
489   return (!newTask && [self taskDueDate] != nil);
490 }
491
492 - (BOOL) dueDateDisabled
493 {
494   return (![self hasDueDate]);
495 }
496
497 - (void) setDueDateDisabled: (BOOL) aBool
498 {
499 }
500
501 - (void) setStartDateDisabled: (BOOL) aBool
502 {
503 }
504
505 @end /* UIxTaskEditor */