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 <NGCards/NSString+NGCards.h>
23 #import <NGCards/NSCalendarDate+NGCards.h>
25 #import <SOGo/AgenorUserManager.h>
26 #import <SOGo/NSCalendarDate+SOGo.h>
29 #import <NGCards/NGCards.h>
30 #import <NGExtensions/NGCalendarDateRange.h>
31 #import <SOGoUI/SOGoDateFormatter.h>
32 #import <Appointments/SOGoAppointmentFolder.h>
33 #import <Appointments/SOGoAppointmentObject.h>
34 #import "UIxComponent+Agenor.h"
36 #import "UIxAppointmentEditor.h"
40 @implementation UIxAppointmentEditor
43 return [super version] + 0 /* v2 */;
47 NSAssert2([super version] == 2,
48 @"invalid superclass (%@) version %i !",
49 NSStringFromClass([self superclass]), [super version]);
60 - (void) setAptStartDate: (NSCalendarDate *)_date
62 [self setStartDate: _date];
65 - (NSCalendarDate *) aptStartDate
67 return [self startDate];
70 - (void) setAptEndDate: (NSCalendarDate *) _date
72 ASSIGN(endDate, _date);
75 - (NSCalendarDate *) aptEndDate
82 - (NSString *) transparency
89 - (NSString *)iCalStringTemplate {
90 static NSString *iCalStringTemplate = \
91 @"BEGIN:VCALENDAR\r\n"
93 @"PRODID://Inverse groupe conseil/SOGo 0.9\r\n"
98 @"STATUS:CONFIRMED\r\n" /* confirmed by default */
105 @"%@" /* organizer */
106 @"%@" /* participants and resources */
111 NSCalendarDate *lStartDate, *lEndDate, *stamp;
112 NSString *template, *s;
115 s = [self queryParameterForKey:@"dur"];
117 minutes = [s intValue];
121 utc = [NSTimeZone timeZoneWithName: @"GMT"];
122 lStartDate = [self newStartDate];
123 [lStartDate setTimeZone: utc];
124 lEndDate = [lStartDate dateByAddingYears: 0 months: 0 days: 0
125 hours: 0 minutes: minutes seconds: 0];
127 stamp = [NSCalendarDate calendarDate];
128 [stamp setTimeZone: utc];
130 s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
131 template = [NSString stringWithFormat:iCalStringTemplate,
132 [[self clientObject] nameInContainer],
133 [stamp iCalFormattedDateTimeString],
134 [lStartDate iCalFormattedDateTimeString],
135 [lEndDate iCalFormattedDateTimeString],
137 [self iCalOrganizerString],
147 This method creates a unique ID and redirects to the "edit" method on the
149 It is actually a folder method and should be defined on the folder.
151 Note: 'clientObject' is the SOGoAppointmentFolder!
152 Update: remember that there are group folders as well.
154 NSString *uri, *objectId, *method, *ps;
156 objectId = [NSClassFromString(@"SOGoAppointmentFolder")
157 globallyUniqueObjectId];
158 if ([objectId length] == 0) {
159 return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
160 reason:@"could not create a unique ID"];
163 method = [NSString stringWithFormat:@"Calendar/%@/editAsAppointment", objectId];
164 method = [[self userFolderPath] stringByAppendingPathComponent:method];
166 /* check if participants have already been provided */
167 ps = [self queryParameterForKey:@"ps"];
169 // [self setQueryParameter:ps forKey:@"ps"];
172 && [[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
173 AgenorUserManager *um;
175 NSMutableArray *emails;
178 /* add all current calendarUIDs as default participants */
180 um = [AgenorUserManager sharedUserManager];
181 uids = [[self clientObject] calendarUIDs];
182 count = [uids count];
183 emails = [NSMutableArray arrayWithCapacity:count];
185 for (i = 0; i < count; i++) {
188 email = [um getEmailForUID:[uids objectAtIndex:i]];
190 [emails addObject:email];
192 ps = [emails componentsJoinedByString:@","];
193 [self setQueryParameter:ps forKey:@"ps"];
195 uri = [self completeHrefForMethod:method];
196 return [self redirectToLocation:uri];
201 - (void) loadValuesFromAppointment: (iCalEvent *) appointment
205 [self loadValuesFromComponent: appointment];
207 uTZ = [[self clientObject] userTimeZone];
208 endDate = [appointment endDate];
210 endDate = [[self startDate] dateByAddingYears: 0 months: 0 days: 0
211 hours: 1 minutes: 0 seconds: 0];
213 [endDate setTimeZone: uTZ];
217 - (void) saveValuesIntoAppointment: (iCalEvent *) _appointment
219 /* merge in form values */
220 NSArray *attendees, *lResources;
221 iCalRecurrenceRule *rrule;
223 [_appointment setStartDate:[self aptStartDate]];
224 [_appointment setEndDate:[self aptEndDate]];
226 [_appointment setSummary: [self title]];
227 [_appointment setUrl: [self url]];
228 [_appointment setLocation: [self location]];
229 [_appointment setComment: [self comment]];
230 [_appointment setPriority:[self priority]];
231 [_appointment setAccessClass: [self privacy]];
232 [_appointment setStatus: [self status]];
234 // [_appointment setCategories: [[self categories] componentsJoinedByString: @","]];
236 [_appointment setTransparency: [self transparency]];
241 Organizer is no form value, thus we MUST NOT change it
243 [_appointment setOrganizer:organizer];
245 attendees = [self participants];
246 lResources = [self resources];
247 if ([lResources count] > 0) {
248 attendees = ([attendees count] > 0)
249 ? [attendees arrayByAddingObjectsFromArray: lResources]
252 [attendees makeObjectsPerformSelector: @selector (setTag:)
253 withObject: @"attendee"];
254 [_appointment setAttendees: attendees];
257 [_appointment removeAllRecurrenceRules];
258 rrule = [self rrule];
260 [_appointment addToRecurrenceRules: rrule];
263 - (iCalEvent *) appointmentFromString: (NSString *) _iCalString
265 iCalCalendar *calendar;
266 iCalEvent *appointment;
268 calendar = [iCalCalendar parseSingleFromSource: _iCalString];
269 appointment = (iCalEvent *) [calendar firstChildWithTag: @"vevent"];
274 /* conflict management */
276 - (BOOL) containsConflict: (id) _apt
278 NSArray *attendees, *uids;
279 SOGoAppointmentFolder *groupCalendar;
284 [self logWithFormat:@"search from %@ to %@",
285 [_apt startDate], [_apt endDate]];
287 folder = [[self clientObject] container];
288 attendees = [_apt attendees];
289 uids = [folder uidsFromICalPersons:attendees];
290 if ([uids count] == 0) {
291 [self logWithFormat:@"Note: no UIDs selected."];
295 groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
296 inContext:[self context]];
297 [self debugWithFormat:@"group calendar: %@", groupCalendar];
299 if (![groupCalendar respondsToSelector:@selector(fetchFreeBusyInfosFrom:to:)]) {
300 [self errorWithFormat:@"invalid folder to run freebusy query on!"];
304 infos = [groupCalendar fetchFreeBusyInfosFrom:[_apt startDate]
306 [self debugWithFormat:@" process: %d events", [infos count]];
308 ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey: @"startDate"
309 andEndDateKey: @"endDate"];
310 ranges = [ranges arrayByCompactingContainedDateRanges];
311 [self debugWithFormat:@" blocked ranges: %@", ranges];
313 return [ranges count] != 0 ? YES : NO;
320 /* for testing only */
325 req = [[self context] request];
326 apt = [self appointmentFromString: [self iCalString]];
327 [self saveValuesIntoAppointment:apt];
328 content = [[apt parent] versitString];
329 [self logWithFormat:@"%s -- iCal:\n%@",
336 - (id<WOActionResults>) defaultAction
340 /* load iCalendar file */
342 // TODO: can't we use [clientObject contentAsString]?
343 // ical = [[self clientObject] valueForKey:@"iCalString"];
344 ical = [[self clientObject] contentAsString];
345 if ([ical length] == 0) /* a new appointment */
346 ical = [self iCalStringTemplate];
348 [self setICalString:ical];
349 [self loadValuesFromAppointment: [self appointmentFromString: ical]];
351 // if (![self canEditComponent]) {
352 // /* TODO: we need proper ACLs */
353 // return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
358 - (id <WOActionResults>) saveAction
362 id <WOActionResults> result;
366 if (![self isWriteableClientObject]) {
367 /* return 400 == Bad Request */
368 return [NSException exceptionWithHTTPStatus:400
369 reason:@"method cannot be invoked on "
370 @"the specified object"];
373 apt = [self appointmentFromString: [self iCalString]];
377 s = [self labelForKey:@"Invalid iCal data!"];
378 [self setErrorText:s];
382 [self saveValuesIntoAppointment:apt];
383 p = [apt findParticipantWithEmail:[self emailForUser]];
385 [p setParticipationStatus:iCalPersonPartStatAccepted];
388 if ([self checkForConflicts]) {
389 if ([self containsConflict:apt]) {
392 s = [self labelForKey:@"Conflicts found!"];
393 [self setErrorText:s];
398 content = [[apt parent] versitString];
399 // [apt release]; apt = nil;
401 if (content == nil) {
404 s = [self labelForKey:@"Could not create iCal data!"];
405 [self setErrorText:s];
409 ex = [[self clientObject] saveContentString:content];
411 [self setErrorText:[ex reason]];
415 if ([[[[self context] request] formValueForKey: @"nojs"] intValue])
416 result = [self redirectToLocation: [self applicationPath]];
418 result = [self jsCloseWithRefreshMethod: @"refreshAppointmentsAndDisplay()"];
423 - (NSString *) saveUrl
425 return [NSString stringWithFormat: @"%@/saveAsAppointment",
426 [[self clientObject] baseURL]];
431 return [self acceptOrDeclineAction:YES];
436 return [self acceptOrDeclineAction:NO];
439 // TODO: add tentatively
441 - (id) acceptOrDeclineAction: (BOOL) _accept
443 // TODO: this should live in the SoObjects
446 if ((ex = [self validateObjectForStatusChange]) != nil)
449 ex = [[self clientObject] changeParticipationStatus:
450 _accept ? @"ACCEPTED" : @"DECLINED"
451 inContext:[self context]];
452 if (ex != nil) return ex;
455 // return [self redirectToLocation: [self completeURIForMethod: @"../view"]];
458 @end /* UIxAppointmentEditor */