2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #import <SOGo/NSCalendarDate+SOGo.h>
24 #import "UIxTaskEditor.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"
37 @implementation UIxTaskEditor
45 - (void) setTaskStartDate: (NSCalendarDate *) _date
47 [self setStartDate: _date];
50 - (NSCalendarDate *) taskStartDate
52 return [self startDate];
55 - (void) setTaskDueDate: (NSCalendarDate *) _date
57 ASSIGN(dueDate, _date);
60 - (NSCalendarDate *) taskDueDate
67 - (NSString *) iCalStringTemplate
69 static NSString *iCalStringTemplate = \
70 @"BEGIN:VCALENDAR\r\n"
72 @"PRODID://Inverse groupe conseil/SOGo 0.9\r\n"
77 @"STATUS:NEEDS-ACTION\r\n" /* confirmed by default */
78 @"PERCENT-COMPLETE:0\r\n"
85 @"%@" /* participants and resources */
89 NSCalendarDate *stamp, *lStartDate, *lDueDate;
90 NSString *template, *s;
94 s = [self queryParameterForKey:@"dur"];
96 minutes = [s intValue];
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];
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],
124 This method creates a unique ID and redirects to the "edit" method on the
126 It is actually a folder method and should be defined on the folder.
128 Note: 'clientObject' is the SOGoAppointmentFolder!
129 Update: remember that there are group folders as well.
131 NSString *uri, *objectId, *method, *ps;
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"];
140 method = [NSString stringWithFormat:@"Calendar/%@/editAsTask", objectId];
141 method = [[self userFolderPath] stringByAppendingPathComponent:method];
143 /* check if participants have already been provided */
144 ps = [self queryParameterForKey:@"ps"];
146 // [self setQueryParameter:ps forKey:@"ps"];
149 && [[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
150 AgenorUserManager *um;
152 NSMutableArray *emails;
155 /* add all current calendarUIDs as default participants */
157 um = [AgenorUserManager sharedUserManager];
158 uids = [[self clientObject] calendarUIDs];
159 count = [uids count];
160 emails = [NSMutableArray arrayWithCapacity:count];
162 for (i = 0; i < count; i++) {
165 email = [um getEmailForUID:[uids objectAtIndex:i]];
167 [emails addObject:email];
169 ps = [emails componentsJoinedByString:@","];
170 [self setQueryParameter:ps forKey:@"ps"];
172 uri = [self completeHrefForMethod:method];
173 return [self redirectToLocation:uri];
178 - (void) loadValuesFromTask: (iCalToDo *) _task
182 [self loadValuesFromComponent: _task];
184 uTZ = [[self clientObject] userTimeZone];
185 dueDate = [_task due];
187 // dueDate = [[self startDate] dateByAddingYears: 0 months: 0 days: 0
188 // hours: 1 minutes: 0 seconds: 0];
191 [dueDate setTimeZone: uTZ];
196 - (void) saveValuesIntoTask: (iCalToDo *) _task
198 /* merge in form values */
199 NSArray *attendees, *lResources;
200 iCalRecurrenceRule *rrule;
201 NSCalendarDate *dateTime;
204 dateTime = [self taskStartDate];
207 [_task setStartDate: dateTime];
209 dateTime = [self taskDueDate];
212 [_task setDue: dateTime];
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]];
222 // [_task setCategories: [[self categories] componentsJoinedByString: @","]];
227 Organizer is no form value, thus we MUST NOT change it
229 [_task setOrganizer:organizer];
231 attendees = [self participants];
232 lResources = [self resources];
233 if ([lResources count] > 0) {
234 attendees = ([attendees count] > 0)
235 ? [attendees arrayByAddingObjectsFromArray:lResources]
238 [attendees makeObjectsPerformSelector: @selector (setTag:)
239 withObject: @"attendee"];
240 [_task setAttendees:attendees];
243 [_task removeAllRecurrenceRules];
244 rrule = [self rrule];
246 [_task addToRecurrenceRules: rrule];
249 - (iCalToDo *) taskFromString: (NSString *) _iCalString
251 iCalCalendar *calendar;
254 calendar = [iCalCalendar parseSingleFromSource: _iCalString];
255 task = (iCalToDo *) [calendar firstChildWithTag: @"vtodo"];
260 /* conflict management */
262 - (BOOL) containsConflict: (id) _task
264 NSArray *attendees, *uids;
265 SOGoAppointmentFolder *groupCalendar;
270 [self logWithFormat:@"search from %@ to %@",
271 [_task startDate], [_task due]];
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."];
281 groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
282 inContext:[self context]];
283 [self debugWithFormat:@"group calendar: %@", groupCalendar];
285 if (![groupCalendar respondsToSelector:@selector(fetchFreeBusyInfosFrom:to:)]) {
286 [self errorWithFormat:@"invalid folder to run freebusy query on!"];
290 infos = [groupCalendar fetchFreeBusyInfosFrom:[_task startDate]
292 [self debugWithFormat:@" process: %d tasks", [infos count]];
294 ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
295 andEndDateKey:@"dueDate"];
296 ranges = [ranges arrayByCompactingContainedDateRanges];
297 [self debugWithFormat:@" blocked ranges: %@", ranges];
299 return [ranges count] != 0 ? YES : NO;
302 - (id <WOActionResults>) defaultAction
306 /* load iCalendar file */
308 // TODO: can't we use [clientObject contentAsString]?
309 // ical = [[self clientObject] valueForKey:@"iCalString"];
310 ical = [[self clientObject] contentAsString];
311 if ([ical length] == 0)
314 ical = [self iCalStringTemplate];
319 [self setICalString:ical];
320 [self loadValuesFromTask: [self taskFromString: ical]];
322 // if (![self canEditComponent]) {
323 // /* TODO: we need proper ACLs */
324 // return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
330 - (id <WOActionResults>) saveAction
334 id <WOActionResults> result;
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"];
345 task = [self taskFromString: [self iCalString]];
349 s = [self labelForKey: @"Invalid iCal data!"];
350 [self setErrorText: s];
355 [self saveValuesIntoTask:task];
356 p = [task findParticipantWithEmail:[self emailForUser]];
358 [p setParticipationStatus:iCalPersonPartStatAccepted];
361 if ([self checkForConflicts]) {
362 if ([self containsConflict:task]) {
365 s = [self labelForKey:@"Conflicts found!"];
366 [self setErrorText:s];
371 content = [[task parent] versitString];
372 // [task release]; task = nil;
374 if (content == nil) {
377 s = [self labelForKey: @"Could not create iCal data!"];
378 [self setErrorText: s];
382 ex = [[self clientObject] saveContentString:content];
384 [self setErrorText:[ex reason]];
388 if ([[[[self context] request] formValueForKey: @"nojs"] intValue])
389 result = [self redirectToLocation: [self applicationPath]];
391 result = [self jsCloseWithRefreshMethod: @"refreshTasks()"];
396 - (id) changeStatusAction
399 SOGoTaskObject *taskObject;
404 newStatus = [[self queryParameterForKey: @"status"] intValue];
406 taskObject = [self clientObject];
407 task = (iCalToDo *) [taskObject component];
411 [task setCompleted: [NSCalendarDate calendarDate]];
414 [task setStatus: @"IN-PROCESS"];
417 [task setStatus: @"CANCELLED"];
420 [task setStatus: @"NEEDS-ACTION"];
423 content = [[task parent] versitString];
424 ex = [[self clientObject] saveContentString: content];
426 [self setErrorText:[ex reason]];
430 return [self redirectToLocation: [self completeURIForMethod: @".."]];
434 return [self acceptOrDeclineAction:YES];
437 - (id)declineAction {
438 return [self acceptOrDeclineAction:NO];
441 - (NSString *) saveUrl
443 return [NSString stringWithFormat: @"%@/saveAsTask",
444 [[self clientObject] baseURL]];
447 // TODO: add tentatively
449 - (id)acceptOrDeclineAction:(BOOL)_accept {
450 // TODO: this should live in the SoObjects
453 if ((ex = [self validateObjectForStatusChange]) != nil)
456 ex = [[self clientObject] changeParticipationStatus:
457 _accept ? @"ACCEPTED" : @"DECLINED"
458 inContext:[self context]];
459 if (ex != nil) return ex;
462 // return [self redirectToLocation: [self completeURIForMethod:@"../view"]];
465 - (void) setHasStartDate: (BOOL) aBool
467 hasStartDate = aBool;
470 - (BOOL) hasStartDate
472 return (!newTask && [self taskStartDate] != nil);
475 - (BOOL) startDateDisabled
477 return (![self hasStartDate]);
480 - (void) setHasDueDate: (BOOL) aBool
487 return (!newTask && [self taskDueDate] != nil);
490 - (BOOL) dueDateDisabled
492 return (![self hasDueDate]);
495 - (void) setDueDateDisabled: (BOOL) aBool
499 - (void) setStartDateDisabled: (BOOL) aBool
503 @end /* UIxTaskEditor */